前言
(1)前面我们介绍了如何自动产生设备节点,详细分析了驱动层代码。但是我们有没有发现一个问题,我们每次设备节点的主设备号都是240,次设备号是0。主设备能够理解,这个是系统自动分配的,那么为什么次设备号永远是0呢?我能不能是其他的?
(2)答案是可以的。
什么是Linux设备号
(1)为了方便管理, Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。
(2)Linux 提供了一个名为 dev_t 的数据类型表示设备号,而dev_t 其实就是 unsigned int 类型,是一个 32 位的数据类型。
(3)这 32 位的数据构成了主设备号和次设备号两部分,其中高 12 位为主设备号,第 20 位为次设备号。
(4)因此 Linux系统中主设备号范围为 0~4095。
如何动态分配设备号
alloc_chrdev_region()简单介绍
(1)首先需要知道register_chrdev()对于设备号的申请规则是什么。==在register_chrdev()中,他只会申请到一个没有被使用到的主设备号,及其他之下的所有次设备号。==我们会发现,这样太占用次设备号资源。
(2)所以这里要做的是,申请一个主设备号和指定的次设备号,这一部分用于注册驱动程序。我们也就需要了解alloc_chrdev_region()函数。
/*申请一个主次设备号空间,这个区域给当前驱动使用,主设备号和次设备号存入第一个参数中 *参数一 :第一个设备的主设备号和次设备号 *参数二 : 从哪个次设备号开始 *参数三 :想获得几个次设备号 *参数四 :驱动程序名 *返回值:如果申请失败,返回一个负数 */ ret = alloc_chrdev_region(&dev, 0, 2, "hello");
cdev_add()简单介绍
(1)我们申请到了设备号,还需要将设备号传递给驱动,所以需要使用到cdev_add()函数。
(2)而这个传递是如何进行的呢?这里就需要引入一个cdev结构体,这个结构体用于描述一个字符设备,我们需要将设备号传递给这个结构体。
struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; }; static struct cdev hello_cdev; //用于表示一个字符设备 /*申请完设备号之后,需要进行 *参数一 : 要增加的cdev *参数二 : 将主次设备号dev传入 *参数三 : 决定占用几个主次设备号,这里占2个主次设备号 *返回值 : 如果返回值不是0,表示错误 */ ret = cdev_add(&hello_cdev, dev, 2);
cdev_init()简单介绍
(1)我们的字符设备拥有了设备号之后,还需要file_operations结构体,用于进行描述这个驱动如何使用。
(2)所以这个时候,就需要使用到cdev_init()函数,将file_operations结构体传递给cdv结构体。
static const struct file_operations hello_drv = { .owner = THIS_MODULE, .read = hello_read, .write = hello_write, .open = hello_open, .release = hello_release, }; //申请完设备号之后,需要进行初始化。初始化cdev,让cdev与file_operations结构体挂钩 cdev_init(&hello_cdev, &hello_drv);
cdev_del()
这个就是用来删除cdev 的。传入cdev结构体即可
unregister_chrdev_region()
这个是将申请到的主次设备号空间释放
本章与上一章的改动之处
(1)驱动安装:
<1>上一章在进行次设备号的分配上,是直接采用的register_chrdev()函数进行分配。这种方法会导致所有次设备号都被占用。
<2>于是我们这一章,将使用 alloc_chrdev_region(),cdev_add()和cdev_init()函数来代替register_chrdev()函数。
(2)删除驱动:
<1>在上一章中,我们删除驱动是调用的unregister_chrdev()函数。
<2>而在我们当前这一章节,需要使用cdev_del()删除cdev,然后调用unregister_chrdev_region()释放主次设备号。
/******** 驱动安装 ********/ /* *原来 */ major = register_chrdev(0, "100ask_hello", &hello_drv); /* *现在 */ // 如果是register_chrdev,那么会申请到一个主设备号,以及他的所有次设备号。那样太占用次设备号资源 // 所以这里要做,申请一个主设备号和指定的次设备号,这一部分用于注册驱动程序 /*申请一个主次设备号空间,这个区域给当前驱动使用,主设备号和次设备号存入第一个参数中 *参数一 :第一个设备的主设备号和次设备号 *参数二 : 从哪个次设备号开始 *参数三 :想获得几个次设备号 *参数四 :驱动程序名 *返回值:如果申请失败,返回一个负数 */ ret = alloc_chrdev_region(&dev, 0, 2, "hello"); //判断是否申请主次设备号成功,如果失败进入 if (ret < 0) { //打印无法获得设备号 printk(KERN_ERR "alloc_chrdev_region() failed for hello\n"); //返回无效参数 return -EINVAL; } //申请完设备号之后,需要进行初始化。初始化cdev,让cdev与file_operations结构体挂钩 cdev_init(&hello_cdev, &hello_drv); /*申请完设备号之后,需要进行 *参数一 : 要增加的cdev *参数二 : 将主次设备号dev传入 *参数三 : 决定占用几个主次设备号,这里占2个主次设备号 *返回值 : 如果返回值不是0,表示错误 */ ret = cdev_add(&hello_cdev, dev, 2); //如果返回值不为0,打印错误 if (ret) { //打印增加cdev失败 printk(KERN_ERR "cdev_add() failed for hello\n"); //返回无效参数 return -EINVAL; } /******** 驱动卸载 ********/ /* *原来 */ //卸载驱动程序 //第一个参数是主设备号,第二个是名字 unregister_chrdev(major, "100ask_hello"); /* *现在 */ //删除cdev cdev_del(&hello_cdev); //将申请到的主次设备号空间释放 unregister_chrdev_region(dev, 2);