驱动程序就是向下控制硬件,向上提供接口,这里的向上提供的接口最终对应到应用层有三种方式:设备文件,/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,进而实现同一个驱动操作不同的设备,这种思想就是内核驱动中常用的分层!

最后总结一下这些结构之间的关系:

Linux设备文件三大结构:inode,file,file_operations的更多相关文章

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

    驱动程序就是向下控制硬件,向上提供接口,这里的向上提供的接口最终对应到应用层有三种方式:设备文件,/proc,/sys,其中最常用的就是使用设备文件,而Linux设备中用的最多的就是字符设备,本文就以 ...

  2. (转载)使用 udev 高效、动态地管理 Linux 设备文件

    概述: Linux 用户常常会很难鉴别同一类型的设备名,比如 eth0, eth1, sda, sdb 等等.通过观察这些设备的内核设备名称,用户通常能知道这些是什么类型的设备,但是不知道哪一个设备是 ...

  3. 嵌入式 使用udev高效、动态地管理Linux 设备文件

    本文以通俗的方法阐述 udev 及相关术语的概念.udev 的配置文件和规则文件,然后以 Red Hat Enterprise Server 为平台演示一些管理设备文件和查询设备信息的实例.本文会使那 ...

  4. 【转】使用 udev 高效、动态地管理 Linux 设备文件

    简介: 本文以通俗的方法阐述 udev 及相关术语的概念.udev 的配置文件和规则文件,然后以 Red Hat Enterprise Server 为平台演示一些管理设备文件和查询设备信息的实例.本 ...

  5. 使用 udev 高效、动态地管理 Linux 设备文件

    本文转自:https://www.ibm.com/developerworks/cn/linux/l-cn-udev/index.html 概述: Linux 用户常常会很难鉴别同一类型的设备名,比如 ...

  6. 使用 udev 管理 Linux 设备文件

    本文以通俗的方法阐述 udev 及相关术语的概念.udev 的配置文件和规则文件,然后以 Red Hat Enterprise Server 为平台演示一些管理设备文件和查询设备信息的实例.本文会使那 ...

  7. Linux设备文件自动生成

    第一种是使用mknod手工创建:# mknod <devfilename> <devtype> <major> <minor> 第二种是自动创建设备节点 ...

  8. 文件存储结构inode与RAM结构建立联系

    linux下一切皆文件,大致可分为以下几类:目录.普通文件.硬连接.软连接.字符设备.块设备.FIFO.Socket,其在物理存储体内存储按inode和数据块存储,inode代表元数据,是除实际数据外 ...

  9. 【Linux】文件存储结构

    大部分的Linux文件系统(如ext2.ext3)规定,一个文件由目录项.inode和数据块组成: 目录项:包括文件名和inode节点号.  Inode:又称文件索引节点,包含文件的基础信息以及数据块 ...

随机推荐

  1. [转]delphi 有授权许可的字符串拷贝函数源码

    一段看上去“貌不惊人”的Delphi插入汇编代码,却需要授权许可,但是与经典的同类型函数比较,确实“身手不凡”. 研究代码的目的在于借鉴,本文通过分析,并用C++重写代码进行比较,再次证明这段代码效率 ...

  2. mysql 时间类型精确到毫秒、微秒及其处理

    一.MySQL 获得毫秒.微秒及对毫秒.微秒的处理 MySQL 较新的版本中(MySQL 6.0.5),也还没有产生微秒的函数,now() 只能精确到秒. MySQL 中也没有存储带有毫秒.微秒的日期 ...

  3. YUV 4:2:0 格式和YUV411格式区别

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/coloriy/article/details/6668447 MPEG 储存的 YU(Cb)V(Cr ...

  4. 如何在Angular优雅编写HTTP请求

    原文:https://segmentfault.com/a/1190000010570799 ----------------------------------------------------- ...

  5. 使用AOF持久化文件实现还原Redis数据库并得到RDB持久化文件

    目录 1 编写本文的初衷 2 具体实施 2.1 Redis持久化概念简介 2.2 获取指定Redis的AOF持久化文件 2.3 把Redis的持久化AOF文件转换为RDB文件 1 编写本文的初衷 因为 ...

  6. web,xml中关于filter的使用

    从J2EE1.3开始,Servlet2.3规范中加入了对过滤器的支持.过滤器能够对目标资源的请求和响应进行截取.过滤器的工作方式分为四种,下面让我们分别来看看这四种过滤器的工作方式:1.request ...

  7. ANTLR flex/bison

    https://stackoverflow.com/questions/29971097/how-to-create-ast-with-antlr4 这个很值得仔细看 https://github.c ...

  8. mongodb用户管理和服务安装

    一.忘记密码快速找回 任何系统忘记密码都是一样的操作:以无需授权的模式开启程序,然后进入系统修改权限设置,退出来重新以授权方式开启程序.MySQL是这样,MongoDB也是这样.好的系统都提供了无授权 ...

  9. Ubuntu 安装nginx

    https://www.nginx.com/resources/admin-guide/load-balancer/ https://github.com/gplessis/dotdeb-nginx/ ...

  10. Struct2 (一)

    STRUCT2 权威指南(1) MVC思想将一个应用分成三个基本部分:Model(模型).View(视图)和Controller(控制器),这三个部分以最少的耦合协同工作,从而提高应用的可扩展性及可维 ...