下载

1下载券

加入VIP
  • 专属下载特权
  • 现金文档折扣购买
  • VIP免费专区
  • 千万文档免费下载

上传资料

关闭

关闭

关闭

封号提示

内容

首页 从文件 IO看linux虚拟文件系统

从文件 IO看linux虚拟文件系统.pdf

从文件 IO看linux虚拟文件系统

生命之光
2011-10-14 0人阅读 举报 0 0 0 暂无简介

简介:本文档为《从文件 IO看linux虚拟文件系统pdf》,可适用于IT/计算机领域

从文件 IO 看 Linux 的虚拟文件系统   简介: Linux 允许众多不同的文件系统共存并支持跨文件系统的文件操作这是因为有虚拟文件系统的存在。虚拟文件系统即VFS(Virtual File System)是 Linux 内核中的一个软件抽象层。它通过一些数据结构及其方法向实际的文件系统如 extvfat 提供接口机制。本文在简要介绍 VFS 的相关数据结构后以文件 IO 为切入点深入 Linux 内核源代码追踪了 sysopen 和 sysread 两个系统调用的代码结构并在追踪的过程中理清了跨文件系统的文件操作的基本原理和“一切皆是文件”的口号得以实现的根本。  引言Linux中允许众多不同的文件系统共存如ext,ext,vfat等。通过使用同一套文件IO系统调用即可对Linux中的任意文件进行操作而无需考虑其所在的具体文件系统格式更进一步对文件的操作可以跨文件系统而执行。如图所示我们可以使用cp命令从vfat文件系统格式的硬盘拷贝数据到ext文件系统格式的硬盘而这样的操作涉及到两个不同的文件系统。图跨文件系统的文件操作“一切皆是文件”是UnixLinux的基本哲学之一。不仅普通的文件目录、字符设备、块设备、套接字等在UnixLinux中都是以文件被对待它们虽然类型不同但是对其提供的却是同一套操作界面。图一切皆是文件而虚拟文件系统正是实现上述两点Linux特性的关键所在。虚拟文件系统(VirtualFileSystem,简称VFS)是Linux内核中的一个软件层用于给用户空间的程序提供文件系统接口同时它也提供了内核中的一个抽象功能允许不同的文件系统共存。系统中所有的文件系统不但依赖VFS共存而且也依靠VFS协同工作。为了能够支持各种实际文件系统VFS定义了所有文件系统都支持的基本的、概念上的接口和数据结构同时实际文件系统也提供VFS所期望的抽象接口和数据结构将自身的诸如文件、目录等概念在形式上与VFS的定义保持一致。换句话说一个实际的文件系统想要被Linux支持就必须提供一个符合VFS标准的接口才能与VFS协同工作。实际文件系统在统一的接口和数据结构下隐藏了具体的实现细节所以在VFS层和内核的其他部分看来所有文件系统都是相同的。图显示了VFS在内核中与实际的文件系统的协同关系。图VFS在内核中与其他的内核模块的协同关系我们已经知道正是由于在内核中引入了VFS跨文件系统的文件操作才能实现“一切皆是文件”的口号才能承诺。而为什么引入了VFS就能实现这两个特性呢?在接下来我们将以这样的一个思路来切入文章的正题:我们将先简要介绍下用以描述VFS模型的一些数据结构总结出这些数据结构相互间的关系然后选择两个具有代表性的文件IO操作sysopen()和sysread()来详细说明内核是如何借助VFS和具体的文件系统打交道以实现跨文件系统的文件操作和承诺“一切皆是文件”的口号。VFS数据结构一些基本概念从本质上讲文件系统是特殊的数据分层存储结构它包含文件、目录和相关的控制信息。为了描述这个结构Linux引入了一些基本概念:文件一组在逻辑上具有完整意义的信息项的系列。在Linux中除了普通文件其他诸如目录、设备、套接字等也以文件被对待。总之“一切皆文件”。目录目录好比一个文件夹用来容纳相关文件。因为目录可以包含子目录所以目录是可以层层嵌套形成文件路径。在Linux中目录也是以一种特殊文件被对待的所以用于文件的操作同样也可以用在目录上。目录项在一个文件路径中路径中的每一部分都被称为目录项如路径homesourcehelloworldc中目录,home,source和文件helloworldc都是一个目录项。索引节点用于存储文件的元数据的一个数据结构。文件的元数据也就是文件的相关信息和文件本身是两个不同的概念。它包含的是诸如文件的大小、拥有者、创建时间、磁盘位置等和文件相关的信息。超级块用于存储文件系统的控制信息的数据结构。描述文件系统的状态、文件系统类型、大小、区块数、索引节点数等存放于磁盘的特定扇区中。如上的几个概念在磁盘中的位置关系如图所示。图磁盘与文件系统关于文件系统的三个易混淆的概念:创建以某种方式格式化磁盘的过程就是在其之上建立一个文件系统的过程。创建文现系统时会在磁盘的特定位置写入关于该文件系统的控制信息。注册向内核报到声明自己能被内核支持。一般在编译内核的时侯注册也可以加载模块的方式手动注册。注册过程实际上是将表示各实际文件系统的数据结构structfilesystemtype实例化。安装也就是我们熟悉的mount操作将文件系统加入到Linux的根文件系统的目录树结构上这样文件系统才能被访问。VFS数据结构VFS依靠四个主要的数据结构和一些辅助的数据结构来描述其结构信息这些数据结构表现得就像是对象每个主要对象中都包含由操作函数表构成的操作对象这些操作对象描述了内核针对这几个主要的对象可以进行的操作。超级块对象存储一个已安装的文件系统的控制信息代表一个已安装的文件系统每次一个实际的文件系统被安装时内核会从磁盘的特定位置读取一些控制信息来填充内存中的超级块对象。一个安装实例和一个超级块对象一一对应。超级块通过其结构中的一个域stype记录它所属的文件系统类型。根据第三部分追踪源代码的需要以下是对该超级块结构的部分相关成员域的描述(如下同):清单超级块structsuperblock{超级块数据结构structlistheadslist*指向超级块链表的指针*⋯⋯structfilesystemtype*stype*文件系统类型*structsuperoperations*sop*超级块方法*⋯⋯structlistheadsinstances*该类型文件系统*⋯⋯}structsuperoperations{超级块方法⋯⋯该函数在给定的超级块下创建并初始化一个新的索引节点对象structinode*(*allocinode)(structsuperblock*sb)⋯⋯该函数从磁盘上读取索引节点并动态填充内存中对应的索引节点对象的剩余部分void(*readinode)(structinode*)⋯⋯}索引节点对象索引节点对象存储了文件的相关信息代表了存储设备上的一个实际的物理文件。当一个文件首次被访问时内核会在内存中组装相应的索引节点对象以便向内核提供对一个文件进行操作时所必需的全部信息这些信息一部分存储在磁盘特定位置另外一部分是在加载时动态填充的。清单索引节点structinode{索引节点结构⋯⋯structinodeoperations*iop*索引节点操作表*structfileoperations*ifop*该索引节点对应文件的文件操作集*structsuperblock*isb*相关的超级块*⋯⋯}structinodeoperations{索引节点方法⋯⋯该函数为dentry对象所对应的文件创建一个新的索引节点主要是由open()系统调用来调用int(*create)(structinode*,structdentry*,int,structnameidata*)在特定目录中寻找dentry对象所对应的索引节点structdentry*(*lookup)(structinode*,structdentry*,structnameidata*)⋯⋯}目录项对象引入目录项的概念主要是出于方便查找文件的目的。一个路径的各个组成部分不管是目录还是普通的文件都是一个目录项对象。如在路径homesourcetestc中目录,home,source和文件testc都对应一个目录项对象。不同于前面的两个对象目录项对象没有对应的磁盘数据结构VFS在遍历路径名的过程中现场将它们逐个地解析成目录项对象。清单目录项structdentry{目录项结构⋯⋯structinode*dinode*相关的索引节点*structdentry*dparent*父目录的目录项对象*structqstrdname*目录项的名字*⋯⋯structlistheaddsubdirs*子目录*⋯⋯structdentryoperations*dop*目录项操作表*structsuperblock*dsb*文件超级块*⋯⋯}structdentryoperations{判断目录项是否有效int(*drevalidate)(structdentry*,structnameidata*)为目录项生成散列值int(*dhash)(structdentry*,structqstr*)⋯⋯}文件对象文件对象是已打开的文件在内存中的表示主要用于建立进程和磁盘上的文件的对应关系。它由sysopen()现场创建由sysclose()销毁。文件对象和物理文件的关系有点像进程和程序的关系一样。当我们站在用户空间来看待VFS我们像是只需与文件对象打交道而无须关心超级块索引节点或目录项。因为多个进程可以同时打开和操作同一个文件所以同一个文件也可能存在多个对应的文件对象。文件对象仅仅在进程观点上代表已经打开的文件它反过来指向目录项对象(反过来指向索引节点)。一个文件对应的文件对象可能不是惟一的但是其对应的索引节点和目录项对象无疑是惟一的。清单文件对象structfile{⋯⋯structlistheadflist*文件对象链表*structdentry*fdentry*相关目录项对象*structvfsmount*fvfsmnt*相关的安装文件系统*structfileoperations*fop*文件操作表*⋯⋯}structfileoperations{⋯⋯文件读操作ssizet(*read)(structfile*,charuser*,sizet,lofft*)⋯⋯文件写操作ssizet(*write)(structfile*,constcharuser*,sizet,lofft*)⋯⋯int(*readdir)(structfile*,void*,filldirt)⋯⋯文件打开操作int(*open)(structinode*,structfile*)⋯⋯}其他VFS对象和文件系统相关根据文件系统所在的物理介质和数据在物理介质上的组织方式来区分不同的文件系统类型的。filesystemtype结构用于描述具体的文件系统的类型信息。被Linux支持的文件系统都有且仅有一个filesystemtype结构而不管它有零个或多个实例被安装到系统中。而与此对应的是每当一个文件系统被实际安装就有一个vfsmount结构体被创建这个结构体对应一个安装点。清单和文件系统相关structfilesystemtype{constchar*name*文件系统的名字*structsubsystemsubsys*sysfs子系统对象*intfsflags*文件系统类型标志**在文件系统被安装时从磁盘中读取超级块,在内存中组装超级块对象*structsuperblock*(*getsb)(structfilesystemtype*,int,constchar*,void*)void(*killsb)(structsuperblock*)*终止访问超级块*structmodule*owner*文件系统模块*structfilesystemtype*next*链表中的下一个文件系统类型*structlistheadfssupers*具有同一种文件系统类型的超级块对象链表*}structvfsmount{structlistheadmnthash*散列表*structvfsmount*mntparent*父文件系统*structdentry*mntmountpoint*安装点的目录项对象*structdentry*mntroot*该文件系统的根目录项对象*structsuperblock*mntsb*该文件系统的超级块*structlistheadmntmounts*子文件系统链表*structlistheadmntchild*子文件系统链表*atomictmntcount*使用计数*intmntflags*安装标志*char*mntdevname*设备文件名*structlistheadmntlist*描述符链表*structlistheadmntfslink*具体文件系统的到期列表*structnamespace*mntnamespace*相关的名字空间*}和进程相关清单打开的文件集structfilesstruct{打开的文件集atomictcount*结构的使用计数*⋯⋯intmaxfds*文件对象数的上限*intmaxfdset*文件描述符的上限*intnextfd*下一个文件描述符*structfile**fd*全部文件对象数组*⋯⋯}structfsstruct{建立进程与文件系统的关系atomictcount*结构的使用计数*rwlocktlock*保护该结构体的锁*intumask*默认的文件访问权限*structdentry*root*根目录的目录项对象*structdentry*pwd*当前工作目录的目录项对象*structdentry*altroot*可供选择的根目录的目录项对象*structvfsmount*rootmnt*根目录的安装点对象*structvfsmount*pwdmnt*pwd的安装点对象*structvfsmount*altrootmnt*可供选择的根目录的安装点对象*}和路径查找相关清单辅助查找structnameidata{structdentry*dentry*目录项对象的地址*structvfsmount*mnt*安装点的数据*structqstrlast*路径中的最后一个component*unsignedintflags*查找标识*intlasttype*路径中的最后一个component的类型*unsigneddepth*当前symboliclink的嵌套深度不能大于*char*savednamesMAXNESTEDLINKS*和嵌套symboliclink相关的pathname*union{structopenintentopen*说明文件该如何访问*}intent*专用数据*}对象间的联系如上的数据结构并不是孤立存在的。正是通过它们的有机联系VFS才能正常工作。如下的几张图是对它们之间的联系的描述。如图所示被Linux支持的文件系统都有且仅有一个filesystemtype结构而不管它有零个或多个实例被安装到系统中。每安装一个文件系统就对应有一个超级块和安装点。超级块通过它的一个域stype指向其对应的具体的文件系统类型。具体的文件系统通过filesystemtype中的一个域fssupers链接具有同一种文件类型的超级块。同一种文件系统类型的超级块通过域sinstances链接。图超级块、安装点和具体的文件系统的关系从图可知:进程通过taskstruct中的一个域filesstructfiles来了解它当前所打开的文件对象而我们通常所说的文件描述符其实是进程打开的文件对象数组的索引值。文件对象通过域fdentry找到它对应的dentry对象再由dentry对象的域dinode找到它对应的索引结点这样就建立了文件对象与实际的物理文件的关联。最后还有一点很重要的是,文件对象所对应的文件操作函数列表是通过索引结点的域ifop得到的。图对第三部分源码的理解起到很大的作用。图 进程与超级块、文件、索引结点、目录项的关系  基于VFS的文件IO到目前为止文章主要都是从理论上来讲述VFS的运行机制接下来我们将深入源代码层中通过阐述两个具有代表性的系统调用sysopen()和sysread()来更好地理解VFS向具体文件系统提供的接口机制。由于本文更关注的是文件操作的整个流程体制所以我们在追踪源代码时对一些细节性的处理不予关心。又由于篇幅所限只列出相关代码。本文中的源代码来自于linux内核版本。在深入sysopen()和sysread()之前我们先概览下调用sysread()的上下文。图描述了从用户空间的read()调用到数据从磁盘读出的整个流程。当在用户应用程序调用文件IOread()操作时系统调用sysread()被激发sysread()找到文件所在的具体文件系统把控制权传给该文件系统最后由具体文件系统与物理介质交互从介质中读出数据。图从物理介质读数据的过程sysopen()sysopen()系统调用打开或创建一个文件成功返回该文件的文件描述符。图是sysopen()实现代码中主要的函数调用关系图。图sysopen函数调用关系图由于sysopen()的代码量大函数调用关系复杂以下主要是对该函数做整体的解析而对其中的一些关键点则列出其关键代码。a从sysopen()的函数调用关系图可以看到sysopen()在做了一些简单的参数检验后就把接力棒传给dosysopen():)、首先getunusedfd()得到一个可用的文件描述符通过该函数可知文件描述符实质是进程打开文件列表中对应某个文件对象的索引值)、接着dofilpopen()打开文件返回一个file对象代表由该进程打开的一个文件进程通过这样的一个数据结构对物理文件进行读写操作。)、最后fdinstall()建立文件描述符与file对象的联系以后进程对文件的读写都是通过操纵该文件描述符而进行。bdofilpopen()用于打开文件返回一个file对象而打开之前需要先找到该文件:)、opennamei()用于根据文件路径名查找文件借助一个持有路径信息的数据结构nameidata而进行)、查找结束后将填充有路径信息的nameidata返回给接下来的函数nameidatatofilp()从而得到最终的file对象当达到目的后nameidata这个数据结构将会马上被释放。copennamei()用于查找一个文件:)、pathlookupopen()实现文件的查找功能要打开的文件若不存在还需要有一个新建的过程则调用pathlookupcreate()后者和前者封装的是同一个实际的路径查找函数只是参数不一样使它们在处理细节上有所偏差)、当是以新建文件的方式打开文件时即设置了OCREAT标识时需要创建一个新的索引节点代表创建一个文件。在vfscreate()里的一句核心语句dir>iop>create(dir,dentry,mode,nd)可知它调用了具体的文件系统所提供的创建索引节点的方法。注意:这边的索引节点的概念还只是位于内存之中它和磁盘上的物理的索引节点的关系就像位于内存中和位于磁盘中的文件一样。此时新建的索引节点还不能完全标志一个物理文件的成功创建只有当把索引节点回写到磁盘上才是一个物理文件的真正创建。想想我们以新建的方式打开一个文件对其读写但最终没有保存而关闭则位于内存中的索引节点会经历从新建到消失的过程而磁盘却始终不知道有人曾经想过创建一个文件这是因为索引节点没有回写的缘故。)、pathtonameidata()填充nameidata数据结构)、mayopen()检查是否可以打开该文件一些文件如链接文件和只有写权限的目录是不能被打开的先检查nd>dentry>inode所指的文件是否是这一类文件是的话则错误返回。还有一些文件是不能以TRUNC的方式打开的若nd>dentry>inode所指的文件属于这一类则显式地关闭TRUNC标志位。接着如果有以TRUNC方式打开文件的则更新nd>dentry>inode的信息pathlookupintentopen()不管是pathlookupopen()还是pathlookupcreate()最终都是调用pathlookupintentopen()来实现查找文件的功能。查找时在遍历路径的过程中会逐层地将各个路径组成部分解析成目录项对象如果此目录项对象在目录项缓存中则直接从缓存中获得如果该目录项在缓存中不存在则进行一次实际的读盘操作从磁盘中读取该目录项所对应的索引节点。得到索引节点后则建立索引节点与该目录项的联系。如此循环直到最终找到目标文件对应的目录项也就找到了索引节点而由索引节点找到对应的超级块对象就可知道该文件所在的文件系统的类型。从磁盘中读取该目录项所对应的索引节点这将引发VFS和实际的文件系统的一次交互。从前面的VFS理论介绍可知读索引节点方法是由超级块来提供的。而当安装一个实际的文件系统时在内存中创建的超级块的信息是由一个实际文件系统的相关信息来填充的这里的相关信息就包括了实际文件系统所定义的超级块的操作函数列表当然也就包括了读索引节点的具体执行方式。当继续追踪一个实际文件系统ext的extreadinode()时可发现这个函数很重要的一个工作就是为不同的文件类型设置不同的索引节点操作函数表和文件操作函数表。清单extreadinodevoidextreadinode(structinode*inode){⋯⋯是普通文件if(SISREG(inode>imode)){inode>iop=extfileinodeoperationsinode>ifop=extfileoperationsextsetaops(inode)}elseif(SISDIR(inode>imode)){是目录文件inode>iop=extdirinodeoperationsinode>ifop=extdiroperations}elseif(SISLNK(inode>imode)){是连接文件⋯⋯}else{如果以上三种情况都排除了则是设备驱动这里的设备还包括套结字、FIFO等伪设备⋯⋯}nameidatatofilp子函数:dentryopen这是VFS与实际的文件系统联系的一个关键点。从小节分析中可知调用实际文件系统读取索引节点的方法读取索引节点时实际文件系统会根据文件的不同类型赋予索引节点不同的文件操作函数集如普通文件有普通文件对应的一套操作函数设备文件有设备文件对应的一套操作函数。这样当把对应的索引节点的文件操作函数集赋予文件对象以后对该文件进行操作时比如读操作VFS虽然对各种不同文件都是执行同一个read()操作界面但是真正读时内核却知道怎么区分对待不同的文件类型。清单dentryopenstaticstructfile*dentryopen(structdentry*dentry,structvfsmount*mnt,intflags,structfile*f,int(*open)(structinode*,structfile*)){structinode*inode⋯⋯整个函数的工作在于填充一个file对象⋯⋯f>fmapping=inode>imappingf>fdentry=dentryf>fvfsmnt=mntf>fpos=将对应的索引节点的文件操作函数集赋予文件对象的操作列表f>fop=fopsget(inode>ifop)⋯⋯若文件自己定义了open操作则执行这个特定的open操作。if(!openf>fop)open=f>fop>openif(open){error=open(inode,f)if(error)gotocleanupall⋯⋯returnf}sysread()sysread()系统调用用于从已打开的文件读取数据。如read成功则返回读到的字节数。如已到达文件的尾端则返回。图是sysread()实现代码中的函数调用关系图。图sysread函数调用关系图对文件进行读操作时需要先打开它。从小结可知打开一个文件时会在内存组装一个文件对象希望对该文件执行的操作方法已在文件对象设置好。所以对文件进行读操作时VFS在做了一些简单的转换后(由文件描述符得到其对应的文件对象其核心思想是返回current>files>fdfd所指向的文件对象)就可以通过语句file>fop>read(file,buf,count,pos)轻松调用实际文件系统的相应方法对文件进行读操作了。解决问题跨文件系统的文件操作的基本原理到此我们也就能够解释在Linux中为什么能够跨文件系统地操作文件了。举个例子将vfat格式的磁盘上的一个文件atxt拷贝到ext格式的磁盘上命名为btxt。这包含两个过程对atxt进行读操作对btxt进行写操作。读写操作前需要先打开文件。由前面的分析可知打开文件时VFS会知道该文件对应的文件系统格式以后操作该文件时VFS会调用其对应的实际文件系统的操作方法。所以VFS调用vfat的读文件方法将atxt的数据读入内存在将atxt在内存中的数据映射到btxt对应的内存空间后VFS调用ext的写文件方法将btxt写入磁盘从而实现了最终的跨文件系统的复制操作。“一切皆是文件”的实现根本不论是普通的文件还是特殊的目录、设备等VFS都将它们同等看待成文件通过同一套文件操作界面来对它们进行操作。操作文件时需先打开打开文件时VFS会知道该文件对应的文件系统格式当VFS把控制权传给实际的文件系统时实际的文件系统再做出具体区分对不同的文件类型执行不同的操作。这也就是“一切皆是文件”的根本所在。总结VFS即虚拟文件系统是Linux文件系统中的一个抽象软件层因为它的支持众多不同的实际文件系统才能在Linux中共存跨文件系统操作才能实现。VFS借助它四个主要的数据结构即超级块、索引节点、目录项和文件对象以及一些辅助的数据结构向Linux中不管是普通的文件还是目录、设备、套接字等都提供同样的操作界面如打开、读写、关闭等。只有当把控制权传给实际的文件系统时实际的文件系统才会做出区分对不同的文件类型执行不同的操作。由此可见正是有了VFS的存在跨文件系统操作才能执行UnixLinux中的“一切皆是文件”的口号才能够得以实现。 

用户评价(0)

关闭

新课改视野下建构高中语文教学实验成果报告(32KB)

抱歉,积分不足下载失败,请稍后再试!

提示

试读已结束,如需要继续阅读或者下载,敬请购买!

评分:

/16

VIP

在线
客服

免费
邮箱

爱问共享资料服务号

扫描关注领取更多福利