Linux文件系统
前面介绍了文件系统的基本原理,本文通过Linux文件系统进一步深入分析文件系统的具体设计方法。 Linux的本地文件系统包括:
Ext2 (Second Extended Filesystem,第二扩展文件系统)
Ext3 (ThirdExtended Filesystem,第三扩展文件系统)
它们是UNIX文件系统的一种快速、稳定的实现。同时,为了支持多种类文件系统,如网络文件系统、Windows文件系统等,Linux还引人了虚拟文件系统(Virtual File System,
VFS),应用可以利用标准的UNIX文件系统调用,对不同种类的文件系统进行操作。
Linux本地文件系统
首先研究Linux的本地文件系统,i-node或索引节点是Linux/UNIX文件系统中最有名也是最重要的概念,它存储了文件和目录的元数据(HDFS中对应的结构分別是INodeFile和INodeDirectory,它们是INode的子类,借鉴了 i-node的命名)。所有索引节点大小相同都是128字节,这样,如果磁盘的块(块由多个连续的扇区组成)大小为1024字节,它可以包含8个索引节点。
当i-node用于保存文件时,使用一种变种的索引分配方案,典型结构如图所示。
基本的索引节点开始部分保存了文件的一些元信息,如文件类型与权限 (最前面的2字节)、所有者标识(接下来的2字节)、以字节为单位的文件长度(4字节,严格地说,是文件长度的低32位)等信息。索引节点后半部分是文件所在数据块的索引,开始的12个块地址(毎个地址需要占用4字节)存放在i-node内,这样,对于小文件,所需信息都在i-node中,如果(假设〉文件管理器的块大小是4KB,那么这12个索引能表示的最大文件是48KB。对于稍大的文件,在i-node中有一个称为一次间接块的索引,索引指向的块以连续的方式存放了数据块的索引,也就是文件内容对应的块地址。对干大于48KB的文件(假设块是4KB),部分数据的访问,需要访问一次索引节点和一次间接块,才能定位到数据所在的块,类似的,更大的文件可以通过二次间接块、三次间接块保存数据块的索引。通过间接块技术,i-node很好地实现了在支持大文件的同时,保证了小文件的访问效率。
文件的元信息都存放在文件的i-node中,目录项被保存在为目录分配的块中,它和一个正常的文件很相似,只是它的i-node上的类型和普通文件不一样。前面介绍索引节点时提过,索引节点最开始的2字节保存了文件类型与权限,普通文件类型的类型值为1,目录为2,目录的内容,即目录包含的下一级目录项,可以是目录,也可以是文件,被组织成目录項记录保持在数据块中,通过数据块的索引在目录项的i-node中管理。
由于目录项的元信息和普通文件一样存放在i-node中,所以,目录项记录包含的信息很少,图中给出了新版本目录项记录(Linux目录项有两种格式)中保存的内容和数据布局。
其中,最前面的4字节是目录项对应的i-node号,接下来是本目录项的长度(注意,记录长度出现在记录的第二个字段,而且包含i-node号的那4字节接下来是文件名长度,由于它占1字节,所以,Linux文件的文件/目录名最长是255字节。
—个目录下的所有目录項组织起来,作为数据存放在数据块中。其中,每个目录的前两个目录项分別是当前B录和父目录打开文件时,文件管理器根据文件名,找到它所在的磁盘块。
例如,要査找路径名“/home/aUCe/data.txt”的文件,首先文件管理器要找到根目录,Linux的根目录总是位于系统的2号i-node,通过2号i-node可以找到根目录目录项所在的数据块,在该块的内容中找路径的第一部分:“home”,从而获得目录“/home”的i-节点号,由i-node号可以很直接找到对应的i-node,然后通过这个i-node找鹨对应的数据块,就可以开始査找目录“/home/alice” 了。利用相同的流程,最终获得“/home/alice/data.txt”的i-node号。打开这个文件时,对应的i-node会被读人内存,如图所示.
其中,超级块保存了这个逻辑磁盘结构的一些信息,包括块大小、总块数、总i-node数等。数据块位图和i-node位图分别记录了 i-node表和数据块的使用情况,这样的组织有利干i-node和数据块的分配,接下来才是前面介绍的i-node表和数据块区,如果知道一个i-node号,由于i-node的大小都是
一样的,根据i-node表的起始位置,很快就可以定位到对应的i-node,对于数据块,定位的机制也是一样。
通过Linux的mount命令安装文件系统时,文件管理器会读入起级块,这样就可以获得整个ExtX文件系统的信息,进而挂载整个文件系统,超级块的格式厲于存储媒体上的文件系统的物理结构研究的范围。
虚拟文件系统
随着需求的增长,要求操作系统能够支持不同类型的文件系统,如在Linux环境中访问NTFS (New Technology File System,新技术文件系统)。为了实现这个功能,Linux内核使用了虚拟文件系统(Virtual File System, VFS),它是内核中的一个软件层,为上层应用提供文件系统接口,该接口隐藏了底层各种文件系统的具体细节。基干VFS的文件管理器如图所示:
VFS是由面向对象的思想发展起来的,提供了一个抽象基类,由这个基类派生的子类支持具体的文件系统。真正的文件系统,如Ext2/Ext3、NFS (网络文件系统)、iSO 9660等,在VFS提供的统一接口下工作。当应用程序对文件系统进行操作时,内核文件系统首先调用VFS的相应函败,处理与文件系统无关的搡作,然后再调用真正文件系统中对应的函数,处理与设备相关的操作。
VFS提供的模型源于UNIX风格的文件系统:文件、目录项、索引节点和超级块。像FAT (File Allocation Table)、NTFS这样非UNIX风格的文件系统,为了在Linux中工作,它们必须经过封装提供一个符合这些籤念的界面•如FAT,它本来就不支持索引节点,但具体支持FAT的文件管理器,必须在内存中,根据FAT文件系统的结构,构造出对应的索引节点,就像它本身躭支持索引节点一样。但即使如此,Linux仍然可以支持差异很大的文件系统.
vfs在现代文件管理器中获得巨大的成功,并为实现远程文件服务提供了基础。
Linux文件保护机制
Linux文件系统采用一种改进的存取控制表来实现文件系统的保护控制。首先将文件的潜在用户分为以下3种:
1.作为文件所有者的用户。
2.同组用户、不包括所有者。
3.所有剩下的用户(其他)。
毎个文件都保存了文件所有者和文件所有者所在用户组的信息.同时,Linux将文件的访问权限分为读、写和执行,毎组用户都有这几种扠限.通过将文件潜在用户分类和权限相结合,Linux实现了对文件的保护,这样,毎个文件只需要9位信惠,就实现了存取控制表,该方案虽然没有上面讨论的存取控制表那么全面,但实用、简单而且方便.
对于文件的读、写和执行权限,具体内容为:
1.r (read):可以读取文件的内容。
2.w (write):可以编辑、修改文件的内容。
3.x (execute):该文件可以被执行。
需要注意的是,文件的这些权限都是针对文件的内容,与文件本身没有任何关系,即便是对文件有“rwx”权限,用户也不一定可以修改文件名或刪除文件。
对于目录,如果将目录下的所有文件/子目录看成是目录的内容,其读、写和执行权限具体内容为:
1.r (read):可以读取文件夹内容列表,伹如果用户没有“X”权限,就只能看到文件名而无法査看其他内容(大小、权限等),
2.w (write):用户具有“w”权限,就可以修改目录的内容,即文件夹记录列表,前提是用户拥有“x”权限,可以进入这个目录。修改目录的内容,也就是“w”权限,包括:建立新的文件或文件夹、删除已存在的文件或文件夹、对已存在的文件或文件夹改名和更改目录内文件或文件夹的位置。
3.x (execute):可以进入该文件夹,没有“x”权限便无法执行该目录下的任何命令,需赛注意的是,对文件/子目录的改名与删除,系统检査文件父目录的”w”权限,而和文件本身的权限没有关系。当用户改名或删除文件/子目录时,执行的是对它上一级目录的“w”操作,因此,刪除文件/子目录不需要考虑被删除文件/子目录自身的权限设置。
对于文件"/home/bin/README”,只有用户Bin才能够进行读写,其他用户不能进行任何读、写和执行操作,
对于文件“/home/alice/bin/lc”,文件所有者Bin能读取文件,不能写和执行:处于user组的其他用户具有写和执行的权限,其他用户没有任何权限。
对于文件“/bin/datetime”,系统中所有的用户都具有读和执行权限,伹只有root用户才能对文件内容进行修改,
对于上面于文件“/bin/datetime”的执行权限,严格来说,还需要考虑目录/bin的执行权限
最后分析目录“/bin”。该目录的访问控制位是“rwxr-xr-x”,除了 root用户对目录具有写权限以外,其他用户都不能对“/bin”目录进行修改•也就是说,只有root用户才能在“/bin”目录下创建文件/子目录或删除文件/子0录,或者对文件/子目录进行改名等。非root用户由于只有“r-x”权限,他们能够在“/bin”下列目录,或执行文件(命令),但不能修改目录内容。
Linux 文件系统 API
Linux文件系统API分为两类:
1.文件I/O函数:用于对文件数据读写.
2.文件/目录API:主要是针对文件、目录属性的操作和目录树的操作,不涉及文件数据读写。
文件I/O函数
文件I/O函数包括5个函数:open(),read()、write()、close()和lseek()。应用读写文件之前,必须首先打开文件,这个操作使得文件管理器为需要读写的指定文件做准备。
系统调用open()打开文件,形式如下:
intfid=open(”/home/alice/data.txt“, flag)
其中第一个参数规定了要打开文件的路径名,flag叫是—个开关,如flag中O_RDONLY被设置,表明这次打开是为读打开的。当文件被打开时,文件管理器通过前面介绍的步在目录树中査找文件,并进行检査以确保进程被授权访问文件。文件管理器还会对open()操作检査其他约束,如文件的读/写锁。在确认了进程可以访问文件后,系统会在特定的进程打开文件表中创建表項,该表項由调用返回的整数值来表示,该表项指向打开文件表中被称为文件结构的另外一个表项,文件结构中保存了文件读写位置等信息,如图所示:
如果有两个不同的进程打开了同一个文件,如“/home/alice/data.txt”,那么毎个进程都有它们自己的文件读写位置。打开文件的同时,文件的i-node也会被加载到主存,文件结构表中有指针指向内存中的i-n〇de拷贝•特定文件的i-nodc拷贝只有一份•也就是说,两个不同的进程打开同一个文件,它们各自的打开文件表,指向了同一个i-node拷贝。
文件打开后.就可以进行读写操作了。以read()为例该函数的语法格式如下:
char buffer[1024];
int number=read(fid, buffer, 1024)
fid是由open()打开的文件描述符,buffer是用户进程空间中的一个数据结构的地址,read()的第三个参数是该数据结构的大小,或用户要读的字节数。该调用结束后,buffer中存放所读的文件数据,返回值number是实际读取数据的字节数。
文件管理器接到read()调用时,根据fid找到对应的文件结构项和内存i-node。接着,读请求被分成几个段,毎段对应于设备上的一块。例如,当前文件位置为1600字节,要读取的数据长度为1K字节,设备块大小为1K字节,那么读请求将分为两个部分,分别为1600〜2023字节和2024〜2623字节。通过i-node的直接块指针,可以找到相应的块号,在不考虑文件管理器髙速缓存的情况下,内核向设备管理系统发出两个读操作请求,设备管理系统读操作返回后。再将数据拷贝到用户提供的缓存区,然后文件管理器送出响应消息,告知拷贝字节数,调用结束。
每个打开文件对应的打开文件表中保存了文件读写位置,通常读写操作都是从这个位置
开始,通过lseek()可以改变文件读笱位置。
写文件结束以后,可以通过close()方法关闭文件,释放相关资源。文件管理器如果更
新了内存中的i-node (如在文件中追加数据改变了文件长度),在文件关闭或应用程序发出
sync()命令后,内存中的i-node会被更新到设备上。
未完待续............