本文主要是从应用角度出发,分别阐述操作系统接口,计算机语言,文件系统等背后的一些知识,规范,原理,设计思想,应用法门,让初学者编码有一个整体的,全局的认识,有一个物理的视角,找到自己的起点。

文件系统

文件系统就是用来对文件进行增删改查的控制系统,不同类型的文件系统对应着不同的管理策略,但是,无论何种策略都需要解决两个最基本的问题:

  • 文件存储。
  • 目录访问。

因此文件系统需要在磁盘记录一些额外的信息,所以格式化以后,磁盘容量会小于实际的容量。格式化的过程就是在磁盘上“安装”文件系统的过程,下面以EXT2和FAT32为例来说说文件系统是如何工作的。

EXT

ext系列是linux下面主打的文件系统,它以Block为容量单位,以Inode来抽象文件(目录也是文件)。一个硬盘分成多个Block Group,每个Group里面分别存储Inode和具体的文件数据,如下图所示:

 

(图片来源于 science.unitn.it)

  • Super Block: 又称超级块,是文件系统的头部--如同视频和图像文件都有一个独一无二的头部一样:它包含文件系统的标识(magic代码0XEF53),Block的大小(1k,2K,4K或者8K),Inode数量,Block数量,Inode大小等等文件系统的元数据。一旦它出现损坏,整个文件系统就将无法识别,那么这个文件系统就损坏了,里面的数据也就丢失了。
  • Group Descriptors: 全称 Block Group Descriptor Table简称GDT,包含所有的Group结构,用来描述每个Group里面Inode和Block的位置,空闲数量等信息。
  • Block/Inode Bitmap: 用一个bit位来标示是否空闲,如:bit15代表对应的第15个Block/Inode,0表示空闲,1则标示已被占用了。
  • Inode Table: 每个Inode代表一个文件,它存储着该文件的属性和位置,通过它就可以访问对应的文件。每一个Block Group都有相同数量的Inode,因此,格式化后,所能支持的最大文件个数和每个文件的最大容量都被决定了--Inode的数量就是所能支持的最大文件数量(包含目录,目录也被抽象为文件),Inode的支持的最大寻址空间也就是文件的最大长度。
  • Data Blocks:文件数据的地址,是一个长度为15的数组。

从上面图中可以看到,每个Group Block的结构都是一模一样,除了Data Blocks外,内容也都是一样的吗?
因为Super Block和 GDT是非常重要的,所以,每个Block Group都有一份备份,因此他们的数据是一样的,由于备份会浪费空间,新版本的EXT系统不再要求每个Group都进行备份了,格式化的时候,可以选择备份策略。除了这两项外,其它的内容就跟该Group的存储的数据有关了。

那么划分Block Group的好处有哪些呢?
主要是为了防止文件存储碎片化。在存储的时候,预先保留多余空间,尽量把文件放到一个Group里面,因此文件不是互相连续存放的:比如先后创建2个文件,第一个文件放在213 Block,第二个文件可能就510 Block,预留一部分空间给文件扩展用。当然了,对超大的文件,是会跨Group存放的。 通过精心的Block Group划分,再加上Linux灵活的文件分配策略,EXT文件系统在正常使用情况下“碎片率”是比较低的。

目录结构

目录表没有放到Inode Table里面,而是放到Data Blocks上了,每个目录节点都对应一个Inode,它的数据区域(Data Blocks)就是它的目录表。每个目录表项包括:文件名,类型和Inode号--通过Inode号又能找到下一级目录表,这样就构成了一个目录链表结构。

类Unix系统都有一个“根目录”,是路径的起始点,系统就是从根目录开始来遍历目录链表的,它也相当于链表的“根节点”,因此它的位置必需是固定的:Linux系统的“根目录”Inode节点号是2。

下面以一个小容量的ext2系统为例来遍历下目录。
首先查找“根节点”,它的内容如下:

  

BLOCKS: (0):204 表示:该Inode只占用了一个Block(编号从0开始起),内容在204号Block上,我们现在读取Inode 2 里面的内容(也就是204号Block):

 

根据目录表项的定义解析二进制数据,就可以得到根目录的目录表:

Name Inode Type
. 0x02 目录
.. 0x02 目录
lost+found 0x0b 目录
home 0x501 目录
large.img 0x0c 文件

 

 

(目录表项的结构)

接下来,遍历下一级目录。
如果我们要访问"/home"目录,通过查找表,会发现它的Inode号是1281, 然后,读取Inode 1281的内容,再根据BLOCKS项的信息,读取它的数据块,又可以得到'/home'的目录表--就如同前面读取“根节点”一样,只是Inode 2变成了Inode 1281。这样反复递归,就可以一级一级地查询到最终的目录或者文件。

以上就是目录存储和查找的过程,目录表存储在Inode的数据区域,查找就是从根节点开始,反复递归,直到目的路径。

为什么“根目录”的Inode号是2,而不是1或者0呢?
Inode 0 表示该Inode 不存在,类似C语言的空指针,这个是为了编码方便。 Inode 1是用存储坏块的, 所以,“根目录”就是Inode 2了,当然了,不同操作系统的具体实现估计会有差异。

读取文件内容

我们通过目录结构找到文件对应的Inode节点后,就可以读取它的内容了。Inode节点使用一个数组来存储文件所占用的Block号(BLOCKS项的内容),  数组的长度为15,Block号用4个字节来表示。数组的前12位是立即寻址,第13,14和15位则是间接寻址。
下面以Block大小为1024个字节为例,来说明这个寻址过程。

  

如上图所示:

  • 前12位是直接寻址,数组对应的内容就是文件的前12块。
  • 第13位是“一重间接寻址”,它的内容不是文件内容,而是文件的块地址--类似于C语言的指针,我们先找到218块,再根据218块上的256个地址(1K/4)来读取文件内容,也就是说这个地址的容量是256块,范围是[13 ~ 268]。如图中所示,第13块的内容在525块上,1164块则是文件的第268块--这256块不一定是连续存放的。
  • 14位是“二重间接寻址”,意味着连续2遍寻址,才能读取到文件内容,因此它支持的容量是(256*256=65536)块,可见它相当于C语言里面的“指针的指针”。我们先找到236块,得到它上面的256个地址,再依次读取这256个地址得到最终的65536个块地址。从图中可以看到,236块上的第一个块地址是220,220块上的第一个块地址是1165--也就是说该文件逻辑上的第269块存储在1165号块上,依次方法遍历,可以读取该文件的65536块的内容。
  • 15位是“三重间接寻址”,想当于C语言里的“指针的指针的指针”,它支持寻址(256*256*256=16777216)个块,就不肇叙了。    

分析一下:文件最多可占用(12+256+256*256+256*256*256 =16843020)个Block,如果Block的大小是1K,那么文件大小的上限约是 16G。对于小于13个Block的文件是直接寻址,访问速度快。间接寻址,主要是为了增大文件的容量,同时也能快速读取前12个Block的内容。   

用户评论:
发表评论: (限500字)

注册 忘记密码 登录