前言
一个进程通过文件描述符标识一个打开的文件,进程拿着文件描述符可以在内核中找到目标文件进行读写等操作。这是打开的文件,而没有被打开的文件存储在磁盘中,是如何管理的?操作系统在偌大的磁盘中如何找到想要的文件并打开的?
磁盘
磁盘是计算机的主要存储介质,可以存储大量的二进制文件
磁盘的构造
- 磁盘由多个盘片组成,每个盘片有两面,称为盘面;
- 磁臂带动磁头读取盘面上的信息;
- 每个盘面有个专门负责读取数据的磁头;
磁盘的存储结构
每个盘面由一组磁道组成的同心圆组成;
- 扇区:将盘片分为多个扇形,每个扇形和每个磁道相交产生的区域就被叫做扇区。一个盘面上每个磁道所包含的扇区个数是相同的,同样给每个扇区编号。
- 磁盘的基本读写单位是扇区,扇区大小一般是 512字节(512byte)
扇区是磁盘的最小存储单元。但是如果以扇区来读取数据效率太慢了;
所以文件系统把多个扇区组成一个块(逻辑块),每个块占4kb的字节大小(包含八个扇区)。
每次读写就是按块来读写,提高了整体的读写效率。
注:扇区是磁盘的最小存储单位而不是读取数据的最小单位; 块才是。
CHS寻址
通过 柱面Cylinder —— 磁头Head —— 扇区Sector 进行寻址;
- 找到目标扇区所在的盘面。
- 找到目标扇区所在的磁道。
- 在磁道中确定哪一个扇区。
每个扇区的大小是512K字节,所以内磁道的扇区密度高,外磁道的扇区密度低。
磁盘的抽象存储结构
1:将磁盘想象成一个线性的连续数组,每个元素对应着一个盘面 ;
2:再将某一盘面划分成诺干个大小相同的磁道,于是整个数组的最小元素就变成了磁道;
3:再将每个磁道划分为诺干个大小相同的扇区,于是整个数组的最小元素就变成了一个扇区。
现在整个数组就是磁盘中所有扇区的集合。
每个元素都对应着一个扇区的地址,每一个扇区都有唯一的一个下标映射。
我们想要找某一个扇区,只需要用它在数组中的下标就能找到该扇区的CHS。具体计算方法如下:
假设每个磁道上有100个扇区,每个盘面有100个磁道。现在想找到下标index=50505对应的扇区的CHS地址:
- 根据假设信息,一个盘面有100*100=10000个扇区。
- 盘面位置(H)=index/10000=5,即该扇区在第五盘面。
- 磁道位置( C)=(index%10000)/100=5,即该扇区在第五盘面的第五磁道上。扇区位置(S)=index%100=5,
- 所以该扇区位置在第五盘面的第五磁道的第五个扇区的位置。
LBA地址
上面说了,依靠扇区来读取数据太慢了,有了块之后按照刚刚的抽象方式将磁盘按块划分得到的数组每个元素表示4KB(八个扇区);
每个块的起始地址成为LBA(Logical Block Address)逻辑块地址;其实也就是每个块的第一个扇区地址;
文件系统
一个磁盘几百G,如果按照4KB方式管理也太多了;
采用分治思想,将磁盘划分成一个个区域,每个区域又划分成组来管理
启动块Boot Block存放的是操作系统的核心数据,每当计算机开机都需要先从Boot Block读取数据才能正常启动。
每个Block group里都存着以下信息:
- Super Block(超级块):存放文件系统本身的结构信息。可以说整个文件系统结构就被破坏了。多个组都有 ,但不一定是每个组都有。是为了防止磁盘被刮伤而找不到文件属性。
- GDT,Group Descriptor Table(块组描述符):记录该分组中inode和数据块的使用率
- Block Bitmap(块位图):记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用(用0,1表示)。
- inode Bitmap:每个bit表示一个inode是否空闲可用。
- inode Table:存放文件属性 如 文件大小,所有者,最近修改时间等
- Data blocks 数据区:存放文件内容;
文件的属性就放在一个叫inode的结构体中,文件的内容就放在数据块中。
struct inode
{
int id;
mode_t mode;
size;
.......
int inode_number; // 唯一的inode编号 与文件的映射关系
//存储了当前文件使用的数据块
int dateblock[15];
}
一个文件的所有属性都在inode中,但是没有文件名。
原因:
文件名长度不确定,不利于用一个统一空间的角度看待inode。如果每个文件的inode大小都不一样,不利于读取inode信息
查找一个文件的时候,统一使用的是:inode编号。
目录
目录的data blocks中存放的是:它所包含文件的文件名和对应的inode号
- 一个目录中,可以包含多个文件,但是这些文件的名字不能重。
- 目录也是文件,它也有自己的inode,也有自己的数据块。
总结:
文件名不属于文件的属性,也不存在于inode中!
文件名存在于目录中,而不存在于文件本身。我们通过目录来访问文件名,本质是去目录文件中,通过文件名找到对应的inode编号,然后再访问到文件。
比如说现在通过路径/usr/bin/ls来访问一个文件,其过程为:
- 先在根目录中找到文件名
usr
对应的inode编号
- 访问文件usr,在文件usr中找到文件名bin对应的inode
编号
- 访问文件bin,在文件bin中找到文件名ls对应的inode
编号
- 访问文件ls
文件索引
inode中用来记录数据块的地址是一个Date blocks数组,内部的元素个数是确定的,一般是15个。如果直接指向元素,显然是不够的;
Date blocks中并非所有元素都直接映射一个数据块,会根据实际情况建立二级索引,三级索引:
- 二级索引:每一个元素对应着一张数据块表,表中的内容才是真正的数据块的位置;
- 三级索引:指向的数据块1会指向别的数据块2,数据块2指向的才是存储文件内容;
inode与文件的关系(增删查改)
创建文件
- 创建文件会先在inode bitmap中找到第一个为0(未被使用)的i弄得,置为1;
- 将文件的属性写入到inode bitmap中;
- Date Block 会分配空间给文件,还会将文件名和inode映射关系写到上级目录的Date Block中,且将Block Bitmap中对应位置的0置为1;
删除文件
- 根据文件名和inode的映射关系,找到对应的inode;
- 根据inode找到数据块所对应的inode Bitmap,置0;
- 在Block Bitmap中将对应的位置置0;
文件的删除并不会去清理磁盘上数据块中的内容,只是将对应的位图清0,后续再来的内容进行覆盖就可以。这也是为什么拷贝一个文件比较慢,但是删除一个文件很快的原因。
查找文件
在inode Table中查找到文件对应的struct inode ;
- 通过inode中的dateblock数组找到文件的dateBlock进行访问文件内容;
- 通过inode的其它内容访问文件属性
向文件写入
- 找到映射关系中文件所对应的inode;
- 根据inode中 dateblock数组,找到存放内容的数据块进行数据写入,如果有发生数据块数量变化,应相应在Block Bitmap位图中相应改变;
- 再修改inode中对应的属性信息