chrdev_open --- 通过文件路径,执行驱动设置的open()流程

发布时间 2023-04-01 16:36:27作者: 流水灯

open一个字符设备的流程大概是:文件路径 => inode => chrdev_open() => (kobj_lookup=>) inode.i_cdev => cdev.fops.my_chr_open()。所以只要通过VFS找到了inode,就可以找到chrdev_open(),这里我们就来关注一个chrdev_open()是怎么从内核的数据结构中找到我们的cdev并执行其中的my_chr_open()的。虽然我们有了字符设备的设备文件,inode也被构造并初始化了, 但是在第一次调用chrdev_open()之前,这个inode和具体的chr_dev对象并没有直接关系,而只是通过设备号建立的"间接"关系。在第一次调用chrdev_open()之后, inode->i_cdev才根据设备号找到的cdev对象被赋值,此后inode才和具体的cdev对象直接联系在了一起。

 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);
15         if (!kobj)
16             return -ENXIO;
17         new = container_of(kobj, struct cdev, kobj);
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);
41     if (filp->f_op->open) {
42         ret = filp->f_op->open(inode, filp);
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 }

--  9-->将inode->i_cdev(一个cdev结构指针)保存在局部变量p中,
--10-->如果p为空,即inode->i_cdev为空,
--14-->根据inode->i_rdev(设备号)通过kobj_lookup()搜索cdev_map,并返回与之对应kobj
--23-->由于kobject是cdev的父类,我们根据container_of很容易找到相应的cdev结构并将其保存在inode->i_cdev中,
--24-->将inode->i_devices挂接到inode->i_cdev的管理链表中
--40-->找到了cdev结构,其中的操作方法集inode->i_cdev->ops传递给filp->f_ops
--42-->回调我们的设备打开函数my_chr_open(),如果我们没有实现自己的open接口,就什么都不做,也不是错

 1 void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
 2 {
 3     inode->i_mode = mode;
 4     if (S_ISCHR(mode)) {
 5         inode->i_fop = &def_chr_fops;
 6         inode->i_rdev = rdev;
 7     } else if (S_ISBLK(mode)) {
 8         inode->i_fop = &def_blk_fops;
 9         inode->i_rdev = rdev;
10     } else if (S_ISFIFO(mode))
11         inode->i_fop = &pipefifo_fops;
12     else if (S_ISSOCK(mode))
13         ;    /* leave it no_open_fops */
14     else
15         printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for"
16                   " inode %s:%lu\n", mode, inode->i_sb->s_id,
17                   inode->i_ino);
18 }

 

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

 Linux中几乎所有的"设备"都是"device"的子类,无论是平台设备还是i2c设备还是网络设备,但唯独字符设备不是,注册一个cdev对象到内核其实只是将它放到cdev_map中,当下cdev更合适的一种理解是一种接口,而不是而一个具体的设备,和platform_device,i2c_device有着本质的区别。