《LDD3》这本书中“字符设备驱动程序”一章有这样一段话:
只要cdev_add返回了,我们的设备就“活”了,它的操作就会被内核调用。
这里就研究一下cdev_add
究竟如何让设备“活”过来,以及用户空间访问字符设备节点时,内核的处理流程。
从cdev_add开始分析
先从cdev_add()
入手:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
int error;
p->dev = dev;
p->count = count;
error = kobj_map(cdev_map, dev, count, NULL,
exact_match, exact_lock, p);
if (error)
return error;
kobject_get(p->kobj.parent); /* 增加parent的引用计数 */
return 0;
}
这个函数除了增加parent
的引用计数外,只有一个kobj_map()
的函数调用,所以重要的操作应该都通过该函数进行;函数定义再drivers/base/map.c
中:
int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,
struct module *module, kobj_probe_t *probe,
int (*lock)(dev_t, void *), void *data);
cdev_map
结合cdev_add()
调用该函数时传入的参数,发现一个重要的参数和数据结构,struct kobj_map
类型的cdev_map
,这是一个定义在fs/char_dev.c
中的全局变量;先来看一下struct kobj_map
的结构定义:
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
中有一个指向struct probe
结构的数组,长度为255,而struct probe
结构中包含了设备号、模块的owner
等信息。cdev_map
的初始化操作通过chrdev_init()
进行,而该函数又直接调用了kobj_map_init()
:
struct kobj_map *kobj_map_init(kobj_probe_t *base_probe, struct mutex *lock)
{
struct kobj_map *p = kmalloc(sizeof(struct kobj_map), GFP_KERNEL);
struct probe *base = kzalloc(sizeof(*base), GFP_KERNEL);
int i;
if ((p == NULL) || (base == NULL)) {
kfree(p);
kfree(base);
return NULL;
}
base->dev = 1;
base->range = ~0;
base->get = base_probe;
for (i = 0; i < 255; i++)
p->probes[i] = base;
p->lock = lock;
return p;
}
初始化过程非常简单,除了分配内存,还初始化了一个probe
结构的base
,并将cdev_map
中probes
所有元素指向这个base
,所以初始化后的cdev_map
结构如下图:
kobj_map()
分析完cdev_map
的初始化,继续回到kobj_map()
函数;前面说到cdev_map
被作为参数传递给kobj_map()
函数,接下来分析一下kobj_map()
的实现,注释后的代码如下:
int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,
struct module *module, kobj_probe_t *probe,
int (*lock)(dev_t, void *), void *data)
{
unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1; /* 设备号范围跨了多少个主设备号 */
unsigned index = MAJOR(dev); /* 该设备的起始主设备号 */
unsigned i;
struct probe *p;
if (n > 255) /* 虽然主设备号为12位,但是此处限制设备号范围最多跨255个主设备号 */
n = 255;
p = kmalloc_array(n, sizeof(struct probe), GFP_KERNEL); /* 每个主设备号一个probe结构 */
if (p == NULL)
return -ENOMEM;
/* 逐个初始化probe结构 */
for (i = 0; i < n; i++, p++) {
p->owner = module;
p->get = probe;
p->lock = lock;
p->dev = dev;
p->range = range;
p->data = data; /* 传入的data实际上是cdev结构的实例 */
}
mutex_lock(domain->lock);
for (i = 0, p -= n; i < n; i++, p++, index++) {
struct probe **s = &domain->probes[index % 255];
while (*s && (*s)->range < range)
s = &(*s)->next;
p->next = *s;
*s = p;
}
mutex_unlock(domain->lock);
return 0;
}
mutex_lock()
之前的部分比较好理解,就是根据传入的参数,创建并设置了probe
结构;mutex_lock()
和mutex_unlock()
之间的部分需要单独分析:
……
for (i = 0, p -= n; i < n; i++, p++, index++) {
struct probe **s = &domain->probes[index % 255];
while (*s && (*s)->range < range)
s = &(*s)->next;
p->next = *s;
*s = p;
}
……
在这个for
循环中,index
表示设备的主设备号,p
表示一个probe
的实例,这段代码主要作用就是将kobj_map
结构的probes
数组中的元素指向前面创建的probe
实例。
循环中第一行,创建一个struct probe
的指针指向kobj_map->probes
中的某个位置,这个位置并不直接指向第index
个位置,而是通过index%255
计算得到,因为index
是12位的主设备号,取值范围大于255,取模操作可以保证索引不会溢出。
接下来的while
循环条件比较复杂,暂时先忽略while
循环的内容;for
循环中最后两句则是将probe[index%255]
这个位置指向p
,p->next
指向原来的值,最终cdev_map
会得到这样的结构:
接着再看刚刚忽略的while
循环;简单来说,这里的while
循环作用就是将一条probe
链上的probe
实例按照range
的值从小到大排序。
在kobj_map_init()
中这样一条语句,将base
的range
设置为unsigned long
的最大值:
base->range = ~0
这里我们以probes
数组的第0个元素为例,此时probes[0]
结构如下图:
假设此时插入一个range
为1
的probe
实例到probes[0]
,此时(*s)->range = MAX > range = 1
,所以结构会变为这样:
这种情况下,再插入一个range
为2的probe
实例,这时(*s)->range = 1 < range = 2
,会进入到while
循环中,执行s = &(*s)->next
:
执行完后,s
将指向probe_0
的next
域,而(*s)
则指向base
,此时(*s)->range = MAX > range = 2
,离开while
循环,最终得到如下结构:
所以最终得到的cdev_map
中,每条probe
链都是按range
从小到大排序的,并且每条链的末尾都指向初始化时创建的base
。
字符设备的访问
前面分析完了cdev_map
的初始化流程,到目前为止,cdev
结构已经添加到cdev_map
中,但是从用户空间访问设备节点时,如何找到对应的file_operations
函数呢?
kobj_lookup()
drivers/base/map.c
中还有一个重要的函数kobj_lookup
,先来从这个函数进行分析:
struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index);
从函数定义来看,这个函数的作用是根据设备号,从kobj_map
中找到对应的probe
,并从中返回对应driver
的kobject
;看一下这个函数的实现部分:
……
for (p = domain->probes[MAJOR(dev) % 255]; p; p = p->next) {
struct kobject *(*probe)(dev_t, int *, void *);
struct module *owner;
void *data;
/* 设备号与当前probe不匹配,继续寻找 */
if (p->dev > dev || p->dev + p->range - 1 < dev)
continue;
if (p->range - 1 >= best) /* 达到链表尾,退出循环 */
break;
if (!try_module_get(p->owner))
continue;
owner = p->owner;
data = p->data;
probe = p->get;
best = p->range - 1;
*index = dev - p->dev;
if (p->lock && p->lock(dev, data) < 0) {
module_put(owner);
continue;
}
mutex_unlock(domain->lock);
kobj = probe(dev, index, data); /* 使用kobj_map()时注册的probe函数获取kobj */
/* Currently ->owner protects _only_ ->probe() itself. */
module_put(owner);
if (kobj)
return kobj;
goto retry;
}
……
kobj_lookup()
根据主设备号从kobj_map->probes
中找到对应的链表进行遍历,并通过计算probe
中设备号的范围来匹配正确的probe
结构;而kobj_map()
时注册的probe()
函数则用来从data
中获取kobject
;对应到cdev_map
上,data
指向的是cdev
结构,而probe()
函数指针指向exact_match()
函数:
static struct kobject *exact_match(dev_t dev, int *part, void *data)
{
struct cdev *p = data;
return &p->kobj;
}
exact_match()
的作用就是从data
中获取kobject
。
既然获取到了kobject
,那就可以使用container_of()
获取对应的cdev
结构,所以char_dev.c
中一定有对应的函数会调用kobj_lookup()
。
chrdev_open()
经过搜索,在char_dev.c
中找到了函数chrdev_open()
调用kobj_lookup()
:
/*
* Called every time a character special file is opened
*/
static int chrdev_open(struct inode *inode, struct file *filp)
从注释来看,这个函数在用户空间每次访问字符设备时调用;来看一下哪里会注册这个函数:
const struct file_operations def_chr_fops = {
.open = chrdev_open,
.llseek = noop_llseek,
};
chrdev_open()
函数注册在def_chr_fops
结构体中,继续搜索一下这个结构体会被赋值给谁:
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
inode->i_mode = mode;
if (S_ISCHR(mode)) {
inode->i_fop = &def_chr_fops;
inode->i_rdev = rdev;
}
……
}
在fs/inode.c
中的函数init_special_inode()
里,def_chr_fops
被赋值给了字符设备的inode
结构体,这样在访问字符设备时,就会通过其inode
访问到def_chr_fops->chrdev_open()
,但是目前为止还没有调用我们自己为设备注册的file_operations
,回过头来继续看chrdev_open()
的实现:
static int chrdev_open(struct inode *inode, struct file *filp)
{
const struct file_operations *fops;
struct cdev *p;
struct cdev *new = NULL;
int ret = 0;
spin_lock(&cdev_lock);
p = inode->i_cdev;
if (!p) {
……
kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
……
new = container_of(kobj, struct cdev, kobj);
spin_lock(&cdev_lock);
……
if (!p) {
inode->i_cdev = p = new;
list_add(&inode->i_devices, &p->list);
new = NULL;
} else if (!cdev_get(p))
ret = -ENXIO;
} else if (!cdev_get(p))
ret = -ENXIO;
……
fops = fops_get(p->ops);
……
replace_fops(filp, fops);
if (filp->f_op->open) {
ret = filp->f_op->open(inode, filp);
……
}
return 0;
……
}
函数实现经过精简后,非常容易看出实现过程:第一次访问时,inode->i_cdev
为空,使用kobj_lookup()
结合container_of()
获取cdev
,并将cdev
赋值给inode->i_cdev
,然后使用fops_get()
获取我们设置的fops
,再使用replace_fops()
将我们的fops
设置到filp
文件指针上,然后调用filp->f_op->open()
,至此成功访问到我们自己的open
函数;当再次访问该字符设备时,inode->i_cdev
已经被赋值,无需再次通过kobj_lookup()
查找对应的cdev
结构。