10_申请字符类设备号

申请字符类设备号

字符设备号简介

​ Linux 的设备管理是和文件系统紧密结合的, 各种设备都以文件的形式存放在/dev 目录下, 称为设备文件。 应用程序可以打开、 关闭和读写这些设备文件, 完成对设备的操作, 就像操作普通的数据文件一样。

​ 为了管理这些设备, 系统为设备编了号, 每个设备号又分为主设备号和次设备号。 主设备号用来区分不同类型的设备, 而次设备号用来区分同一类型的多个设备。

设备号的组成

一个字符设备或者块设备都有一个主设备号和次设备号。 主设备号和次设备号统称为设备号。 主设备号用来表示一个特定的驱动程序。 次设备号用来表示使用该驱动程序的各个设备。

1.字符设备和杂项设备的区别(复习)

杂项设备的主设备号是固定的,固定为10,那么我们要学习的字符类设备就需要自己或者系统来给我们分配了。

杂项设备可以自动生成设备节点,字符设备需要我们自己生成设备节点。

2.注册字符类设备号的俩个方法。

头文件 #include <linux/fs.h>

静态分配设备号

​ 静态分配设备号, 就是驱动程序开发者通过静态指定一个设备号。 对于一部分常用的设备, 内核开发者已经为其分配了设备号。 这些设备号可以在内核源码 documentation/ devices.txt 文件中找到。 如果只有开发者自己使用这些设备驱动程序, 那么其可以选择一个尚未使用的设备号。 在不添加新硬件的时候, 这种方式不会产生设备号冲突。 但是当添加新硬件时, 则很可能造成设备号冲突, 影响设备的使用。 使用“cat /proc/devices”命令即可查看当前系统中所有已经使用了的主设备号, 如图所示:

image-20240422173733707

设备号的静态申请函数如下所示:

register_chrdev_region(dev_t, unsigned, const char *);

指定一个设备号, 需要明确知道我们的系统里面那些设备号没有用。

image-20240422235502074

Linux 提供了一个名为dev_t 的数据类型表示设备号, dev_t 定义在文件include/linux/types.h 里面, 定义如下:

typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;

dev_t 是个 32 位的变量, 其中 12 位用来表示主设备号, 20 位用来表示次设备号。 因此 Linux 系统中主设备号范围为 0~4095, 所以大家在选择主设备号的时候一定 不要超过这个范围。 在文件include/linux/kdev_t.h 中提供了几个操作设备号的宏定义, 如下所示:
Linux 提供了几个宏定义来操作设备号:

#define MINORBITS20 //次设备号的位数, 一共是 20 位
#define MINORMASK ((1U << MINORBITS) - 1) //次设备号的掩码
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) //在 dev_t 里面获取我们的主设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) //在 dev_t 里面获取我们的次设备号
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) //将我们的主设备号和次设备号组成一个 dev_t 类型。第一个参数是主设备号, 第二个参数是次设备号  

动态分配 设备号

由于静态分配设备号可能存在冲突问题, 因此建议使用动态分配设备号。 在注册字符设备之前先申请一个设备号, 系统会自动给你一个没有被使用的设备号, 这样就避免了冲突。 卸载驱动的时候释放掉这个设备号即可, 设备号的动态申请函数如下:

alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);image-20240422205517129

image-20240422205536129

3.注销设备号

我们使用的是

unregister_chrdev_region(dev_t, unsigned);

注销字符设备之后要释放掉设备号, 设备号释放函数如下:

image-20240422205643006

示例

chrdev.c

#include <linux/init.h>   //包含宏定义的头文件
#include <linux/module.h> //包含初始化加载模块的头文件
#include <linux/kdev_t.h>
#include <linux/fs.h>

#define DEVICE_NUMBER 1        // 定义次设备号的个数
#define DEVICE_SNAME "schrdev" // 定义静态注册设备的名称
#define DEVICE_ANAME "achrdev" // 定义动态注册设备的名称
#define DEVICE_MINOR_NUMBER 0  // 定义次设备号的起始地址

static int major_num; // 主设备号
static int minor_num; // 次设备号

module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR);

static int hello_init(void)
{
    dev_t dev_num; // 设备号
    int ret;
    if (major_num)
    {
        dev_num = MKDEV(major_num, minor_num); // 主设备号和次设备号组成一个 dev_t 设备号
        ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME); //静态分配设备号
        if(ret < 0)
        {
            printk("register_chrdev_region is error\n");
        }
        printk("register_chrdev_region is ok\n");
        printk("major_num = %d\n", major_num);
        printk("minor_num = %d\n", minor_num);
    }
    else
    {
        ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME); //动态分配设备号
        if(ret < 0)
        {
            printk("alloc_chrdev_region is error\n");
        }
        printk("alloc_chrdev_region is ok\n");
        major_num = MAJOR(dev_num); //在 dev_t 里面获取我们的主设备号
        minor_num = MINOR(dev_num); //在 dev_t 里面获取我们的次设备号
        printk("major_num = %d\n", major_num);
        printk("minor_num = %d\n", minor_num);
    }

    return 0;
}

static void hello_exit(void)
{
    unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER); //注销设备号
    printk("byby\n"); // 内核模块卸载的时候打印"byb byb
}

module_init(hello_init); // 驱动模块的入口
module_exit(hello_exit); // 驱动模块的出口

MODULE_LICENSE("GPL"); // 声明模块拥有开源许可证

Makefile

obj-m +=chrdev.o
KDIR:=/home/mzx/imx6ull/linux-imx-rel_imx_4.1.15_2.1.0_ga 
PWD?=$(shell pwd)

all:
	make -C $(KDIR) M=$(PWD) modules
posted @ 2024-04-25 22:34  爱吃冰激凌的黄某某  阅读(10)  评论(0编辑  收藏  举报