linux文件系统之文件组织方式
本文中将介绍一个具体的linux标准文件系统ext2的磁盘上文件组织方式和数据块寻址(逻辑地址到物理地址映射)
两个问题:
1.一个文件如何组织,采用何种结构;
2.文件的读写实现,如何从逻辑空间找到磁盘上的物理块;
1.文件的组织方式(微观角度,以下讨论的都是单个文件是如何被组织的):
具体文件系统管理的是一个逻辑空间,这个逻辑空间就象一个大的数组,数组的每个元素就是文件系统操作的基本单位——逻辑块,逻辑块是从0开始编号的,而且,逻辑块是连续的。与逻辑块相对的是物理块,物理块是数据在磁盘上的存取单位,也就是每进行一次I/O操作,最小传输的数据大小。我们知道数据是存储在磁盘的扇区中的,那么扇区是不是物理块呢?或者物理块是多大呢?这涉及到文件系统效率的问题。
如果物理块定的比较大,比如一个柱面大小,这时,即使是1个字节的文件都要占用整个一个柱面,假设Linux环境下文件的平均大小为1K,那么分配32K的柱面将浪费97%的磁盘空间,也就是说,大的存取单位将带来严重的磁盘空间浪费。另一方面,如果物理块过小,则意味着对一个文件的操作将进行更多次的寻道延迟和旋转延迟,因而读取由小的物理块组成的文件将非常缓慢!可见,时间效率和空间效率在本质上是相互冲突的。
因此,最优的方法是计算出Linux环境下文件的平均大小,然后将物理块大小定为最接近扇区的整数倍大小。在Ext2中,物理块的大小是可变化的,这取决于你在创建文件系统时的选择,之所以不限制大小,也正体现了Ext2的灵活性和可扩充性,一是因为要适应近年来文件的平均长度缓慢增长的趋势,二是为了适应不同的需要。比如,如果一个文件系统主要用于BBS服务,考虑到BBS上的文章通常很短小,所以,物理块选的小一点是恰当的。通常,Ext2的物理块占一个或几个连续的扇区,显然,物理块的数目是由磁盘容量等硬件因素决定的。逻辑块与物理块的关系类似于虚拟内存中的页与物理内存中的页面的关系。
具体文件系统所操作的基本单位是逻辑块,只在需要进行I/O操作时才进行逻辑块到物理块的映射,这显然避免了大量的I/O操作,因而文件系统能够变得高效。逻辑块作为一个抽象的概念,它必然要映射到具体的物理块上去,因此,逻辑块的大小必须是物理块大小的整数倍,一般说来,两者是一样大的。
通常,一个文件占用的多个物理块在磁盘上是不连续存储的,因为如果连续存储,则经过频繁的删除、建立、移动文件等操作,最后磁盘上将形成大量的空洞,很快磁盘上将无空间可供使用。因此,必须提供一种方法将一个文件占用的多个逻辑块映射到对应的非连续存储的物理块上去,Ext2等类文件系统是用索引节点解决这个问题的,具体实现方法后面再予以介绍。
ext2采用的是混合多级索引表法(其他的还有顺序/链接/散列)对于大文件小文件都适用且支持随机存取,效率比较高。
2.数据块寻址:
逻辑文件:
逻辑文件就是我们用户所看到的文件,他是一个一维数组,以字节为单位。例如,我们在写文 件的时候,系统文件表中会为我们维护一个该文件当前的偏移量offset。
逻辑文件块:
逻辑文件块是文件系统所看到的文件,他是以块为单位的。也就是说,文件系统管理一个文件
是把这个文件划分成连续的逻辑块,以块为基本单位管理。
物理块:
物理块对应着逻辑块,文件在磁盘上存储的基本单位,这才是真是的文件数据块,同一个文件 的所有物理块可能并不连续,块大小为b。逻辑块和物理块大小blocksize是一样大的,他们是 一种映射关系而已。物理块大小影响I/O操作,进行一次I/O操作,最小 传输的数据大小就是 一个物理块的大小。这里物理块和逻辑文件块的概念其实说的是一个东西(文件的那块数据), 只是位于不同的问题空间(或者说分析角度)而已。有点类似内存管理的页框pageframe和页 page,程序在逻辑空间被用户划分成4K为单位的页,并且这些页是连续的,等程序被加 载到内存时候,这些页被分别装入页框里,而这些页框不一定是连续的,因而最后实际存储在 内存的程序也就不一定是连续的。这就是页到内存页框的映射过程;再回到我们的物理块和逻 辑块,这里物理块就对应页框,逻辑块就对应页,连续的逻辑块被映射到可能分散的物理块, 一样的道理。
磁盘扇区:
这个就是真实磁盘硬件的存储单位了,一般一个扇区521B。磁盘寻找数据是寻道,找到扇区, 然后把这个扇区(至少是一个扇区)数据传输到其他存储位置(内存)。那么物理块和磁盘扇 区有什么关系呢?一个物理块当然是扇区的整数倍了。每次磁盘传输的数据块大小是物理块的 大小,而不是一个扇区。也就是说虽然磁盘有自己的划分单位,但是文件系统在此基础上又划 分了一层单位------物理块,以优化文件存储效率,包括时间和空间的优化,在上文已经谈过物 理块大小的选择了。
从文件内的偏移量f导出相应数据块的逻辑块号需要两个步骤:
·从偏移量f导出文件的块号,即偏移量f处的字节所在的块索引。
·把文件的块号转化为相应的逻辑块号。
因为Linux文件不包含任何控制字符,因此,导出文件的第f个字节所在的文件块号是相当容易的:f/b即可得到文件逻辑块号。
但是第f个字节最终要写回磁盘,因此ext2需要提供一种从逻辑块号向物理块号转换的机制,ext2通过i_node节点中的索引地址表来完成这种映射。
structext2_inode {
__u16 i_mode; /* 文件类型和访问权限*/
__u16 i_uid; /* 文件拥有者标识号*/
__u32 i_size; /* 以字节计的文件大小*/
__u32 i_atime; /* 文件的最后一次访问时间*/
__u32 i_ctime; /* 该节点最后被修改时间*/
__u32 i_mtime; /* 文件内容的最后修改时间*/
__u32 i_dtime; /* 文件删除时间*/
__u16 i_gid; /* 文件的用户组标志符*/
__u16 i_links_count; /* 文件的硬链接计数*/
__u32 i_blocks; /* 文件所占块数(每块以512字节计)*/
__u32 i_flags; /* 打开文件的方式*/
union /*特定操作系统的信息*/
__u32i_block[Ext2_N_BLOCKS]; /* 指向数据块的指针数组*/
__u32 i_version; /* 文件的版本号(用于 NFS)*/
__u32 i_file_acl; /*文件访问控制表(已不再使用)*/
__u32 i_dir_acl; /*目录 访问控制表(已不再使用)*/
__u l_i_frag; /* 每块中的片数 */
__u32 i_faddr; /*片的地址 */
union /*特定操作系统信息*/
}
i_node的i_block域是一个有EXT2_N_BLOCKS个元素且包含逻辑块号的数组。在下面的讨论中,我们假定EXT2_N_BLOCKS的默认值为15,如图1所示,这个数组表示一个大型数据结构的初始化部分。正如你从图中所看到的,数组的15个元素有4种不同的类型:
·最初的12个元素产生的逻辑块号与文件最初的12个块对应,即对应的文件块号从0到11。
·索引12中的元素包含一个块的逻辑块号,这个块代表逻辑块号的一个二级数组。这个数组对应的 文件块号从12到b/4+11,这里b是文件系统的块大小(每个逻辑块号占4个字节,因此我们在式 子中用4做除数)。因此,内核必须先用指向一个块的指针访问这个元素,然后,用另一个指向包 含文件最终内容的块的指针访问那个块。
·索引13中的元素包含一个块的逻辑块号,而这个块包含逻辑块号的一个二级数组;这个二级数组的 数组项依次指向三级数组,这个三级数组存放的才是逻辑块号对应的文件块号,范围从b/4+12到 (b/4)2+(b/4)+11。
·最后,索引14中的元素利用了三级间接索引:第四级数组中存放的才是逻辑块号对应的文件块号, 范围从(b/4)2+(b/4)+12到(b/4)3+(b/4)2+(b/4)+11。
eg:读一个文件:
设想我们在读一个文件,使用read(fd,buff,size);
文件逻辑偏移量为f;
物理块大小为b;
每个块的块地址(索引表中的表项)占m个字节;
每个索引块所容纳的块数:bn=b/m;
逻辑块号n=f/b;
块内偏移if=f%b;
第一级索引块:first_index_block
第二级索引块:second_index_block
第三级索引块:third_index_block
那么,
当0<=n<= 11
1.从索引表中读取第n项,得到数据数据块;
2.再使用if读取第f个字节;
当12<=n<= (b/m)+11
1.从索引表读取第12项,得到first_index_block;
2.从first_index_block读取第(n-12)项,得到数据块;
3.再使用if读取第f个字节;
当(b/m)+12<=n<= (b/m)^2 +(b/m)+11
1.从索引表读取第13项,得到first_index_block;
2.从first_index_block读取第(n-[(b/m)+12])/bn项,得到second_index_block;
3.从second_index_block读取第(n-[(b/m)+12])%bn项,得到数据块;
4.再使用if读取第f个字节;
对(n-[(b/m)+12])/bn和(n-[(b/m)+12])%bn的解释:
(n-[(b/m)+12])是除去前面直接和一级索引块 后剩余的块,再把这些剩余的块看成一个逻辑块 文件,那么first_index_block就是以bn为基本单 位的划分,(n-[(b/m)+12])/bn(即第几 个集合块(每个集合块包含bn个块))可得到 second_index_block,(n-[(b/m)+12])%bn 恰好就是second_index_block对应集合块的块内偏移 三级索引的情况类似
当(b/m)^2+(b/m)+12<= n<= (b/m)^3+(b/m)^2 +(b/m)+11
1.从索引表读取第14项,得到first_index_block;
2.从first_index_block读取第(n-[(b/m)^2+(b/m)+12])/bn^2项,得到second_index_block;
3.从second_index_block读取第(n-[(b/m)^2+(b/m)+12])%bn^2/bn项,得third_index_block;
4.从third_index_block读取第(n-[(b/m)^2+(b/m)+12])%bn^2%bn项,得到数据块;
5.再使用if读取第f个字节;
eg:对于写操作
这个会比读更复杂,因为写操作涉及到申请磁盘块和地址回填。而且有不同的实现,有的系统会预分配一些块给空文件,有的系统动态申请(即写时申请)。
设想你新建了一个空文件,大小为0;若是动态申请磁盘块,那么此时索引表项目还未填写,需要根据你写的多少来填写相应的索引表项目,也就是申请磁盘块,然后回填地址项目。你需要计算写的是哪个逻辑块以及回填到哪个表项当中。还是比较复杂的。(这就是分析容易建立难)(这里和内存管理里面的页表申请建立读取释放很相似)
ext2采用的是动态预分配。当申请新的磁盘空间时候,会预先分配8块或者更多,如果文件关闭后申请的块中有没有写的,那么再回收这些块。
补充:
以上都是分析基本原理,实际实现可能比这个会复杂很多,原因是
1.读写不会以字节为单位,是以逻辑块为单位,所以你读写某一个物理块,会有缓冲区,先读写缓冲区(缓冲区一般是物理块的整数倍),然后一次性读写磁盘;见图2
2.逻辑块可能和物理块(逻辑块=2^n*物理块)不是一样大的,你读写一个逻辑块,但是文件系统逻 辑操作是几个物理块。
3.在申请新磁盘块的时候,虽然理论上物理块之间不连续,但是为了提高效率,ext2采用了相应的策略让同一文件的块尽可能连续靠近。
注意这种机制是如何支持小文件的。如果文件需要的数据块小于12,那么两次访问磁盘就可以检索到任何数据:一次是读磁盘索引节点i_block数组的一个元素,另一次是读所需要的数据块。对于大文件来说,可能需要3-4次的磁盘访问才能找到需要的块。实际上,这是一种最坏的估计,因为目录项、缓冲区及页高速缓存都有助于极大地减少实际访问磁盘的次数。
也要注意文件系统的块大小是如何影响寻址机制的,Ext2的块大小是允许调整的,因为大的块大小允许Ext2把更多的逻辑块号存放在一个单独的块中。表9.2显示了对每种块大小和每种寻址方式所存放文件大小的上限。例如,如果块的大小是1024字节,并且文件包含的数据最多为268KB,那么,通过直接映射可以访问文件最初的12KB数据,通过简单的间接映射可以访问剩余的13KB到268KB的数据。对于4096字节的块,两次间接就完全满足了对2GB文件的寻址(2GB是32位体系结构上的Ext2文件系统所允许的最大值)。
表9.2可寻址的文件数据块大小的界限
块大小 |
直接 |
一次间接 |
二次间接 |
三次间接 |
1024 |
12KB |
268KB |
63.55MB |
2GB |
2048 |
24KB |
1.02MB |
513.02MB |
2GB |
4096 |
48KB |
4.04MB |
2GB |
|
版权声明:本文为博主原创文章,未经博主允许不得转载。