这是最后的运行流程,用户open设备节点,通过主设备号找到驱动程序,根据次设备号找到对应的设备,使用驱动函数中的read(),write()等函数操作设备。
为了搭建这样的运行时环境,需要做如下工作:
1 驱动程序加载(申请主设备号,在/proc/devices下创建设备)
2 根据具体设备创建设备节点 (查询合适的主设备号和可用的次设备号 在/dev下面创建设备节点)
先说说设备号的申请吧
int register_chrdev_region(dev_t first, unsigned int -count, char*name) 这个是静态申请 第一个参数是自己定义的一个32位无符号整数,第二个是要申请的连续设备号的个数,第三个参数是设备名称
int alloc_chrdev_region(dev_t *dev, unsigned int -firstminor, unsigned int -count, char *name)
这个是动态申请 dev中存放的是返回值 第二个参数是只第一个次设备号 一般为0,count还是指支持的设备个数,name是设备名
比如,若first为 0x3FFFF0,count为0x5,那么该函数就会为5个设备注册设备号,分别是0x3FFFF0、 0x3FFFF1、 0x3FFFF2、 0x3FFFF3、 0x3FFFF4,其中0x3(高12位)为这5个设备所共有的主设备号(也就是说这5个设备都使用同一个驱动程序)。而0xFFFF0、 0xFFFF1、 0xFFFF2、 0xFFFF3、 0xFFFF4就分别是这5个设备的次设备号了。
static struct char_device_struct { struct char_device_struct *next; // 指向散列冲突链表中的下一个元素的指针 unsigned int major; // 主设备号 unsigned int baseminor; // 起始次设备号 int minorct; // 设备编号的范围大小 对设备个数的约束 char name[64]; // 处理该设备编号范围内的设备驱动的名称 struct file_operations *fops; // 没有使用 struct cdev *cdev; // 指向字符设备驱动程序描述符的指针 } *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; 注意,内核并不是为每一个字符设备编号定义一个 char_device_struct 结构,而是为一组对应同一个字符设备驱动的设备编号范围定义一个 char_device_struct 结构。 chrdevs 散列表的大小是 255,散列算法是把每组字符设备编号范围的主设备号以 255 取模插入相应的散列桶中。(如果出现相同的主设备号,还可以使用拉链法处理) 不过不管怎么样,上面的这个结构体只与主设备号有关(或者说只与驱动有关,这也是/proc/devices的源头吧)
内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。 这三个函数都会调用一个共用的 __register_chrdev_region() 函数来注册一组设备编号范围(即一个 char_device_struct 结构)。 static struct char_device_struct * __register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name) { struct char_device_struct *cd, **cp; int ret = 0; int i; cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL); if (cd == NULL) return ERR_PTR(-ENOMEM); mutex_lock(&chrdevs_lock); if (major == 0) { //主设备号为0就自动分配 从最后向前寻找 找一个主设备号没有被占用的 for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) if (chrdevs[i] == NULL) break; if (i == 0) { ret = -EBUSY; goto out; } major = i; ret = major; } cd->major = major; cd->baseminor = baseminor; cd->minorct = minorct; strncpy(cd->name,name, 64);//给结构体中的成员赋值 i = major_to_index(major); for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) if ((*cp)->major > major || ((*cp)->major == major && ( ((*cp)->baseminor >= baseminor) || ((*cp)->baseminor + (*cp)->minorct > baseminor)) )) break; if (*cp && (*cp)->major == major) { int old_min = (*cp)->baseminor; int old_max = (*cp)->baseminor + (*cp)->minorct - 1; int new_min = baseminor; int new_max = baseminor + minorct - 1; if (new_max >= old_min && new_max <= old_max) { ret = -EBUSY; goto out; } if (new_min <= old_max && new_min >= old_min) { ret = -EBUSY; goto out; } } cd->next = *cp; *cp = cd; mutex_unlock(&chrdevs_lock); return cd; out: mutex_unlock(&chrdevs_lock); kfree(cd); return ERR_PTR(ret); }
int register_chrdev_region(dev_t from, unsigned count, const char *name) { struct char_device_struct *cd; dev_t to = from + count; dev_t n, next; for (n = from; n < to; n = next) { next = MKDEV(MAJOR(n)+1, 0); if (next > to) next = to; cd = __register_chrdev_region(MAJOR(n), MINOR(n), next - n, name); if (IS_ERR(cd)) goto fail; } return 0; fail: to = n; for (n = from; n < to; n = next) { next = MKDEV(MAJOR(n)+1, 0); kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n)); } return PTR_ERR(cd); } register_chrdev_region() 函数用于分配指定的设备编号范围。如果申请的设备编号范围跨越了主设备号,它会把分配范围内的编号按主设备号分割成较小的子范围, 并在每个子范围上调用 __register_chrdev_region() 。如果其中有一次分配失败的话,那会把之前成功分配的都全部退回。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) { struct char_device_struct *cd; cd = __register_chrdev_region(0, baseminor, count, name); //直接动态分配 if (IS_ERR(cd)) return PTR_ERR(cd); *dev = MKDEV(cd->major, cd->baseminor); return 0; } alloc_chrdev_region() 函数用于动态申请设备编号范围,这个函数好像并没有检查范围过大的情况,不过动态分配总是找个空的散列桶,所以问题也不大。通过指针参数返回实际获得的起始设备编号。
总结上述,申请设备号的最终结果就是创建了一个对应的结构体置于一个链表中。
上述只是一部分,/proc/devices中有内容了,cdev_map链表中也有提供给用户的信息了。可是/dev中还是没有设备节点啊?当然你可以手动创建,可那时虚拟的,不是具体意义上的设备
那么设备的监测到/dev下设备节点的创建又经历了什么过程呢?
当linux系统启动时,/dev目录是空的。udev程序扫描/sys/class目录,查找名字为dev的文件。
每一个这样的dev文件,其内容是系统支持逻辑设备的主次设备号。udev根据主次设备号,在/dev目录
下创建相应的文件。同时,udev会根据配置文件,分配文件名并创建符号链接。最终,对于此系统内核
支持的每一种设备,都会在/dev目录下有相应的设备文件。
struct class *myclass = class_create(THIS_MODULE, “my_device_driver”); class的名字 /sys/class下的
device_create(myclass, NULL, MKDEV(major_num, 0), NULL, “my_device”); 设备的名字 /dev下的 创建设备节点 不需要手动创建了
这样的module被加载时,udev daemon就会自动在/dev下创建my_device设备文件。
还有一个问题:设备节点的创建都在驱动init中,怎么没有看到设备的具体信息呢?这与驱动的编写有关系,比如总线设备,设备信息与驱动信息就是分开的。字符设备
有很多是写在一起的,可能字符设备的具体信息影响不大。刚做了个测试,我们记得申请主设备号的时候还包括从设备号,有一个从设备号的起始值与范围,其实这是个约束。其实他就对应了具体的设备,所以在自己创建设备节点的时候要注意,你到底要访问那个设备,从设备号是个关键,创建设备节点要与之对应。这也充分说明了字符设备驱动中,设备与驱动是一体的,他没有注册驱动的说法,不像总线设备那样,设备与驱动的分开注册的。