目录
一、文件分治
早期,存储文件的设备是磁盘(当下的市场几乎都是SSD),但大家习惯的把它们都称为磁盘,磁盘是用来表示区分内存的存储设备。而在操作系统看来,这个存储设备的结构就是一个线性结构,这一点很重要。
磁盘有一个概念叫扇区,一般是521字节大小,磁盘视其为一个管理单位。
而操作系统做I/O的这个单位大小一般是4KB,换句话说,现在要向磁盘写1个字节大小的数据,其实是一次性写入了4KB,只是有效数据为1个字节,从磁盘读数据也是如此,一次读入是读入了4KB。
操作系统管理这个线性结构,采用分治思想。
通过分治思想,我们要理解文件系统,只需要理解其中一个分组即可。
二、文件分组
文件=属性+内容。Linux中,文件的内容和属性是分开存储的。于是,不难知道这样一点,文件的内容有大有小,而文件的属性却是能确定大小的,比如包括int类型的文件大小等一些其他属性,都是可以确定大小的,于是,可以定义一个结构体专门用来描述文件的属性,这个结构体就叫做inode。提醒一下,因为文件名有长有短,不太好确定大小,所以在Linux中并没有把它定义到文件属性里。
Linux下,一个inode结构体的大小是128Byte。
- Boot Block
启动块,在分区的开头,和备份文件、启动相关,一般开机后,要加载操作系统,而操作系统在哪个分区哪个位置,这个过程就和Boot Block有关。
- inode Table
在inode结构体中,有一个变量叫做inode编号即inode_id,系统通过inode_id标识唯一的文件,而不是文件名。
因此,每一个文件都对应一个inode_id。一个分组能容纳的文件数量是有限的,因此inode结构体的数量也是有限的,inode_id在一个分区内是唯一的,inode Table就用来保存当前分组所有的inode结构体。
- inode Bitmap
inode Table对应的位图结构,用来统计inode的使用情况,位图中比特位的位置和某一个文件对应的inode的位置是一一对应的,位图中比特位为1,代表inode被占用,否则表示可用。
- Block Bitmap
数据块对应的位图结构,位图中的比特位位置和当前data block对应的数据块位置是一一对应位置,一个数据块一般是4KB,如果一个数据块被使用了,则这个块对应的位图结构中的位置就为1。
- GDT(Group Descriptor Table)
磁盘最终被分出了很多很多个组,每一个组的状态是怎么样的,则用GDT来描述。GDT内容对应分组的宏观的属性信息,比如data blocks已经使用多少,inode有多少个,已经被占用了多少个,还剩下多少个。
- Super Block:
保存的是整个文件系统的信息,比如从a地址b地址是第一个分组、从c地址到d地址是第二个分组,比如这个分区有多少个分组,每个分组的使用情况是什么样的,都在这里保存。
为什么Super Block不像Boot Block一样存在每个分区的开头,而要存在分组的开头?Super Block保存在不同的分组里意味着备份,如果某个Super Block损坏,便可以通过拷贝其他分组的Super Block来恢复。
就像Boot Block和开机启动相关,保存在每个分区意味着备份。
Super Block和文件系统相关,保存在每个分组意味着备份。
值得一提的是,并不是每个分组都有Super Block,而是部分分组保存了Super Block来备份。
一个分组是有n个data blocks的,一个文件的内容使用了几个data block,是哪几个data block,这个信息是保存在inode Table中的inode结构体里面的,Linux下把它定义为blocks数组。
关于inode_id的唯一性。
inode_id在整个分区内是唯一的,如果比作Windows,就是一个d盘上不存在相同的inode_id。
一个分区有多个分组,一个分组能存储的文件个数是有限的,于是,可以为这个分组分配一个起始的inode_id、和最多有多少个inode_id,比如一个分区的其中一个分组,这个分组的起始inode_id是1000,这个分组最多可以有1000个inode_id,那么只要是在这个分组里面的inode_id,它的范围就是【1000,1999】。
于是,我们宏观上的操作,在操作系统看来,其实是下面这样的。
创建一个文件:在inode Bitmap的比特位由0置为1,找到其inode table,把该文件对应属性填进去,文件的数据写到block里,在inode中用数组blocks建立映射关系,最后返回inode编号,创建成功,只不过这个inode_id是Bitmap对应的值+当前分组的起始inode_id,比如在Bitmap对应的第500个位置将0设置为1,但是最后返回的inode_id = 500 + 1000(起始inode_id)。
查找一个文件:拿到inode_id后,先和分组的起始inode_id比较,确定它在哪一个分组,再减去起始inode_id,比如1500-1000=500,拿着500在inode Bitmap里面验证比特位为1,再在inode table中找到对应的inode,再根据inode中的blocks数组找到对应的多个data blocks,内容加属性就全找到了。
删除一个文件:删除文件也需要用到inode_id,实际上删除一个文件时,我们只需要找到inode在inode bitmap当中的比特位,把比特位由1置为0就删除了。
所以删除一个文件根本不需要把属性和内容清空,只要把inode bitmap的1置为0,属性就删除了,这个文件也占着数据块,也把block的比特位也置为0。所以把文件删除是能够恢复的,一旦删除只是把bit位清掉了。
如果在Linux中误删除一个文件,还是能恢复的,但前提必须是inode和data block没有被占用
计算机可能有多个磁盘,一个磁盘又可能会有多个分区,一个分区有多个分组,操作系统把对一个分区中文件的管理叫文件系统,同一台计算机上的每个分区的文件系统可以不同,上面介绍的文件系统叫Ext(2),一个文件系统的属性就是分组中的Super Block字段,操作系统对文件系统的管理也遵循先组织、后描述的理念,即有几个分区,就有几个文件系统,有几个文件系统,就相应的有几个对应的对象,再用链表或其他数据结构管理这几个对象,这就是操作系统对文件系统的管理。
可是我们在查找一个文件的时候,往往是用文件名,而不是inode_id?
回答:
- 任何一个文件都在一个目录下,由于操作系统不允许同一目录下有同名文件,所有这些文件并没有重复的文件名。
- 目录是一个文件,也有自己的inode,有对应自己的data block,目录的数据块存放的是当前目录下的文件名和inode_id的映射关系,所以inode结构体并不需要保存文件名,这就是为什么文件名不是inode变量之一的原因。
- 我们在一个目录下新增一个文件,要向当前目录的内容里去写文件名和inode的映射关系,所以这个目录文件必须得有写入权限。
- 罗列当前的文件,要有读权限。因为想读的时候必须得去访问数据块中存储的映射关系,所以要有读权限,读完以后便可以拿到文件的inode_id。