2.5 字符设备的注册
《深入Linux设备驱动程序内核机制》第2章字符设备驱动程序,本章描述了字符设备驱动程序内核框架的技术细节。本节为大家介绍字符设备的注册。
2.5 字符设备的注册
前面已经讨论了字符设备对象的分配、初始化及设备号等概念,在一个字符设备初始化阶段完成之后,就可以把它加入到系统中,这样别的模块才可以使用它。把一个字符设备加入到系统中所需调用的函数为cdev_add,它在Linux源码中的实现如下:
- <fs/char_dev.c>
- int cdev_add(struct cdev *p, dev_t dev, unsigned count)
- {
- p->devdev = dev;
- p->countcount = count;
- return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
- }
其中,参数p为要加入系统的字符设备对象的指针,dev为该设备的设备号,count表示从次设备号开始连续的设备数量。
cdev_add的核心功能通过kobj_map函数来实现,后者通过操作一个全局变量cdev_map来把设备(*p)加入到其中的哈希链表中。cdev_map的定义如下:
- <fs/char_dev.c>
- static struct kobj_map *cdev_map;
这是一个struct kobj_map指针类型的全局变量,在Linux系统启动期间由chrdev_init函数负责初始化。struct kobj_map的定义如下:
- <drivers/base/map.c>
- struct kobj_map {
- struct probe {
- struct probe *next;
- dev_t dev;
- unsigned long range;
- struct module *owner;
- kobj_probe_t *get;
- int (*lock)(dev_t, void *);
- void *data;
- } *probes[255];
- struct mutex *lock;
- };
kobj_map函数中哈希表的实现原理和前面注册分配设备号中的几乎完全一样,通过要加入系统的设备的主设备号major(major=MAJOR(dev))来获得probes数组的索引值i(i = major % 255),然后把一个类型为struct probe的节点对象加入到probes[i]所管理的链表中,如图2-6所示。其中struct probe所在的矩形块中的深色部分是我们重点关注的内容,记录了当前正在加入系统的字符设备对象的有关信息。其中,dev是它的设备号,range是从次设备号开始连续的设备数量,data是一void *变量,指向当前正要加入系统的设备对象指针p。图2-6展示了两个满足主设备号major % 255 = 2的字符设备通过调用cdev_add之后,cdev_map所展现出来的数据结构状态。
图2-6 通过cdev_add向系统中加入设备 |
所以,简单地说,设备驱动程序通过调用cdev_add把它所管理的设备对象的指针嵌入到一个类型为struct probe的节点之中,然后再把该节点加入到cdev_map所实现的哈希链表中。
对系统而言,当设备驱动程序成功调用了cdev_add之后,就意味着一个字符设备对象已经加入到了系统,在需要的时候,系统就可以找到它。对用户态的程序而言,cdev_add调用之后,就已经可以通过文件系统的接口呼叫到我们的驱动程序,本章稍后将会详细描述这一过程。
不过在开始文件系统如何通过cdev_map来使用驱动程序提供的服务这个话题之前,我们要来看看与cdev_add相对应的另一个函数cdev_del。其实光通过这个函数名,读者想必也想到这个函数的作用了:在cdev_add中我们动态分配了struct probe类型的节点,那么当对应的设备从系统中移除时,显然需要将它们从链表中删除并释放节点所占用的内存空间。在cdev_map所管理的链表中查找对应的设备节点时使用了设备号。cdev_del函数的实现如下:
- <fs/char_dev.c>
- void cdev_del(struct cdev *p)
- {
- cdev_unmap(p->dev, p->count);
- kobject_put(&p->kobj);
- }
对于以内核模块形式存在的驱动程序,作为通用的规则,模块的卸载函数应负责调用这个函数来将所管理的设备对象从系统中移除。