《LDD3》这本书中“字符设备驱动程序”一章有这样一段话:

只要cdev_add返回了,我们的设备就“活”了,它的操作就会被内核调用。

这里就研究一下cdev_add究竟如何让设备“活”过来,以及用户空间访问字符设备节点时,内核的处理流程。

从cdev_add开始分析

先从cdev_add()入手:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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中:

1
2
3
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的结构定义:

1
2
3
4
5
6
7
8
9
10
11
12
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():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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_mapprobes所有元素指向这个base,所以初始化后的cdev_map结构如下图:

image.png

kobj_map()

分析完cdev_map的初始化,继续回到kobj_map()函数;前面说到cdev_map被作为参数传递给kobj_map()函数,接下来分析一下kobj_map()的实现,注释后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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()之间的部分需要单独分析:

1
2
3
4
5
6
7
8
9
……
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]这个位置指向pp->next指向原来的值,最终cdev_map会得到这样的结构:

image.png

接着再看刚刚忽略的while循环;简单来说,这里的while循环作用就是将一条probe链上的probe实例按照range的值从小到大排序。

kobj_map_init()中这样一条语句,将baserange设置为unsigned long的最大值:

1
base->range = ~0

这里我们以probes数组的第0个元素为例,此时probes[0]结构如下图:

image.png

假设此时插入一个range1probe实例到probes[0],此时(*s)->range = MAX > range = 1,所以结构会变为这样:

image.png

这种情况下,再插入一个range为2的probe实例,这时(*s)->range = 1 < range = 2,会进入到while循环中,执行s = &(*s)->next

image.png

执行完后,s将指向probe_0next域,而(*s)则指向base,此时(*s)->range = MAX > range = 2,离开while循环,最终得到如下结构:

image.png

所以最终得到的cdev_map中,每条probe链都是按range从小到大排序的,并且每条链的末尾都指向初始化时创建的base

字符设备的访问

前面分析完了cdev_map的初始化流程,到目前为止,cdev结构已经添加到cdev_map中,但是从用户空间访问设备节点时,如何找到对应的file_operations函数呢?

kobj_lookup()

drivers/base/map.c中还有一个重要的函数kobj_lookup,先来从这个函数进行分析:

1
struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index);

从函数定义来看,这个函数的作用是根据设备号,从kobj_map中找到对应的probe,并从中返回对应driverkobject;看一下这个函数的实现部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
……
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()函数:

1
2
3
4
5
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()

1
2
3
4
/*
* Called every time a character special file is opened
*/
static int chrdev_open(struct inode *inode, struct file *filp)

从注释来看,这个函数在用户空间每次访问字符设备时调用;来看一下哪里会注册这个函数:

1
2
3
4
const struct file_operations def_chr_fops = {
.open = chrdev_open,
.llseek = noop_llseek,
};

chrdev_open()函数注册在def_chr_fops结构体中,继续搜索一下这个结构体会被赋值给谁:

1
2
3
4
5
6
7
8
9
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()的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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结构。