Linux设备文件三大结构:inode,file,file_operations

驱动程序就是向下控制硬件,向上提供接口,这里的向上提供的接口最终对应到应用层有三种方式:设备文件,/proc,/sys,其中最常用的就是使用设备文件,而Linux设备中用的最多的就是字符设备,本文就以字符设备为例来分析创建并打开一个字符设备的文件内部机制。

struct inode

Linux中一切皆文件,当我们在Linux中创建一个文件时,就会在相应的文件系统创建一个inode与之对应,文件实体和文件的inode是一一对应的,创建好一个inode会存在存储器中,第一次open就会将inode在内存中有一个备份,同一个文件被多次打开并不会产生多个inode,当所有被打开的文件都被close之后,inode在内存中的实例才会被释放。既然如此,当我们使用mknod(或其他方法)创建一个设备文件时,也会在文件系统中创建一个inode,这个inode和其他的inode一样,用来存储关于这个文件的静态信息(不变的信息),包括这个设备文件对应的设备号,文件的路径以及对应的驱动对象etc。inode作为VFS四大对象之一,在驱动开发中很少需要自己进行填充,更多的是在open()方法中进行查看并根据需要填充我们的file结构。
对于不同的文件类型,inode被填充的成员内容也会有所不同,以创建字符设备为例,我们知道,add_chrdev_region其实是把一个驱动对象和一个(一组)设备号联系到一起。而创建设备文件,其实是把设备文件设备号联系到一起。至此,这三者就被绑定在一起了。这样,内核就有能力创建一个struct inode实例了,下面是4.8.5内核中的inode。这个inode是VFS的inode,是最具体文件系统的inode的进一步封装,也是驱动开发中关心的inode,针对具体的文件系统,还有struct ext2_inode_info 等结构。

//include/linux/fs.h
 596 /*
 597  * Keep mostly read-only and often accessed (especially for
 598  * the RCU path lookup and 'stat' data) fields at the beginning
 599  * of the 'struct inode'
 600  */
 601 struct inode {
 602         umode_t                 i_mode;
 604         kuid_t                  i_uid;
 605         kgid_t                  i_gid;
 606         unsigned int            i_flags;
 630         union {
 631                 const unsigned int i_nlink;
 632                 unsigned int __i_nlink;
 633         };
 634         dev_t                   i_rdev;
 635         loff_t                  i_size;
 636         struct timespec         i_atime;
 637         struct timespec         i_mtime;
 638         struct timespec         i_ctime;
 668         union {
 669                 struct hlist_head       i_dentry;
 670                 struct rcu_head         i_rcu;
 671         };
 672         u64                     i_version;
 673         atomic_t                i_count;
 674         atomic_t                i_dio_count;
 675         atomic_t                i_writecount;
 679         const struct file_operations    *i_fop; /* former ->i_op->default_file_ops */
 681         struct address_space    i_data;
 682         struct list_head        i_devices;
 683         union {
 684                 struct pipe_inode_info  *i_pipe;
 685                 struct block_device     *i_bdev;
 686                 struct cdev             *i_cdev;
 687                 char                    *i_link;
 688                 unsigned                i_dir_seq;
 689         };
 702         void                    *i_private; /* fs or device private pointer */
 703 };    

这里面与本文相关的成员主要有:

struct inode
--602-->i_mode表示访问权限控制
--604-->UID
--605-->GID
--606-->i_flags文件系统标志
--630-->硬链接数计数
--635-->i_size以字节为单位的文件大小
--636-->最后access时间
--637-->最后modify时间
--638-->最后change时间
--669-->i_dentry; //目录项链表
--673-->i_count引用计数,当引用计数变为0时,会释放inode实例
--675-->i_writecount写者计数
--679-->创建设备文件的时候i_fops填充的是def_chr_fops,blk_blk_fops,def_fifo_fops,bad_sock_fops之一,参见创建过程中调用的init_special_inode()
--683-->特殊文件类型的union,pipe,cdev,blk.link etc,i_cdev表示这个inode属于一个字符设备文件,本文中创建设备文件的时候会把与之相关的设备号的驱动对象cdev拿来填充
--702-->inode的私有数据

