磁盘与文件系统原理

    磁盘寻址方式:操作系统使用的CHS(柱面、磁头、扇区)并非对应于磁盘的物理柱面、磁头和扇区。现代磁盘每磁道扇区数不同,在对外提供访问接口时,硬盘支持LBA方式和CHS方式的两种逻辑地址。其中,提供CHS方式的逻辑地址是为了与之前使用CHS地址的软件兼容。硬盘控制器接收到逻辑地址后再转化为物理地址访问物理空间。


    在CHS寻址方式(逻辑地址)下,磁盘的最小分区单位为柱面(通常,每柱面为 255 磁头,每磁头 63 扇区,则每柱面空间为 255*63*512 bytes,可以用 fdisk -l 命令查看)。其中0柱面单独存在,其0磁头1扇区即主引导扇区,包含MBR和分区表(0 柱面其余扇区全部浪费?)。硬盘分区从1柱面开始(可以用 fdisk -l 命令查看)。

    每个分区要包含一个 boot sector 用于存储引导程序,每个分区都存储一个单独的文件系统。boot sector 占用固定的 1 kb 空间,文件系统的第一部分 superblock 也是固定占用 1 kb 空间。此为背景。

    每个分区首先划分为大小固定的 block通常为 1 kb, 2 kb, 4kb 等,编号从 0 开始),从这里开始 block 为最小划分单位。当 block 大小为 1 kb 时,boot sector 占用 block 0,文件系统从 block 1 开始,具体来说,block 1 是文件系统的 superblock。当 block 大小为 2 kb 以上时文件系统从 block 0 开始,只不过 block 0 的第一个 1 kb 空间分给 boot sector,第二个 1 kb 空间才是文件系统 superblock,block 0 若有剩余空间(block 大于 2 kb 时)则不使用。单说 boot sector,永远是分区的第一个 1 kb 空间。单说 superblock,永远是分区的第二个 1kb 空间。这样就可以根据分区位置直接找到这两块数据,又可以根据 superblock 的内容解析整个分区(?)。(dumpe2fs 分区名 命令查看到的上部分信息就是 superblock 的内容?)

    文件系统拥有的 block 首先分组为数个 block group,每个 block group 中包含不同功能的多个区域,分别分配不同数量的 block,这些区域可能包括 superblock(每个文件系统只有一个 superblock,在第一个 group 的第一个 block中,所以可以说是整个文件系统的第一部分), group discriptors, block bitmap, inode bitmap, inode table, data block 等(可以用 dumpe2fs 分区名(如 /dev/sda1)命令查看)

    无论在磁盘中添加一个文件还是目录,都要分配一个 inode 和至少一个 block。对于文件,inode 记录文件元数据和文件占用的 block 的号码,inode 中可以记录 15 个 block 号码,前 12 个 block 直接存储文件内容,第 13 个 block 为间接指向(block 中记录的是 block 的号码,再指向的 block 才存储文件内容),第 14 个 block 为双间接,第 15 个 block 为三间接。因此一个文件的最大大小为

    12 * blockSize + 1 * ( blockSize / blockPointerSize ) * blockSize + 1 * ( blockSize / blockPointerSize ) * ( blockSize / blockPointerSize) * blockSize + 1 * ( blockSize / blockPointerSize ) * ( blockSize / blockPointerSize ) * ( blockSize / blockPointerSize ) * blockSize

    目录与文件的区别在于,目录占用的 block 存储的是目录下文件/目录的名称与其 inode 号码的映射表。

    本质上讲,操作系统是基于 cpu 和内存而存在的一组进程,硬盘只是一个可有可无的外设。操作系统管理磁盘这个外设,向其他进程提供访问磁盘的接口,具体方式是进程向操作系统提交 路径+文件名 字符串。以读取 /etc/passwd 为例说明过程,操作系统接收到该字符串后进行分析,读取到第一个 '/',判断起始目录为根目录,读取到第二个 '/',判断下一个目录名称为 "etc",继续读到字符串结束,判断文件名称为 "passwd"。根目录的 inode 号码固定为 2,于是操作系统首先从磁盘根目录所在分区读取 2 号 inode 到内存,提取其中记录的 block 号码;然后从磁盘读取对应的 block 到内存,查找到其中名称为 “etc” 的目录的 inode 号码;然后从磁盘读取对应的 inode 到内存,提取其中记录的 block 号码;然后从磁盘读取对应的 block 到内存,查找其中名称为 “passwd” 的文件的 inode 号码;然后从磁盘读取对应的 inode 到内存,提取其中记录的 block 号码;然后从磁盘读取对应的 block 到内存,至此,文件内容成功读入内存。基本模式就是,从磁盘读取文件/目录的 inode,提取 block 号码,从磁盘读取 block。

    可见,文件系统是存在于磁盘这个外设中的,进一步,每个分区应该都存储维护着一个完整的独立的树状文件系统,每个分区都以 2 号 inode 作为这个文件系统的根目录的 inode。CPU + 内存只需要知道文件系统是树状结构这一事实(决定了访问文件系统的逻辑是层层搜索),再知道文件系统根目录的 inode 位置,即可根据 路径+文件名 字符串访问文件系统中的任一文件。

    操作系统能够访问一个文件系统时,这个文件系统首先要挂载,文件系统的superblock有一个属性 valid bit 表示的就是该文件系统是否已经被挂载。所谓的挂载,其实现可能是这样的:内存中维护一个挂载点(目录树的节点)与实际文件系统根目录inode的映射表,当挂载一个文件系统时,在这个映射表中添加相应的条目。比如根目录(/)映射到启动盘第一个分区上的文件系统的根目录的 inode,/var 映射到另一个分区的文件系统的根目录的 inode等。操作系统根据 路径+文件名 访问磁盘时,首先在映射表中通过最大匹配确定第一个要访问的 inode 再继续层层搜索。这也说明文件系统虽然可以有多个,但操作系统呈现的目录树是唯一的,实际是多个文件系统嫁接生成的。(可以挂载一个文件系统用 ls -di 查看挂载点的 inode 号码,当文件系统是基于磁盘时,inode 号码为 2,当文件系统是基于U盘时,inode 号码为1,总之应当就是文件系统根目录的 inode)

    所以对操作系统来说,根目录跟任何其他一个挂载点实际上没有本质区别,只是在映射表中不同条目的主键而已,所谓唯一的目录树其实也只是一种假象,本质上还是相互独立的多个文件系统,操作系统只是能根据 路径+文件名 字符串的前缀自动选择不同的文件系统而已,是一种对多文件系统的管理功能,是一种管理外设的功能


    鸟哥书中有这么一句话:開機過程中僅有根目錄會被掛載, 其他分割槽則是在開機完成之後才會持續的進行掛載的行為。

    这句话里的根目录指的应当是挂载点为根目录的文件系统(分区)。之所以只能读取这个文件系统中的文件,应该是因为引导加载程序为了简化逻辑,只在目录到文件系统根目录 inode 的映射表中维护一条根目录("/")的映射。因此有些文件必须放在这个文件系统中,包括 /etc, /bin, /sbin, /lib, /dev 这些在建立其他挂载映射条目之前需要用到的目录。


