Linux字符设备驱动
本文详细介绍字符设备驱动,使用linux-4.8.2版本代码。
1.综述:从注册到open、read/write
- 申请设备号;
- 注册cdev到cdev_map:cdev_init和cdev_add;
- 创建设备节点:mknod(手动)或者udev(自动,借助热插拔和sysfs,推荐);
- 用户空间通过路径open到内核空间对应的设备节点,内核空间中(VFS、filp和cdev_map):chrdev_open根据设备节点中的设备号(此时该节点的cdev还是NULL),找到cdev实例,并赋值到设备节点的成员cdev变量上,最后把cdev的ops赋值到filp->f_op,并调用filp->f_op->open函数;
- 用户空间通过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 = 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