针对我们的问题,上面的几个成员只有struct def_chr_fops 值得一追,后面有大用:

//fs/char_dev.c
429 const struct file_operations def_chr_fops = { 
430         .open = chrdev_open,
431         .llseek = noop_llseek,
432 };

struct file

Linux内核会为每一个进程维护一个文件描述符表,这个表其实就是struct file[]的索引。open()的过程其实就是根据传入的路径填充好一个file结构并将其赋值到数组中并返回其索引。下面是file的主要内容

//include/linux/fs.h
 877 struct file {
 878         union {
 879                 struct llist_node       fu_llist;
 880                 struct rcu_head         fu_rcuhead;
 881         } f_u;
 882         struct path             f_path;
 883         struct inode            *f_inode;       /* cached value */
 884         const struct file_operations    *f_op;
 885 
 886         /*                                            
 887          * Protects f_ep_links, f_flags.
 888          * Must not be taken from IRQ context.
 889          */
 890         spinlock_t              f_lock;
 891         atomic_long_t           f_count;
 892         unsigned int            f_flags;
 893         fmode_t                 f_mode;
 894         struct mutex            f_pos_lock;
 895         loff_t                  f_pos;
 896         struct fown_struct      f_owner;
 897         const struct cred       *f_cred;
 898         struct file_ra_state    f_ra;f
 904         /* needed for tty driver, and maybe others */
 905         void                    *private_data;
 912         struct address_space    *f_mapping;
 913 } __attribute__((aligned(4)));  /* lest something weird decides that 2 is OK */

struct file
--882-->f_path里存储的是open传入的路径,VFS就是根据这个路径逐层找到相应的inode
--883-->f_inode里存储的是找到的inode
--884-->f_op里存储的就是驱动提供的file_operations对象,这个对象应该在第一次open()的时候被填充,具体地,应用层的open通过层层搜索会调用inode.i_fops->open(),即chrdev_open()
--891-->f_count的作用是记录对文件对象的引用计数,也即当前有多少个使用CLONE_FILES标志克隆的进程在使用该文件。典型的应用是在POSIX线程中。就像在内核中普通的引用计数模块一样,最后一个进程调用put_files_struct()来释放文件描述符。
--892-->f_flags当打开文件时指定的标志,对应系统调用open的int flags,比如驱动程序为了支持非阻塞型操作需要检查这个标志是否有O_NONBLOCK。
--893-->f_mode;对文件的读写模式,对应系统调用open的mod_t mode参数,比如O_RDWR。如果驱动程序需要这个值,可以直接读取这个字段。
--905-->private_data表示file结构的私有数据

我在Linux设备管理(二)_从cdev_add说起一文中已经分析过chrdev_open(),这里仅作概述。

//fs/chr_dev.c
348 /*
349  * Called every time a character special file is opened
350  */
351 static int chrdev_open(struct inode *inode, struct file *filp)
352 {
            /* 搜索cdev */
            ...
390         replace_fops(filp, fops);
391         if (filp->f_op->open) {
392                 ret = filp->f_op->open(inode, filp);
393                 if (ret)
394                         goto out_cdev_put;
395         } 
            ...
402 }

可以看出,这个函数有三个任务(划重点!!!):

chrdev_open()
--352-389-->利用container_of等根据inode中的成员找到相应的cdev
--390-->用cdev.fops替换filp->f_op,即填充了一个空的struct file的f_op成员。
--392-->回调替换之后的filp->f_op->open,由于替换,这个其实就是cdev.fops

至此,我们知道了我们写的驱动中的open()在何时会被回调,这样我们就可以实现很多有意思的功能,比如,
我们可以在open中通过inode->cdev来识别具体的设备,并将其私有数据隐藏到file结构的private_data中,进而识别同一个驱动操作一类设备;
我们也可以在回调cdev.fops->open()阶段重新填充file结构的fop,进而实现同一个驱动操作不同的设备,这种思想就是内核驱动中常用的分层!
最后总结一下这些结构之间的关系:

posted @ 2017-02-04 13:05  Abnor  阅读(11486)  评论(2编辑  收藏  举报