关于开机流程:

    MBR 和 boot sector 中都可以存储引导程序,例如 grub。加电后机器自动从 BIOS 开始运行(开机后 CPU 从内存空间的 ffff0 地址读取第一条指令,事实上这个地址指向的是 BIOS),BIOS 将 MBR 加载到内存 7c00 地址处并跳转执行 MBR,MBR 可能直接将操作系统加载入内存并跳转执行(那活动分区的意义是什么?),或将某个分区的 boot sector 加载入内存并跳转执行,再由这个 boot sector 加载操作系统。

    另一种说法,BIOS 跳转到 MBR 后,MBR 并不直接加载操作系统,而是从分区表中检测活动分区(每个磁盘只能有一个活动分区,具体来说,分区表记录的四个分区只能有一个标记为活动分区),然后将活动分区的 boot sector 加载到内存并跳转。


    查看grub的配置文件(/boot/grub/grub.conf),可以看到加载内核是根据文件系统的路径进行加载的,因此boot loader是可以读取并操作文件系统的。


一些细节:

    用 fdisk 查看分区的磁柱范围,显示两个值 Start 和 End,分区实际包含的磁柱是从 Start 到 End-1,并不包含 End 磁柱。

    格式化后实际可以存储的文件总大小是小于物理空间大小的。一方面格式化建立文件系统本身要写入一些数据,另一方面任何文件占用的实际空间都是block的整数倍从而可能造成空间的浪费。


你可能感兴趣的:(磁盘与文件系统原理)