看了之前关于Linux内存管理和进程调度的文章,读者应该对Linux有一个大概的了解,本文的主题是Linux虚拟文件系统。闲话少说,开始吧!一、软链接和硬链接的区别我们知道文件有文件名和数据,数据分为两部分:用户数据(userdata)和元数据(metadata)。用户数据,即文件数据块(datablock),数据块是记录文件真正内容的地方;而元数据是文件的附加属性,如文件大小、创建时间、所有者等信息。在Linux中,元数据中的inode号(inode是文件元数据的一部分但不包含文件名,inode号为inode编号)是文件的唯一标识,而不是文件名。文件名只是为了方便人们的记忆和使用。系统或程序通过inode号找到正确的文件数据块。称为符号链接,即软链接或符号链接)。链接为Linux系统解决了文件的共享使用,同时也带来了隐藏文件路径、增加权限安全、节省存储等好处。如果一个inode号对应多个文件名,这些文件就称为硬链接。硬链接意味着同一个文件使用多个别名。由于硬链接是一个inode号相同但文件名不同的文件,因此硬链接具有以下特点:文件具有相同的inode和数据块;创建文件;不能跨文件系统创建硬链接;不能创建目录,只能创建文件;删除硬链接文件不会影响其他具有相同索引节点号的文件。inode号只是在每个文件系统下是唯一的。Linux挂载多个文件系统时,inode号会重复。因此,创建硬链接时,不能跨文件系统。软链接不同于硬链接。如果文件用户数据块中存放的内容是另一个文件的路径名,那么这个文件就是一个软链接。软链接就是一个普通的文件,只是数据块的内容有点特殊。软链接有自己的索引节点号和用户数据块。因此,软链接的创建和使用没有很多类似硬链接的限制:软链接有自己的文件属性和权限等;可以为不存在的文件或目录创建软链接;软链接可以跨文件系统;软链接可以链接文件或创建目录;创建软链接时,i_nlink的链接数不会增加;删除软链接不影响指向的文件,但如果删除指向的原文件,相关的软链接称为死链接(即danglinglink,如果重新创建指向的路径文件,则可以恢复死链接到一个正常的软链接)。一般来说,文件名和inode号是“一一对应”的关系,每个inode号对应一个文件名。但是,Unix/Linux系统允许多个文件名指向同一个inode号。这意味着可以使用不同的文件名来访问相同的内容;修改文件内容会影响所有文件名;但是,删除一个文件名不会影响另一个文件名的访问。这种情况称为“硬链接”。2.LinuxVFSLinux拥有极为丰富的文件系统,大致可分为以下几类:网络文件系统,如nfs、cifs等;磁盘文件系统,如ext4、ext3等;特殊的文件系统,如proc、sysfs、ramfs、tmpfs等。实现上述文件系统并在Linux下共存的基础是LinuxVFS(VirtualFileSystem,也称为VirtualFilesystemSwitch),即虚拟文件系统。VFS作为一个通用文件系统,抽象了文件系统的四个基本概念:文件、目录项(dentry)、索引节点(inode)和挂载点,为内核中的用户空间层文件系统提供相关信息.界面。VFS实现了open()和read()等系统调用,并使cp等用户空间程序能够跨文件系统。VFS真正实现了以上几点:Linux中除了进程之外,一切都是文件。LinuxVFS中有四种基本对象:超级块对象(superblockobject)、索引节点对象(inodeobject)、目录项对象(dentryobject)和文件对象(fileobject)。超级块对象代表一个挂载的文件系统;索引节点对象代表一个文件;directoryentry对象表示一个目录项,比如路径/dev/input/event5中的设备文件event5,有四个目录项对象:/、dev/、input/、event5。文件对象表示进程打开的文件。为了快速解析文件路径,LinuxVFS设计了目录项缓存(DirectoryEntryCache,或dcache)。三、文件打开流程open()系统调用的流程如下:1、检查文件是否存在于系统范围的打开文件表(systemopenfiletable)中,即检查文件是否已经被打开被其他进程打开2.如果存在,那么进程会在自己的per-processopen-filetable(进程打开文件表)中创建一个item,指向系统范围的open-filetable中的文件3.如果不存在,需要根据文件名在目录中查找文件,通常目录中的部分内容在缓存中,可以加快查找速度。4.一旦找到文件,FCB(filecontrolblock)文件控制块将被复制到系统范围的打开文件表中。这个表不仅保存了FCB,还记录了每个文件被多少个进程打开。5.ThenNext,在per-processopen-filetable(进程打开文件表)中,它只是一个条目,指向进程打开文件表中的项当进程close()一个文件时:1.per-processopen-flle进程表中相应的项将被删除,系统打开表中的文件计数器将减12。如果系统打开表中的计算为0,则删除该文件项。它会一个一个地读取扇区,效率太低,而是一次连续读取多个扇区,即一次读取一个“块”(block)。这个由多个扇区组成的“块”是文件访问的最小单位。“块”的大小,最常见的是4KB,即8个连续的扇区组成一个块。文件数据是以“块”的形式存储的,那么显然,我们还必须找到一个地方来存储文件的元数据,比如文件的创建者、文件的创建日期、文件的大小等等在。这个存放文件元信息的区域称为inode,中文翻译为“索引节点”。inode包含了文件的元信息,具体有以下内容:*文件的字节数*文件所有者的UserID*文件的GroupID*文件的读、写、执行权限*文件的时间戳,有三种:ctime指inode最后一次改变时间,mtime指文件内容最后一次改变时间,atime指文件最后一次打开时间。*链接数,即有多少个文件名指向这个inode*文件数据块的位置,除文件名外的所有文件信息都存储在inode中。每个inode都有一个编号,操作系统通过inode编号来标识不同的文件。.从表面上看,用户通过文件名打开文件。其实系统内部的流程分为三步:首先,系统找到文件名对应的inode号;第二,通过inode号获取inode信息;最后根据inode信息找到文件数据所在的block,并读出数据。目录也是一种文件。目录文件的结构非常简单,就是一系列目录项(dirent)的列表。每个目录项由两部分组成:包含文件的文件名,和文件名对应的inode号。数据块寻址文件数据块的位置记录在inode中。寻址方式有3种:directblocks直接指向数据块;singleindirectpointstoablock,也就是指向数据块的指针;doubleindirect,two-levelblockLinux系统篇-Filesystem&virtualfilesystem(很重要!)5.文件描述符在Linux系统中可以看做文件,文件分为:普通文件、目录文件、链接文件和设备文件。文件描述符(filedescriptor)是内核为了高效管理打开的文件而创建的索引。它是一个非负整数(通常是一个小整数),用来指代打开的文件,所有执行I/O操作的系统调用都经过文件描述符。程序刚启动时,0为标准输入,1为标准输出,2为标准错误。如果此时打开一个新文件,它的文件描述符就是3。POSIX标准要求每次打开一个文件(包括socket)时,必须使用当前进程中最小的可用文件描述符代码。文件描述符是系统的重要资源。虽然有多少系统内存就有多少文件描述符,但是在实际执行过程中,内核会做相应的处理。一般打开文件的最大数量会是系统内存的10%(以KB计算)(称为系统级限制)6.文件描述符与打开文件之间的关系每个文件描述符都会对应一个打开的文件,同时,不同的文件描述符也会指向同一个文件。同一个文件可以被不同的进程打开,也可以在同一个进程中打开多次。系统为每个进程维护一个文件描述符表。表的值从0开始,所以你会在不同的进程中看到相同的文件描述符。在这种情况下,同一个文件描述符可能指向同一个文件,也有可能指向不同的文件。具体情况需要具体分析,要了解具体的概况,需要看一下内核维护的三种数据结构。1.进程级文件描述符表2.系统级打开文件描述符表3.文件系统i节点表进程级描述符表中的每一项记录了单个文件描述符的信息。1.一组控制文件描述符操作的标志。(目前只定义了一个这样的标志,即close-on-exec标志)2.打开文件句柄的引用内核为所有打开的文件维护了一个系统级的描述符表(打开文件描述表)。.有时也称为打开文件表,表中的每一项称为一个打开文件句柄。一个打开的文件句柄存储了一个打开文件的所有相关信息,如下:1.当前文件偏移量(在调用read()和write()时更新,或者直接由lseek()修改)2.打开文件3.文件访问方式(比如调用open()时设置的只读模式、只写模式或读写模式)4.信号驱动相关设置5.文件i节点对象的引用6.类型文件(例如:常规文件、套接字或FIFO)和访问权限7.指向文件持有的锁列表的指针8.每个文件的属性,包括文件大小和与不同类型操作相关的时间戳处理中A、文件描述符1和30都指向同一个打开的文件句柄(标签23)。这可能是由于调用dup()、dup2()、fcntl()或对同一文件多次调用open()函数引起的。进程A的文件描述符2和进程B的文件描述符2都指向同一个打开的文件句柄(标签73)。这种情况可能发生在调用fork()之后(即进程A和B是父子进程关系),或者当一个进程通过UNIX域套接字将打开的文件描述符传递给另一个进程时,也会发生。此外,不同进程独立调用open函数打开同一个文件。此时进程内部的描述符刚好分配给了其他进程打开的描述符。此外,进程A的描述符0和进程B的描述符3指向不同的打开文件句柄,但这些句柄都指向i节点表的同一个条目(1976),换句话说,指向同一个文件。发生这种情况是因为每个进程都对同一个文件进行自己的open()调用。当同一个进程打开同一个文件两次时,也会出现类似的情况。七、总结1、由于进程级文件描述符表的存在,相同的文件描述符会出现在不同的进程中,它们可能指向同一个文件,也可能指向不同的文件。2.两个不同的文件描述符,如果它们指向同一个打开的文件句柄,它们将共享相同的文件偏移量。因此,如果通过其中一个文件描述符修改了文件偏移量(由调用read()、write()或lseek()引起),也会从另一个描述符中观察到更改,无论这两个描述符是否无论文件描述符属于不同进程还是同一进程,都是如此。3.要获取和修改打开文件标志(例如:O_APPEND、O_NONBLOCK、O_ASYNC),可以执行fcntl()的F_GETFL和F_SETFL操作,作用域的约束和前面的很相似。4.文件描述符标志(即close-on-exec)对进程和文件描述符都是私有的。对该标志的修改不会影响同一进程或不同进程中的其他文件描述符
