看了那么多文章,收益匪浅,所以现在准备多写点文章,把自己学到的分享给大家。
LDD3是一本好书,对Linux内核驱动模型有一个比较系统的讲述,但是并不够细致,比如对一个简单的字符驱动,调用register_chrdev()函数,内核都干了写什么事呢?该书并没有详细讲述。
介绍两个核心数据结构:chrdevs和cdev_map,这两个数据结构保存了字符内核中字符驱动的所有信息,添加字符驱动的过程实际上就是将自己的驱动接口添加到这两个数据结构中。
向内核注册一个新的字符驱动主要分为两个过程:(1)寻找一个可用的设备号(2)将驱动程序添加到内核
(1) 查询chrdevs表,chrdevs实质上是一个哈希表,里面记录了所有已经分配了的设备号,通过查找该表就可以知道当前的设备号有没有被使用,如果没有被使用则添加一个对应的char_device_struct结构,里面记录的主,此设备好以及设备号的范围,如果需要动态分配设备号则反向遍历该表寻找一个最大的主设备好,添加char_device_struct结构并返回成功,否则失败。
(2)如果第一步成功,则新分配一个cdev结构,这里会用到第二个关键数据结构,struct kobj_map,通过第一步已经找到了一个可用的设备号,现在还需要将驱动程序添加进内核的某处,这个地方就是cdev_map,该结构的probe指针数组的每一个元素都是一个链表,它的void *data成员实际上就是代表设备驱动程序的struct cdev结构,其里面又有struct file_operations 结构,这个结构就是我们注册的实际驱动程序了。
相关数据结构:
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
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;
};
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; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
static struct kobj_map *cdev_map;
用户空间怎么调用自己的驱动程序呢?说来复杂,但是这里只简单的分析下原理:
用户空间执行open系统调用,回通过kobj_lookup(cdev_map,inode->i_rdev,&idx)函数通过用户空间传递经来的设备号i_rdev查找cdev_map表找到对应的cdev结构,去除ops数据赋值非filp->f_op,这样后面实现了用户空间通过文件描述符与内核空间的驱动挂钩了。
今天就到这吧。