chrdev_open --- 通过文件路径,执行驱动设置的open()流程
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有着本质的区别。