Linux字符设备驱动

时间:2021-12-01 17:55:14

本文详细介绍字符设备驱动,使用linux-4.8.2版本代码。

1.综述:从注册到open、read/write

  1. 申请设备号;
  2. 注册cdev到cdev_map:cdev_init和cdev_add;
  3. 创建设备节点:mknod(手动)或者udev(自动,借助热插拔和sysfs,推荐);
  4. 用户空间通过路径open到内核空间对应的设备节点,内核空间中(VFS、filp和cdev_map):chrdev_open根据设备节点中的设备号(此时该节点的cdev还是NULL),找到cdev实例,并赋值到设备节点的成员cdev变量上,最后把cdev的ops赋值到filp->f_op,并调用filp->f_op->open函数;
  5. 用户空间通过fd,read到内核空间,sys_read调用vfs_read最终调用 file->f_op->read;

  备注:

      • 由3创建出来的设备节点是不完全初始化的设备节点,至少包含设备号,不包含cdev实例;
      • 在5处,根据fd而不是文件路径,找到struct file而不是struct inode去获取open已经挂接好的cdev的read;

2.sys_open代码

  [todo]

3.chrdev_open代码

 

 1 static int chrdev_open(struct inode *inode, struct file *filp)
2 {
3 const struct file_operations *fops;
4 struct cdev *p;
5 struct cdev *new = NULL;
6 int ret = 0;
7
8 spin_lock(&cdev_lock);
9 p = inode->i_cdev;
10 if (!p) {//第一次打开时进入
11 struct kobject *kobj;
12 int idx;
13 spin_unlock(&cdev_lock);
14 kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);//在cdev_map中找到kobj
15 if (!kobj)
16 return -ENXIO;
17 new = container_of(kobj, struct cdev, kobj);//找到cdev
18 spin_lock(&cdev_lock);
19 /* Check i_cdev again in case somebody beat us to it while
20 we dropped the lock. */
21 p = inode->i_cdev;
22 if (!p) {
23 inode->i_cdev = p = new;
24 list_add(&inode->i_devices, &p->list);
25 new = NULL;
26 } else if (!cdev_get(p))
27 ret = -ENXIO;
28 } else if (!cdev_get(p))
29 ret = -ENXIO;
30 spin_unlock(&cdev_lock);
31 cdev_put(new);
32 if (ret)
33 return ret;
34
35 ret = -ENXIO;
36 fops = fops_get(p->ops);
37 if (!fops)
38 goto out_cdev_put;
39
40 replace_fops(filp, fops);//绑定驱动的fops到filp上
41 if (filp->f_op->open) {
42 ret = filp->f_op->open(inode, filp);//调用驱动的open
43 if (ret)
44 goto out_cdev_put;
45 }
46
47 return 0;
48
49 out_cdev_put:
50 cdev_put(p);
51 return ret;
52 }

 

4.sys_read代码

Linux字符设备驱动

 

5.vfs_read代码

  [todo]

6.cdev_add调用kobj_map

字符设备结构体cdev的添加步骤:
cdev初始化:cdev_init,该函数将file_operations与cdev对应起来;
向kernel添加:cdev_add,该函数将主设备号与cdev结构体对应起来,由此主设备号对应驱动程序;(设备节点对应的是文件两码事,ALSA字符设备)
当对open设备节点时,首先通过节点找到主设备号,然后再kernel中搜索与主设备号相对应的字符设备cdev,然后动过cdev中file_operations结构体定义的open方法(这个open是需要自己实现的);

 1 int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,
2 struct module *module, kobj_probe_t *probe,
3 int (*lock)(dev_t, void *), void *data)
4 {
5 unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
6 unsigned index = MAJOR(dev);//取得主设备号
7 unsigned i;
8 struct probe *p;
9
10 if (n > 255)
11 n = 255;
12
13 p = kmalloc_array(n, sizeof(struct probe), GFP_KERNEL);
14 if (p == NULL)
15 return -ENOMEM;
16
17 for (i = 0; i < n; i++, p++) {
18 p->owner = module;
19 p->get = probe;
20 p->lock = lock;
21 p->dev = dev;
22 p->range = range;
23 p->data = data;
24 }
25 mutex_lock(domain->lock);
26 for (i = 0, p -= n; i < n; i++, p++, index++) {
27 struct probe **s = &domain->probes[index % 255];
28 while (*s && (*s)->range < range)
29 s = &(*s)->next;
30 p->next = *s;
31 *s = p;
32 }
33 mutex_unlock(domain->lock);
34 return 0;
35 }

 

参考:

  1.Multiconflictism的《Linux设备管理(二)_从cdev_add说起》,http://www.cnblogs.com/xiaojiang1025/p/6196198.html

  2.cuijiyue的《主设备号--驱动模块与设备节点联系的纽带》http://blog.csdn.net/cuijiyue/article/details/42066425

2017-06-08