标签:
目录
1. Linux文件系统简介 2. 通用文件模型 3. VFS相关数据结构 4. 处理VFS对象 5. 标准函数
1. Linux文件系统简介
Linux系统由数以万计的文件组成,其数据存储在硬盘或者其他块设备(例如ZIP驱动、软驱、光盘等)。存储使用了层次式文件系统,文件系统使用目录结构组织存储的数据,并将其他元信息(例如所有者、访问权限等)与实际数据关联起来
Linux支持许多不同的文件系统
1. Ext2 2. Ext3 3. ReiserFS 4. XFS 5. VFAT(兼容DOS) ..
不同文件系统锁基于的概念抽象差别很大,例如Ext2基于"Inode",它对每个文件哦度构造了一个单独的管理结构即"Inode",这些元信息也存储到磁盘上。inode包含了文件所有的元信息,以及指向相关数据块的指针,目录可以表示为普通文件,其数据包括了指向目录下所有文件的inode指针,因而层次结构得以建立。相比之下,ReiserFS广泛应用了树形结构来提供同样的层次功能
内核提供了一个额外的软件抽象层,将各种底层文件系统的具体特性与应用层包括内核自身隔离开来,该软件层称为VFS(Virtual Filesystem/Virtual Filesystem Switch 虚拟文件系统/虚拟文件系统交换器)。VFS既是向下的接口(所有文件系统都必须实现该接口,以此来和内核通信),同时也是向上的接口(用户进程通过系统调用访问的对外接口 实现系统调用功能)
为支持各种本机文件系统,且在同时允许访问其他操作系统的文件,Linux内核在用户进程(或C标准库)和文件系统实现之间引入了一个抽象层,该抽象层称之为虚拟文件系统(Virtual File System VFS)
VFS的任务很复杂,一方面,它用来提供一种操作文件、目录及其他对象的统一方法。另一方面,它必须能够与各种方法给出的具体文件系统的实现进行兼容,这导致了VFS在实现上的复杂性。但VFS带来的好处是使得Linux内核更加灵活了
Linux内核支持很多种文件系统
1. ext2 是Linux使用的,性能很好的文件系统,用于固定文件系统和可活动文件系统。它是作为ext文件系统的扩展而设计的。ext2在Linux所支持的文件系统中,提供最好的性能(在速度和CPU使用方面),ext2是Linux目前的主要文件系统 2. ext3文件系统 是对ext2增加日志功能后的扩展。它向前、向后兼容ext2。意为ext2不用丢失数据和格式化就可以转换为ext3,ext3也可以转换为ext2而不用丢失数据(只要重新安装该分区就行了) 3. proc 是一种假的文件系统,用于和内核数据结构接口,它不占用磁盘空间。参考 man proc 4. Devpts 是一个虚拟的文件系统,一般安装在/dev/pts。为了得到一个虚拟终端,进程打开/dev/ptmx,然后就可使用虚拟终端 5. raiserfs 是Linux内核2.4.1以后(2001年1 月)支持的,一种全新的日志文件系统 6. swap文件系统 swap文件系统用于Linux的交换分区。在Linux中,使用整个交换分区来提供虚拟内存,其分区大小一般应是系统物理内存的2倍,在安装Linux 操作系统时,就应创分交换分区,它是Linux正常运行所必需的,其类型必须是swap,交换分区由操作系统自行管理 7. vfat文件系统 vfat是Linux对DOS、Windows系统下的FAT(包括fat16和Fat32)文件系统的一个统称 8. NFS文件系统 NFS即网络文件系统,用于在UNIX系统间通过网络进行文件共享,用户可将网络中NFS服务器提供的共享目录挂载到本地的文件目录中,从而实现操作和访问NFS文件系统中的内容 9. ISO 9660文件系统 文件系统中光盘所使用的标准文件系统,是一种针对ISO9660标准的CD-ROM文件系统,Linux对该文件系统也有很好的支持,不仅能读取光盘和光盘ISO映像文件,而且还支持在Linux环境中刻录光盘
0x1: 文件系统类型
文件系统一般可以分为以下几种
1. 基于磁盘的文件系统(Disk-based Filesystem) 是在非易失介质存储文件的经典方式,用以在多次会话之间保持文件的内容。实际上,大多数文件系统都由此 2. 虚拟文件系统(Virtual Filesystem) 在内核中生成,是一种使用户应用程序与内核通信的方法。proc文件系统就是这一类的最好示例,它不需要任何种类的硬件设备上分配存储空间,而是内核建立了一个层次化的文件结构,其中的项包含了与系统特定部分相关的信息 ll /proc/version /* 占用空间: 0字节 -r--r--r--. 1 root root 0 Feb 27 23:39 /proc/version */ cat /proc/version /* 从内核内存中的数据结构提取出来 Linux version 2.6.32-504.el6.x86_64 (mockbuild@c6b9.bsys.dev.centos.org) (gcc version 4.4.7 20120313 (Red Hat 4.4.7-11) (GCC) ) #1 SMP Wed Oct 15 04:27:16 UTC 2014 */ 3. 网络文件系统(Network Filesystem) 基于磁盘的文件系统和虚拟文件系统之间的折中。这种文件系统允许访问另一台计算机上的数据,该计算机通过网络连接到本地计算机。它仍然需要文件长度、文件在目录层次中的位置、文件的其他重要信息。它也必须提供函数,使得用户进程能够执行通常的文件相关操作,如打开、读、删除等。由于VFS抽象层的存在,用户空间进程不会看到本地文件系统和网络文件系统之间的区别
2. 通用文件模型
VFS不仅为文件系统提供了方法和抽象,还支持文件系统中对象(或文件)的统一视图。由于各个文件系统的底层实现不同,文件在不同的底层文件系统环境下特性存在微秒的差异
1. 并非所有文件系统都支持同样的功能,而有些操作对"普通"文件是不可缺少的,却对某些对象完全没有意义,例如集成到VFS中的命名管道 2. 并非每一种文件系统都支持VFS中的所有抽象,例如设备文件无法存储在源自其他系统的文件系统中(例如FAT),因为FAT的设计没有考虑到设备文件这类对象
VFS的设计思想是提供一种结构模型,包含一个强大文件系统所应具备的所有组件,但该模型只存在于虚拟中,必须使用各种对象和函数指针与每种文件系统适配。所有文件系统的实现都必须提供与VFS定义的结构配合的例程,以弥合两种视图之间的差异
需要明白的是,虚拟文件系统的结构并非是凭空创造出来的,而是基于描述经典文件系统所使用的结构。VFS抽象层的组织和Ext2文件系统类似,这对基于完全不同概念的文件系统(例如ReiserFS、XFS)来说,会更加困难,但处理Ext2文件系统时会提高性能,因为在Ext2和VFS结构之间转换,几乎不会损失时间
在处理文件时,内核空间和用户空间所使用的主要对象是不同的
1. 对用户程序来说 一个文件由一个"文件描述符"标识,文件描述符是一个整数,在所有有关文件的操作中用作标识文件的参数。文件描述符是在打开文件时由内核分配的,只在一个进程内部有效,两个进程可以使用同样的文件描述符,但二者并不指向同一个文件,基于同一个描述符来共享文件是不可能的 2. 对内核来说 内核处理文件的关键是inode,每个文件(目录)都有且只有一个对应的inode,其中包含元数据(如访问权限、上次修改时间、等等)和指向文件数据的指针。但inode并不包含文件名
关于Linux下inode、链接的相关知识,请参阅另一篇文章
http://www.cnblogs.com/LittleHann/p/4208619.html
0x1: 编程接口
用户进程和内核的VFS实现之间由"系统调用"组成,其中大多数涉及对文件、目录和一般意义上的文件系统的操作,和文件操作相关的系统调用请参阅另一篇文章
http://www.cnblogs.com/LittleHann/p/3850653.html //搜索:0x3: 系统调用分类
文件使用之前,必须用open或openat系统调用打开,在成功打开文件之后,内核向用户层返回一个非负的整数,这种分配的文件描述符起始于3(0表示标准输入、1表示标准输出、2表示标准错误输出)。在文件已经打开后,其名称就没有用处了(文件名只是在利用inode进行文件遍历的时候起过滤作用),它现在由文件描述符唯一标识,所有其他库函数都需要传递文件描述符作为一个参数(进一步传递到系统调用)
尽管传统上文件描述符在内核中足以标识一个文件,但是由于多个命名空间(namespace)和容器(container)的引入,在不同层次命名空间中看到的同一个进程中的文件描述符(fd)是不同的,因为对文件的唯一表示由一个特殊的数据结构(struct file)提供
系统调用read需要将文件描述符作为第一个参数,以标识读取数据的来源
在一个打开文件中的当前位置保存在"文件位置指针(f_pos)",这是一个整数,指定了当前位置与文件起始点的偏移量。对随机存取文件而言,该指针可以设置成任何值,只要不超出文件存储容量范围即可,这用于支持对文件数据的随机访问。其他文件类型,如命名管道或字符设备的设备文件,不支持这种做法,它们只能从头至尾顺序读取
系统调用close关闭与文件的"连接"(释放文件描述符,以便在后续打开其他文件时使用)
关于struct file数据结构的相关知识,请参阅另一篇文章
http://www.cnblogs.com/LittleHann/p/3865490.html //搜索:0x1: struct file
0x2: 将文件作为通用接口(万物皆文件)
*nix似乎基于少量审慎选择的范型而建立的,一个非常重要的隐喻贯穿内核的始终(特别是VFS),尤其是在有关输入和输出机制的实现方面
大多数内核导出、用户程序使用的函数都可以通过(VFS)定义的文件接口访问,以下是使用文件作为其主要通信手段的一部分内核子系统
1. 字符和块设备 2. 进程之间的管道 3. 用于所有网络协议的套接字 4. 用户交互式输入和输出的终端
要注意的是,上述的某些对象不一定联系到文件系统中的某个项。例如,管道是通过特殊的系统调用生成,然后由内核中VFS的数据结构中管理,管道并不对应于一个可以用通常的rm、ls等命令访问的真正的文件系统项
3. VFS相关数据结构
0x1: 结构概观
VFS由两个部分组成: 文件、文件系统,这些都需要管理和抽象
1. 文件的表示 inode是内核选择用于表示文件内容和相关元数据的方法,理论上,实现这个概念只需要一个大的数据结构,其中包含了所有必要的数据,但实际上,linux内核将数据结构分散到一些列较小的、布局清晰的结构中 在抽象对底层文件系统的访问时,并未使用固定的函数,而是使用了函数指针。这些函数指针保存在两个结构中,包括了所有相关的函数,因为实际数据是通过具体文件系统的实现操作的,调用接口总是保持不变,但实际的工作是由特定于实现的函数完成的 1) inode操作: "struct inode"->"const struct inode_operations *i_op" 特定于inode操作有关,负责管理结构性的操作,例如创建链接、文件重命名、在目录中生成新文件、删除文件 2) 文件操作: "struct file"->"const struct file_operations *f_op;" 特定于文件的数据内容的操作,它们包含一些常用的操作(如读和写)、设置文件位置指针、创建内存映射等操作 除此之外,还需要其他结构来保存与inode相关的信息,特别重要的是与每个inode关联的数据段,其中存储了文件的内容或目录项表。每个inode还包含了一个指向底层文件系统的超级快对象的指针(struct super_block *i_sb;),用于执行对inode本身的操作(这些操作也是通过函数指针数组实现的) 因为打开的文件总是分配到系统中的一个特定的进程,内核必须在数据结构中存储文件和进程之间的关联,我们知道,task_struct包含了一个成员,保存了所有打开的文件的一个数组 各个文件系统的实现也能在VFS inode中存储自身的数据(不通过VFS层操作) /* http://www.cnblogs.com/LittleHann/p/3865490.html 搜索:0x2: struct inode 搜索:0x1: struct file */ 2. 文件系统和超级块信息 VFS支持的文件系统类型通过一种特殊的内核对象连接进来,该对象提供了一种读取"超级块"的方法,除了文件系统的关键信息(块长度、最大文件长度、..),超级块还包含了读、写、操作inode的函数指针 内核还建立了一个链表,包含所有"活动"(active、或者称为"已装载(mounted)")文件系统的超级块实例 超级块结构的一个重要成员是一个列表,包括相关文件系统中所有修改过的inode(脏inode),根据该列表很容易标识已经修改过的文件和目录,以便将其写回到存储介质,回写必须经过协调,保证在一定程度上最小化开销 1) 因为这是一个非常费时的操作,硬盘、软盘驱动器及其他介质与系统其余组件相比,速度慢了几个数量级 2) 另一方面,如果写回修改数据的间隔时间太长也可能带来严重后果,因为系统崩溃(停电)会导致不能恢复的数据丢失 内核会周期性扫描脏块(dirty inode)的列表,并将修改传输(同步)到底层硬件 /* http://www.cnblogs.com/LittleHann/p/3865490.html 搜索:0x10: struct super_block */
值得注意的,inode和file结构体都包含了file_operations结构的指针,而inode还额外包含inode_operations结构指针
0x2: 特定于进程的信息
文件描述符(fd)用于在一个进程内唯一地标识打开的文件,这使得内核能够在用户进程中的描述符和内核内部使用的结构之间,建立一种关联
struct task_struct { ... /* 文件系统信息,整数成员link_count、total_link_count用于在查找环形链表时防止无限循环 */ int link_count, total_link_count; //用来表示进程与文件系统的联系,包括当前目录和根目录、以及chroot有关的信息 struct fs_struct *fs; //表示进程当前打开的文件 struct files_struct *files; //命名空间 strcut nsproxy *nsproxy; ... }
由于命名空间、容器的引入,从容器(container)角度看似"全局"的每个资源,都由内核包装起来,分别由每个容器进行管理,表现出一种虚拟的分组独立的概念。虚拟文件系统(VFS)同样也受此影响,因为各个容器可能装载点的不同导致不同的目录层次结构(即在不同命名空间中看到的目录结构不同),对应的信息包含在ns_proxy->mnt_namespacez中
关于命名空间的相关知识,请参阅另一篇文章
http://www.cnblogs.com/LittleHann/p/4026781.html //搜索:2. Linux命名空间
0x3: 文件操作
文件不能只存储信息,必须容许操作其中的信息。从用户的角度来看,文件操作由标准库的函数执行。这些函数指示内核执行体统调用(VFS提供的系统调用),然后VFS系统调用执行所需的操作,当然各个文件系统实现的接口可能不同,因而VFS层提供了抽象的操作,以便将通用文件对象与具体文件系统实现的底层机制关联起来
用于抽象文件操作的结构必须尽可能通用,以考虑到各种各样的目标文件。同时,它不能带有过多只适用于特定文件类型的专门操作。尽管如此,仍然必须满足各种文件(普通文件、设备文件、..)的特殊需求,以便充分利用。可见,VFS的数据机构是一个承上启下的关键层
各个file实例都包含一个指向struct file_operation实例的指针,该结构保存了指向所有可能文件操作的函数指针
如果一个对象使用这里给出的结构作为接口,那么并不必实现所有的操作,例如进程间管道只提供了少量的操作,因为剩余的操作根本没有意义,例如无法对管道读取目录内容,因此readdir对于管道文件是不可用的。有两种方法可以指定某个方法不可用
1. 将函数指针设置为NULL 2. 将函数指针指向一个占位函数,该函数直接返回错误值
0x4: VFS命名空间
我们知道,内核提供了实现容器的底层机制,单一的系统可以提供很多容器,但容器中的进程无法感知容器外部的情况,也无法得知所在容器有关的信息,容器彼此完全独立,从VFS的角度来看,这意味着需要针对每个容器分别跟踪装载的文件系统,单一的全局视图是不够的
VFS命名空间是所有已经装载的、构成某个容器目录树的文件系统的集合
通常调用fork、clone建立的进程会继承其父进程的命名空间(即默认情况,新进程和父进程会存在于同一个命名空间中),但可以设置CLONE_NEWNS标志,以建立一个新的VFS命名空间
关于VFS命名空间的相关知识,请参阅另一篇文章
http://www.cnblogs.com/LittleHann/p/3865490.html //搜索:0x3: struct nsproxy
命名空间操作(mount、umount)并不作用于内核的全局数据结构,而是操作当前命名空间的实例,可以通过task_strcut的同名成员访问,改变会影响命名空间的所有成员,因为一个命名空间中的所有进程共享同一个命名空间实例
0x5: 目录项缓存: dentry缓存
我们知道,若干dentry描绘了一个树型的目录结构(dentry树),这就是用户所看到的目录结构,每个dentry指向一个索引节点(inode)结构。然而,这些dentry结构并不是常驻内存的,因为整个目录结构可能会非常大,以致于内存根本装不下。Linux的处理方式为
1. 初始状态下: 系统中只有代表根目录的dentry和它所指向的inode 2. 当要打开一个文件: 文件路径中对应的节点都是不存在的,根目录的dentry无法找到需要的子节点(它现在还没有子节点),这时候就要通过inode->i_op中的lookup方法来寻找需要的inode的子节点,找到以后(此时inode已被载入内存),再创建一个dentry与之关联上
由这一过程可见,其实是先有inode再有dentry。inode本身是存在于文件系统的存储介质上的,而dentry则是在内存中生成的。dentry的存在加速了对inode的查询
每个dentry对象都属于下列几种状态之一
1. 未使用(unused)状态 该dentry对象的引用计数d_count的值为0,但其d_inode指针仍然指向相关的的索引节点。该目录项仍然包含有效的信息,只是当前没有人引用他。这种dentry对象在回收内存时可能会被释放 2. 正在使用(inuse)状态 处于该状态下的dentry对象的引用计数d_count大于0,且其d_inode指向相关的inode对象。这种dentry对象不能被释放 3. 负(negative)状态 与目录项(dentry)相关的inode对象不复存在(相应的磁盘索引节点可能已经被删除),dentry对象的d_inode指针为NULL。但这种dentry对象仍然保存在dcache中,以便后续对同一文件名的查找能够快速完成。这种dentry对象在回收内存时将首先被释放
为了提高目录项对象的处理效率,加速对重复的路径的访问,引入dentry cache(简称dcache),即目录项高速缓。它主要由两个数据结构组成:
1. 哈希链表(dentry_hashtable): 内存中所有活动的dentry实例在保存在一个散列表中,该散列表使用fs/dcache.c中的全局变量dentry_hashtable实现,dcache中的所有dentry对象都通过d_hash指针域链到相应的dentry哈希链表中。d_hash是一种溢出链,用于解决散列碰撞 2. 未使用的dentry对象链表(dentry_unused): 内核中还有另一个dentry链表,表头是全局变量dentry_unused(在fs/dcache.c中初始化) dcache中所有处于unused状态和negative状态的dentry对象都通过其d_lru指针域链入dentry_unused链表(super_block->s_dentry_lru)中。该链表也称为LRU链表 为了保证内存的充分利用,在内存中生成的dentry将在无人使用时被释放。d_count字段记录了dentry的引用计数,引用为0时,dentry将被释放。 这里的释放dentry并不是直接销毁并回收,而是将dentry放入目录项高速缓的LRU链表中(即dentry_unused指向的链表中)。当队列过大,或系统内存紧缺时,最近最少使用的一些dentry才真正被释放
目录项高速缓存dcache是索引节点缓存icache(inode cache)的主控器(master),即dcache中的dentry对象控制着icache中的inode对象的生命期转换。无论何时,只要一个目录项对象存在于dcache中(非negative状态),则相应的inode就将总是存在,因为inode的引用计数i_count总是大于0。当dcache中的一个dentry被释放时,针对相应inode对象的iput()方法就会被调用
当寻找一个文件路径时,对于其中经历的每一个节点,有三种情况:
1. 对应的dentry引用计数尚未减为0,它们还在dentry树中,直接使用即可 2. 如果对应的dentry不在dentry树中,则试图从LRU队列去寻找。LRU队列中的dentry同时被散列到一个散列表中,以便查找。查找到需要的dentry后,这个dentry被从LRU队列中拿出来,重新添加到dentry树中 3. 如果对应的dentry在LRU队列中也找不到,则只好去文件系统的存储介质里面查找inode了。找到以后dentry被创建,并添加以dentry树中
dentry结构不仅使得易于处理文件系统,对提高系统性能也很关键,它们通过最小化与底层文件系统实现的通信,加速了VFS的处理。每个由VFS发送到底层实现的请求,都会导致创建一个新的dentry对象,以保存请求的结果,这些对象保存在一个缓存中,在下一次需要时可以更快速地访问,这样操作就能够更快速地执行
0x6: dentry管理
关于struct dentry结构的相关知识,请参阅另一篇文章
http://www.cnblogs.com/LittleHann/p/3865490.html //搜索:0x7: struct dentry
各个dentry实例组成了一个网络(层次目录网络),与文件系统的结构形成一定的映射关系。在内核中需要获取有关文件的信息时,使用dentry对象很方便,dentry更多地体现了linux目录组织关系,但它不是表示文件及文件内容,这一职责分配给了inode,使用dentry对象很容易找到inode实例
Relevant Link:
http://blog.csdn.net/denzilxu/article/details/9188003 http://www.cnblogs.com/hzl6255/archive/2012/12/31/2840854.html
4. 处理VFS对象
0x1: 文件系统操作
尽管"文件操作"对所有应用程序来说都属于标准功能,但"文件系统操作"只限于少量几个系统程序,即用于装载和卸载文件系统的mount、unmount程序。同时还必须考虑到另一个重要的方面,即文件文件在内核中是以模块化形式实现的,这意味着可以将文件系统编译到内核中,而内核自身在编译时也完全可以限制不支持某个特定的文件系统。因此,每个文件系统在使用以前必须注册到内核,这样内核能够了解可用的文件系统,并按需调用装载功能(mount)
1. 注册文件系统
在文件系统注册到内核时,文件系统是编译为模块(LKM),或者持久编译到内核中,都没有差别。如果不考虑注册的时间(持久编译到内核的文件系统在启动时注册,模块化文件系统在相关模块载入内核时注册),在两种情况下所用的技术是同样的
\linux-2.6.32.63\fs\filesystems.c
/** * register_filesystem - register a new filesystem * @fs: the file system structure * * Adds the file system passed to the list of file systems the kernel * is aware of for mount and other syscalls. Returns 0 on success, * or a negative errno code on an error. * * The &struct file_system_type that is passed is linked into the kernel * structures and must not be freed until the file system has been * unregistered. */ int register_filesystem(struct file_system_type * fs) { int res = 0; struct file_system_type ** p; BUG_ON(strchr(fs->name, ‘.‘)); if (fs->next) return -EBUSY; //所有文件系统都保存在一个单链表中,各个文件系统的名称存储为字符串 INIT_LIST_HEAD(&fs->fs_supers); write_lock(&file_systems_lock); /* 在新的文件系统注册到内核时,将逐元素扫描该单链表 1. 到达链表尾部: 将描述新文件系统的对象置于链表末尾,完成了向内核的注册 2. 找到对应的文件系统: 返回一个适当的错误信息,表明一个文件系统不能被注册两次 */ p = find_filesystem(fs->name, strlen(fs->name)); if (*p) res = -EBUSY; else *p = fs; write_unlock(&file_systems_lock); return res; } EXPORT_SYMBOL(register_filesystem);
关于"struct file_system_type"的相关知识,请参阅另一篇文章
http://www.cnblogs.com/LittleHann/p/3865490.html //搜索:0x11: struct file_system_type
2. 装载和卸载
目录树的装载和卸载比仅仅注册文件系统要复杂得多,因为后者(注册文件系统)只需要向一个链表添加对象,而前者(目录树的装载和卸载)需要对内核的内部数据结构执行很多操作,所以要复杂得多。文件系统的装载由mount系统调用发起,在详细讨论各个步骤之前,我们需要阐明在现存目录树中装载新的文件系统必须执行的任务,我们还需要讨论用于描述装载点的数据结构
vfsmount结构
unix采用了一种单一的文件系统层次结构,新的文件系统可以集成到其中
使用mount指令可查询目录树中各种文件系统的装载情况
在这里例子中,/mnt和/cdrom目录被称为"装载点",因为这是附接(装载)文件系统的位置。每个装载的文件系统都有一个"本地根目录",其中包含了系统目录(例如对于cdrom这个装载点来说,它的系统目录就是src、libs)。在将文件系统装载到一个目录时,装载点的内容被替换为即将装载的文件系统的相对根目录的内容,前一个目录数据消失,直至新文件系统卸载才重新出现(在此期间旧文件系统的数据不会被改变,但是无法访问)
从这个例子中可以看到,装载是可以嵌套的,光盘装载在/mnt/cdrom目录中,这意味着ISO9660文件系统的相对根目录装载在一个reiser文件系统内部,因而与用作全局根目录的ext2文件系统是完全分离的
在内核其他部分常见的父子关系,也可以用于更好地描述两个文件系统之间的关系
1. ext2是/mnt中的reiserfs的父文件系统 2. /mnt/cdrom中包含的是/mnt的子文件系统,与根文件系统ext2
每个装载的文件系统都对应于一个vfsmount结构的实例,关于结构体定义的相关知识,请参阅另一篇文章
http://www.cnblogs.com/LittleHann/p/3865490.html //搜索:0x8: struct vfsmount
超级块管理
在装载新的文件系统时,vfsmount并不是唯一需要在内存中创建的结构,装载操作开始于超级块的读取。file_system_type对象中保存的read_super函数指针返回一个类型为super_block的对象,用于在内存中表示一个超级块,它是借助底层实现产生的
关于struct super_block的相关知识,请参阅另一篇文章
http://www.cnblogs.com/LittleHann/p/3865490.html 搜索:0x10: struct super_block
mount系统调用
mount系统调用的入口点是sys_mount函数,\linux-2.6.32.63\fs\namespace.c
SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name, char __user *, type, unsigned long, flags, void __user *, data) { int ret; char *kernel_type; char *kernel_dir; char *kernel_dev; unsigned long data_page; /*从用户空间复制到系统空间*/ /* 以下几个函数将用户态参数拷贝至内核态,在后面需要使用这些参数,包括: 1. kernel_type: 挂载文件系统类型,如ext3 2. kernel_dir: 载点路径 3. dev_name: 设备名称 4. data_pages: 选项信息 */ ret = copy_mount_string(type, &kernel_type); if (ret < 0) goto out_type; kernel_dir = getname(dir_name); if (IS_ERR(kernel_dir)) { ret = PTR_ERR(kernel_dir); goto out_dir; } ret = copy_mount_string(dev_name, &kernel_dev); if (ret < 0) goto out_dev; /*用户空间复制到系统空间,拷贝整个页面*/ ret = copy_mount_options(data, &data_page); if (ret < 0) goto out_data; /*操作主体 调用do_mount 完成主要挂载工作*/ ret = do_mount(kernel_dev, kernel_dir, kernel_type, flags, (void *) data_page); free_page(data_page); out_data: kfree(kernel_dev); out_dev: putname(kernel_dir); out_dir: kfree(kernel_type); out_type: return ret; }
调用do_mount 完成主要挂载工作
long do_mount(char *dev_name, char *dir_name, char *type_page, unsigned long flags, void *data_page) { struct path path; int retval = 0; int mnt_flags = 0; /* Discard magic */ if ((flags & MS_MGC_MSK) == MS_MGC_VAL) flags &= ~MS_MGC_MSK; /* Basic sanity checks */ if (!dir_name || !*dir_name || !memchr(dir_name, 0, PAGE_SIZE)) return -EINVAL; if (data_page) ((char *)data_page)[PAGE_SIZE - 1] = 0; /* Default to relatime unless overriden */ if (!(flags & MS_NOATIME)) mnt_flags |= MNT_RELATIME; /* Separate the per-mountpoint flags */ if (flags & MS_NOSUID) mnt_flags |= MNT_NOSUID; if (flags & MS_NODEV) mnt_flags |= MNT_NODEV; if (flags & MS_NOEXEC) mnt_flags |= MNT_NOEXEC; if (flags & MS_NOATIME) mnt_flags |= MNT_NOATIME; if (flags & MS_NODIRATIME) mnt_flags |= MNT_NODIRATIME; if (flags & MS_STRICTATIME) mnt_flags &= ~(MNT_RELATIME | MNT_NOATIME); if (flags & MS_RDONLY) mnt_flags |= MNT_READONLY; flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE | MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT | MS_STRICTATIME); /* ... and get the mountpoint */ /*获得安装点path结构,用kern_path(),根据挂载点名称查找其dentry等信息 */ retval = kern_path(dir_name, LOOKUP_FOLLOW, &path); if (retval) return retval; //LSM的hook挂载点 retval = security_sb_mount(dev_name, &path, type_page, flags, data_page); if (retval) goto dput_out; //对于挂载标志的检查和初始化 if (flags & MS_REMOUNT) //修改已经存在的文件系统参数,即改变超级块对象s_flags字段的安装标志 retval = do_remount(&path, flags & ~MS_REMOUNT, mnt_flags, data_page); else if (flags & MS_BIND) //要求在系统目录树的另一个安装点上得文件或目录能够可见 retval = do_loopback(&path, dev_name, flags & MS_REC); else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE)) retval = do_change_type(&path, flags); else if (flags & MS_MOVE) //改变已安装文件的安装点* retval = do_move_mount(&path, dev_name); else retval = do_new_mount(&path, type_page, flags, mnt_flags, dev_name, data_page); dput_out: path_put(&path); return retval; }
retval = do_new_mount(&path, type_page, flags, mnt_flags, dev_name, data_page);,该函数接手来完成接下来的挂载工作
static int do_new_mount(struct path *path, char *type, int flags, int mnt_flags, char *name, void *data) { struct vfsmount *mnt; if (!type) return -EINVAL; /* we need capabilities... 必须是root权限 */ if (!capable(CAP_SYS_ADMIN)) return -EPERM; lock_kernel(); /* 调用do_kern_mount()来完成挂载第一步 1. 处理实际的安装操作并返回一个新的安装文件系统描述符地址 2. 使用get_fs_type()辅助函数扫描已经注册文件系统链表,找到匹配的file_system_type实例,该辅助函数扫描已注册文件系统的链表,返回正确的项。如果没有找到匹配的文件系统,该例程就自动加载对应的模块 3. 调用vfs_kern_mount用以调用特定于文件系统的get_sb函数读取sb结构(超级块),并与mnt关联,初始化mnt并返回 */ mnt = do_kern_mount(type, flags, name, data); unlock_kernel(); if (IS_ERR(mnt)) return PTR_ERR(mnt); /* do_add_mount处理一些必须的锁定操作,并确保一个文件系统不会重复装载到同一位置,并将创建的vfsmount结构添加到全局结构中,以便在内存中形成一棵树结构 */ return do_add_mount(mnt, path, mnt_flags, NULL); }
调用do_kern_mount()来完成挂载第一步
struct vfsmount * do_kern_mount(const char *fstype, int flags, const char *name, void *data) { /* 首先根据文件系统名称获取文件系统结构file_system_type 内核中所有支持的文件系统的该结构通过链表保存 */ struct file_system_type *type = get_fs_type(fstype); struct vfsmount *mnt; if (!type) return ERR_PTR(-ENODEV); /* 调用vfs_kern_mount()完成主要挂载 1. 分配一个代表挂载结构的struct vfs_mount结构 2. 调用具体文件系统的get_sb方法,从name代表的设备上读出超级块信息 3. 设置挂载点的dentry结构为刚读出的设备的根目录 */ mnt = vfs_kern_mount(type, flags, name, data); if (!IS_ERR(mnt) && (type->fs_flags & FS_HAS_SUBTYPE) && !mnt->mnt_sb->s_subtype) mnt = fs_set_subtype(mnt, fstype); put_filesystem(type); return mnt; } EXPORT_SYMBOL_GPL(do_kern_mount);
至此,我们第一部分的主要工作就完成了,在该部分的核心工作就是创建一个struct vfsmount,并读出文件系统超级块来初始化该结构。接下来就是将该结构添加到全局结构中,这就是do_add_mount()的主要工作,真正的挂载过程在函数do_add_mount()中完成
int do_add_mount(struct vfsmount *newmnt, struct path *path, int mnt_flags, struct list_head *fslist) { int err; down_write(&namespace_sem); /* Something was mounted here while we slept 如果在获取信号量的过程中别人已经进行了挂载,那么我们进入已挂载文件系统的根目录 */ while (d_mountpoint(path->dentry) && follow_down(path)) ; err = -EINVAL; if (!(mnt_flags & MNT_SHRINKABLE) && !check_mnt(path->mnt)) goto unlock; /* Refuse the same filesystem on the same mount point */ err = -EBUSY; if (path->mnt->mnt_sb == newmnt->mnt_sb && path->mnt->mnt_root == path->dentry) goto unlock; err = -EINVAL; if (S_ISLNK(newmnt->mnt_root->d_inode->i_mode)) goto unlock; newmnt->mnt_flags = mnt_flags; /* 调用graft_tree()来实现文件系统目录树结构,其中newmnt是本次创建的vfsmount结构,path是挂载点信息 */ if ((err = graft_tree(newmnt, path))) goto unlock; if (fslist) /* add to the specified expiration list */ list_add_tail(&newmnt->mnt_expire, fslist); up_write(&namespace_sem); return 0; unlock: up_write(&namespace_sem); mntput(newmnt); return err; } EXPORT_SYMBOL_GPL(do_add_mount);
主要是将当前新建的vfsmount结构与挂载点挂钩,并和挂载点所在的vfsmount形成一种父子关系结构。形成的结构图如下所示
至此,整个mount过程分析完毕
Relevant Link:
http://blog.csdn.net/kai_ding/article/details/9050429 http://blog.csdn.net/ding_kai/article/details/7106973 http://www.2cto.com/os/201202/119141.html
共享子树
unmount系统调用
自动过期
伪文件系统
文件系统未必需要底层块设备支持,它们可以使用内存作为后备存储器(如ramfs、tmpfs),或者根本不需要后备存储器(如procfs、sysfs),其内容是从内核数据结构包含的信息生成的。伪文件系统的例子包括
1. 负责管理表示块设备的inode的bdev 2. 处理管道的pipefs 3. 处理套接字的sockfs
所有这些都出现在/proc/filesystems中,但不能装载,内核提供了装载标志"MS_NOUSER",防止此类文件系统被装载,伪文件系统的加载和卸载都和普通文件系统都调用同一套函数API。内核可以使用kern_mount、kern_mount_data装载一个伪文件系统,最后调用vfs_kern_mount,将文件系统数据集成到VFS数据结构中。
在从用户层装载一个文件系统时,只有do_kern_mount并不够,还需要将文件和目录集成到用户可见的空间中,该工作由graft_tree处理,但如果设置了MS_NOUSER,则graft_tree拒绝工作
static int graft_tree(struct vfsmount *mnt, struct path *path) { int err; if (mnt->mnt_sb->s_flags & MS_NOUSER) return -EINVAL; ... }
需要明白的是,伪文件系统的结构内容对内核都是可用的,文件系统库提供了一些方法,可以用于向伪文件系统写入数据
0x2: 文件操作
操作整个文件系统是VFS的一个重要方面,但相对而言很少发生,因为除了可移动设备之外,文件系统都是在启动过程中装载,在关机时卸载。更常见的是对文件的频繁操作,所有系统进程都需要执行此类操作
为容许对文件的通用存取,而无需考虑所用的文件系统,VFS以各种系统调用的形式提供了用于文件处理的接口函数
1. 查找inode
一个主要操作是根据给定的文件名查找inode,nameidata结构用来向查找函数传递参数,并保存查找结果
有关struct nameidata的相关知识,请参阅另一篇文章
http://www.cnblogs.com/LittleHann/p/3865490.html //搜索:0x9: struct nameidata
内核使用path_lookup函数查找路径或文件名
\linux-2.6.32.63\fs\namei.c
/* Returns 0 and nd will be valid on success; Retuns error, otherwise. 除了所需的名称name和查找标志flags之外,该函数需要一个指向nameidata实例的指针,用作临时结果的"暂存器" */ static int do_path_lookup(int dfd, const char *name, unsigned int flags, struct nameidata *nd) { //内核使用nameidata实例规定查找的起点,如果名称以/开始,则使用当前根目录的dentry和vfsmount实例(要考虑到chroot情况),否则从当前进程的task_struct获得当前工作目录的数据 int retval = path_init(dfd, name, flags, nd); if (!retval) { retval = path_walk(name, nd); } if (unlikely(!retval && !audit_dummy_context() && nd->path.dentry && nd->path.dentry->d_inode)) { audit_inode(name, nd->path.dentry); } if (nd->root.mnt) { path_put(&nd->root); nd->root.mnt = NULL; } return retval; } int path_lookup(const char *name, unsigned int flags, struct nameidata *nd) { return do_path_lookup(AT_FDCWD, name, flags, nd); }
其中的核心path_walk()的流程是一个不断穿过目录层次的过程,逐分量处理文件名或路径名,名称在循环内部分解为各个分量(通过一个或多个斜线分隔),每个分量表示一个目录名,最后一个分量例外,总是文件名
static int path_walk(const char *name, struct nameidata *nd) { current->total_link_count = 0; return link_path_walk(name, nd); } /* * Wrapper to retry pathname resolution whenever the underlying * file system returns an ESTALE. * * Retry the whole path once, forcing real lookup requests * instead of relying on the dcache. */ static __always_inline int link_path_walk(const char *name, struct nameidata *nd) { struct path save = nd->path; int result; /* make sure the stuff we saved doesn‘t go away */ path_get(&save); result = __link_path_walk(name, nd); if (result == -ESTALE) { /* nd->path had been dropped */ nd->path = save; path_get(&nd->path); nd->flags |= LOOKUP_REVAL; result = __link_path_walk(name, nd); } path_put(&save); return result; } static int __link_path_walk(const char *name, struct nameidata *nd) { struct path next; struct inode *inode; int err; unsigned int lookup_flags = nd->flags; while (*name==‘/‘) name++; if (!*name) goto return_reval; inode = nd->path.dentry->d_inode; if (nd->depth) lookup_flags = LOOKUP_FOLLOW | (nd->flags & LOOKUP_CONTINUE); /* At this point we know we have a real path component. */ for(;;) { unsigned long hash; struct qstr this; unsigned int c; nd->flags |= LOOKUP_CONTINUE; /* 检查权限 */ err = exec_permission_lite(inode); if (err) break; this.name = name; c = *(const unsigned char *)name; //计算路径中下一个部分的散列值 hash = init_name_hash(); do { name++; //路径分量的每个字符都传递给partial_name_hash,用于计算一个递增的散列和,当路径分量的所有字符都已经计算,则将该散列和转换为最后的散列值,并保存到一个qstr中 hash = partial_name_hash(c, hash); c = *(const unsigned char *)name; } while (c && (c != ‘/‘)); this.len = name - (const char *) this.name; this.hash = end_name_hash(hash); /* remove trailing slashes? */ if (!c) goto last_component; while (*++name == ‘/‘); if (!*name) goto last_with_slashes; /* * "." and ".." are special - ".." especially so because it has * to be able to know about the current root directory and * parent relationships. */ if (this.name[0] == ‘.‘) switch (this.len) { default: break; case 2: if (this.name[1] != ‘.‘) break; /* 如果路径分量中出现两个点(..) 1. 当查找操作处理进程的根目录时,没有效果,因为根目录是没有父母了的 2. 如果当前目录不是一个装载点的根目录,则将当前dentry对象的d_parent成员用作新的目录,因为它总是表示父目录 3. 如果当前目录是一个已装载文件系统的根目录,保存在mnt_mountpoint和mnt_parent中的信息用于定义新的dentry和vfsmount对象 */ follow_dotdot(nd); inode = nd->path.dentry->d_inode; /* fallthrough */ case 1: continue; } /* * See if the low-level filesystem might want * to use its own hash.. */ if (nd->path.dentry->d_op && nd->path.dentry->d_op->d_hash) { err = nd->path.dentry->d_op->d_hash(nd->path.dentry, &this); if (err < 0) break; } /* This does the actual lookups.. 如果路径分量是一个普通文件,则内核可以通过两种方法查找对应的dentry实例(以及对应的inode) 1. 位于dentry cache中,访问它仅需很小的延迟 2. 需要通过文件系统底层实现进行查找 */ err = do_lookup(nd, &this, &next); if (err) break; err = -ENOENT; inode = next.dentry->d_inode; if (!inode) goto out_dput; /* 处理路径的最后一步是内核判断该分量是否为符号链接look */ if (inode->i_op->follow_link) { err = do_follow_link(&next, nd); if (err) goto return_err; err = -ENOENT; inode = nd->path.dentry->d_inode; if (!inode) break; } else path_to_nameidata(&next, nd); err = -ENOTDIR; if (!inode->i_op->lookup) break; continue; /* here ends the main loop */ last_with_slashes: lookup_flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY; last_component: /* Clear LOOKUP_CONTINUE iff it was previously unset */ nd->flags &= lookup_flags | ~LOOKUP_CONTINUE; if (lookup_flags & LOOKUP_PARENT) goto lookup_parent; if (this.name[0] == ‘.‘) switch (this.len) { default: break; case 2: if (this.name[1] != ‘.‘) break; follow_dotdot(nd); inode = nd->path.dentry->d_inode; /* fallthrough */ case 1: goto return_reval; } if (nd->path.dentry->d_op && nd->path.dentry->d_op->d_hash) { err = nd->path.dentry->d_op->d_hash(nd->path.dentry, &this); if (err < 0) break; } err = do_lookup(nd, &this, &next); if (err) break; inode = next.dentry->d_inode; if (follow_on_final(inode, lookup_flags)) { err = do_follow_link(&next, nd); if (err) goto return_err; inode = nd->path.dentry->d_inode; } else path_to_nameidata(&next, nd); err = -ENOENT; if (!inode) break; if (lookup_flags & LOOKUP_DIRECTORY) { err = -ENOTDIR; if (!inode->i_op->lookup) break; } goto return_base; lookup_parent: nd->last = this; nd->last_type = LAST_NORM; if (this.name[0] != ‘.‘) goto return_base; if (this.len == 1) nd->last_type = LAST_DOT; else if (this.len == 2 && this.name[1] == ‘.‘) nd->last_type = LAST_DOTDOT; else goto return_base; return_reval: /* * We bypassed the ordinary revalidation routines. * We may need to check the cached dentry for staleness. */ if (nd->path.dentry && nd->path.dentry->d_sb && (nd->path.dentry->d_sb->s_type->fs_flags & FS_REVAL_DOT)) { err = -ESTALE; /* Note: we do not d_invalidate() */ if (!nd->path.dentry->d_op->d_revalidate( nd->path.dentry, nd)) break; } return_base: return 0; out_dput: path_put_conditional(&next, nd); break; } path_put(&nd->path); return_err: return err; } static int exec_permission_lite(struct inode *inode) { int ret; /* 判断inode是否定义了permission方法 */ if (inode->i_op->permission) { ret = inode->i_op->permission(inode, MAY_EXEC); if (!ret) goto ok; return ret; } ret = acl_permission_check(inode, MAY_EXEC, inode->i_op->check_acl); if (!ret) goto ok; if (capable(CAP_DAC_OVERRIDE) || capable(CAP_DAC_READ_SEARCH)) goto ok; return ret; ok: //LSM hook挂载点 return security_inode_permission(inode, MAY_EXEC); }
循环一直重复下去,直至到达文件名的末尾,如果内核发现文件名不再出现,则确认已经到达文件名末尾
do_lookup起始于一个路径分量,并且包含最初目录数据的nameidata实例,最终返回与之相关的inode
1. 内核首先试图在dentry缓存中查找inode,使用__d_lookup函数,找到匹配的数据,并不意味着它是最新的,必须调用底层文件系统的dentry_operation中的d_revalidate函数,来检查缓存项是否仍然有效 1) 如果有效: 则将其作为缓存搜索的结果返回 2) 如果无效: 必须在底层文件系统中发起一个查找操作 如果缓存没有找到,也必须在底层文件系统中发起一个查找操作,即在内存中建立dentry结构
do_follow_link,在内核跟踪符号链接时,它必须要注意死循环符号链接的可能性
static inline int do_follow_link(struct path *path, struct nameidata *nd) { //检查链接限制 int err = -ELOOP; if (current->link_count >= MAX_NESTED_LINKS) goto loop; if (current->total_link_count >= 40) goto loop; BUG_ON(nd->depth >= MAX_NESTED_LINKS); cond_resched(); err = security_inode_follow_link(path->dentry, nd); if (err) goto loop; current->link_count++; current->total_link_count++; nd->depth++; err = __do_follow_link(path, nd); current->link_count--; nd->depth--; return err; loop: path_put_conditional(path, nd); path_put(&nd->path); return err; }
task_struct结构包含两个计数变量,用于跟踪连接
strcut task_struct { .. //link_count用于防止递归循环 int link_count; //total_link_count限制路径名中连接的最大数目 int total_link_count; .. }
2. 打开文件
在读写文件之前,必须先打开文件,从应用程序的角度来看,这是通过标准库的open函数完成的,该函数返回一个文件描述符。该函数使用了同名的open()系统调用
\linux-2.6.32.63\fs\open.c
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode) { long ret; /* 检查是否应该不考虑用户层传递的标志,总是强行设置O_LARGEFILE。如果底层处理器字长是64位系统,就需要设置这个选项 */ if (force_o_largefile()) { flags |= O_LARGEFILE; } //调用do_sys_open完成实际功能 ret = do_sys_open(AT_FDCWD, filename, flags, mode); /* avoid REGPARM breakage on x86: */ asmlinkage_protect(3, ret, filename, flags, mode); return ret; } long do_sys_open(int dfd, const char __user *filename, int flags, int mode) { /*获取文件名称,由getname()函数完成,其内部首先创建存取文件名称的空间,然后从用户空间把文件名拷贝过来*/ char *tmp = getname(filename); int fd = PTR_ERR(tmp); if (!IS_ERR(tmp)) { /* 在内核中,每个打开的文件由一个文件描述符表示,该描述符在特定于进程的数组中充当位置索引(task_strcut->files->fd_array),该数组的元素包含了file结构,其中包含了每个打开文件所有必要的信息 获取一个可用的fd,此函数调用alloc_fd()函数从fd_table中获取一个可用fd,并进行初始化 */ fd = get_unused_fd_flags(flags); if (fd >= 0) { /*fd获取成功则开始打开文件,此函数是主要完成打开功能的函数,用于获取对应文件的inode*/ struct file *f = do_filp_open(dfd, tmp, flags, mode, 0); if (IS_ERR(f)) { /*打开失败,释放fd*/ put_unused_fd(fd); fd = PTR_ERR(f); } else { //文件如果已经被打开了,调用fsnotify_open()函数 fsnotify_open(f->f_path.dentry); //将文件指针安装在fd数组中,每个进程都会将打开的文件句柄保存在fd_array[]数组中 fd_install(fd, f); } } //释放放置从用户空间拷贝过来的文件名的存储空间 putname(tmp); } return fd; }
do_filp_open完成查找文件inode的主要工作
struct file *do_filp_open(int dfd, const char *pathname, int open_flag, int mode, int acc_mode) { /* 若干变量声明 */ struct file *filp; struct nameidata nd; int error; struct path path; struct dentry *dir; int count = 0; int will_write; /*改变参数flag的值,具体做法是flag+1*/ int flag = open_to_namei_flags(open_flag); /*设置访问权限*/ if (!acc_mode) { acc_mode = MAY_OPEN | ACC_MODE(flag); } /* O_TRUNC implies we need access checks for write permissions */ /* 根据O_TRUNC标志设置写权限 */ if (flag & O_TRUNC) { acc_mode |= MAY_WRITE; } /* Allow the LSM permission hook to distinguish append access from general write access. */ /* 设置O_APPEND标志 */ if (flag & O_APPEND) { acc_mode |= MAY_APPEND; } /* The simplest case - just a plain lookup. */ /* 如果不是创建文件 */ if (!(flag & O_CREAT)) { /* 当内核要访问一个文件的时候,第一步要做的是找到这个文件,而查找文件的过程在vfs里面是由path_lookup或者path_lookup_open函数来完成的 这两个函数将用户传进来的字符串表示的文件路径转换成一个dentry结构,并建立好相应的inode和file结构,将指向file的描述符返回用户 用户随后通过文件描述符,来访问这些数据结构 */ error = path_lookup_open(dfd, pathname, lookup_flags(flag), &nd, flag); if (error) { return ERR_PTR(error); } goto ok; } /* * Create - we need to know the parent. */ //path-init为查找作准备工作,path_walk真正上路查找,这两个函数联合起来根据一段路径名找到对应的dentry error = path_init(dfd, pathname, LOOKUP_PARENT, &nd); if (error) { return ERR_PTR(error); } /* 这个函数相当重要,是整个NFS的名字解析函数,其实也是NFS得以构筑的函数 该函数采用一个for循环,对name路径根据目录的层次,一层一层推进,直到终点或失败。在推进的过程中,一步步建立了目录树的dentry和对应的inode */ error = path_walk(pathname, &nd); if (error) { if (nd.root.mnt) { /*减少dentry和vsmount得计数*/ path_put(&nd.root); } return ERR_PTR(error); } if (unlikely(!audit_dummy_context())) { /*保存inode节点信息*/ audit_inode(pathname, nd.path.dentry); } /* * We have the parent and last component. First of all, check * that we are not asked to creat(2) an obvious directory - that * will not do. */ error = -EISDIR; /*父节点信息*/ if (nd.last_type != LAST_NORM || nd.last.name[nd.last.len]) { goto exit_parent; } error = -ENFILE; /* 返回特定的file结构体指针 */ filp = get_empty_filp(); if (filp == NULL) { goto exit_parent; } /* 填充nameidata结构 */ nd.intent.open.file = filp; nd.intent.open.flags = flag; nd.intent.open.create_mode = mode; dir = nd.path.dentry; nd.flags &= ~LOOKUP_PARENT; nd.flags |= LOOKUP_CREATE | LOOKUP_OPEN; if (flag & O_EXCL) { nd.flags |= LOOKUP_EXCL; } mutex_lock(&dir->d_inode->i_mutex); /*从哈希表中查找nd对应的dentry*/ path.dentry = lookup_hash(&nd); path.mnt = nd.path.mnt; do_last: error = PTR_ERR(path.dentry); if (IS_ERR(path.dentry)) { mutex_unlock(&dir->d_inode->i_mutex); goto exit; } if (IS_ERR(nd.intent.open.file)) { error = PTR_ERR(nd.intent.open.file); goto exit_mutex_unlock; } /* Negative dentry, just create the file */ /*如果此dentry结构没有对应的inode节点,说明是无效的,应该创建文件节点 */ if (!path.dentry->d_inode) { /* * This write is needed to ensure that a * ro->rw transition does not occur between * the time when the file is created and when * a permanent write count is taken through * the ‘struct file‘ in nameidata_to_filp(). */ /*write权限是必需的*/ error = mnt_want_write(nd.path.mnt); if (error) { goto exit_mutex_unlock; } /*按照namei格式的flag open*/ error = __open_namei_create(&nd, &path, flag, mode); if (error) { mnt_drop_write(nd.path.mnt); goto exit; } /*根据nameidata 得到相应的file结构*/ filp = nameidata_to_filp(&nd, open_flag); if (IS_ERR(filp)) { ima_counts_put(&nd.path, acc_mode & (MAY_READ | MAY_WRITE | MAY_EXEC)); } /*放弃写权限*/ mnt_drop_write(nd.path.mnt); if (nd.root.mnt) { /*计数减一*/ path_put(&nd.root); } return filp; } /* * It already exists. */ /*要打开的文件已经存在*/ mutex_unlock(&dir->d_inode->i_mutex); /*保存inode节点*/ audit_inode(pathname, path.dentry); error = -EEXIST; /*flag标志检查代码*/ if (flag & O_EXCL) { goto exit_dput; } if (__follow_mount(&path)) { error = -ELOOP; if (flag & O_NOFOLLOW) { goto exit_dput; } } error = -ENOENT; if (!path.dentry->d_inode) { goto exit_dput; } if (path.dentry->d_inode->i_op->follow_link) { goto do_link; } /*路径装化为相应的nameidata结构*/ path_to_nameidata(&path, &nd); error = -EISDIR; /*如果是文件夹*/ if (path.dentry->d_inode && S_ISDIR(path.dentry->d_inode->i_mode)) { goto exit; } ok: /* * Consider: * 1. may_open() truncates a file * 2. a rw->ro mount transition occurs * 3. nameidata_to_filp() fails due to * the ro mount. * That would be inconsistent, and should * be avoided. Taking this mnt write here * ensures that (2) can not occur. */ /*检测是否截断文件标志*/ will_write = open_will_write_to_fs(flag, nd.path.dentry->d_inode); if (will_write) { /*要截断的话就要获取写权限*/ error = mnt_want_write(nd.path.mnt); if (error) { goto exit; } } //may_open执行权限检测、文件打开和truncate的操作 error = may_open(&nd.path, acc_mode, flag); if (error) { if (will_write) { mnt_drop_write(nd.path.mnt); } goto exit; } filp = nameidata_to_filp(&nd, open_flag); if (IS_ERR(filp)) { ima_counts_put(&nd.path, acc_mode & (MAY_READ | MAY_WRITE | MAY_EXEC)); } /* * It is now safe to drop the mnt write * because the filp has had a write taken * on its behalf. */ //安全的放弃写权限 if (will_write) { mnt_drop_write(nd.path.mnt); } if (nd.root.mnt) { path_put(&nd.root); } return filp; exit_mutex_unlock: mutex_unlock(&dir->d_inode->i_mutex); exit_dput: path_put_conditional(&path, &nd); exit: if (!IS_ERR(nd.intent.open.file)) { release_open_intent(&nd); } exit_parent: if (nd.root.mnt) { path_put(&nd.root); } path_put(&nd.path); return ERR_PTR(error); do_link: //允许遍历连接文件,则手工找到连接文件对应的文件 error = -ELOOP; if (flag & O_NOFOLLOW) { //不允许遍历连接文件,返回错误 goto exit_dput; } /* * This is subtle. Instead of calling do_follow_link() we do the * thing by hands. The reason is that this way we have zero link_count * and path_walk() (called from ->follow_link) honoring LOOKUP_PARENT. * After that we have the parent and last component, i.e. * we are in the same situation as after the first path_walk(). * Well, almost - if the last component is normal we get its copy * stored in nd->last.name and we will have to putname() it when we * are done. Procfs-like symlinks just set LAST_BIND. */ /* 以下是手工找到链接文件对应的文件dentry结构代码 */ //设置查找LOOKUP_PARENT标志 nd.flags |= LOOKUP_PARENT; //判断操作是否安全 error = security_inode_follow_link(path.dentry, &nd); if (error) { goto exit_dput; } //处理符号链接 error = __do_follow_link(&path, &nd); if (error) { /* Does someone understand code flow here? Or it is only * me so stupid? Anathema to whoever designed this non-sense * with "intent.open". */ release_open_intent(&nd); if (nd.root.mnt) { path_put(&nd.root); } return ERR_PTR(error); } nd.flags &= ~LOOKUP_PARENT; //检查最后一段文件或目录名的属性情况 if (nd.last_type == LAST_BIND) { goto ok; } error = -EISDIR; if (nd.last_type != LAST_NORM) { goto exit; } if (nd.last.name[nd.last.len]) { __putname(nd.last.name); goto exit; } error = -ELOOP; //出现回环标志: 循环超过32次 if (count++==32) { __putname(nd.last.name); goto exit; } dir = nd.path.dentry; mutex_lock(&dir->d_inode->i_mutex); //更新路径的挂接点和dentry path.dentry = lookup_hash(&nd); path.mnt = nd.path.mnt; __putname(nd.last.name); goto do_last; }
接下来,将控制权返回用户进程,返回文件描述符之前,fd_install必须将file实例放置到进程task_struct的files_fd数组中
3. 读取和写入
在文件成功打开后,进程将使用内核提供的read或write系统调用,来读取或修改文件的数据,入口例程是sys_read、sys_write
读写数据涉及到一个复杂的缓冲区和缓存系统,这些用于提供系统性能
5. 标准函数
0x1: 通用读取例程
0x2: 失效机制
0x3: 权限检查
Copyright (c) 2014 LittleHann All rights reserved
标签:
原文地址:http://www.cnblogs.com/LittleHann/p/4305892.html