首页 > 代码库 > Linux字符设备驱动

Linux字符设备驱动

本文详细介绍字符设备驱动,使用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代码

技术分享

 

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 =http://www.mamicode.com/ 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

Linux字符设备驱动