文章目录
- 磁盘构造
- 磁盘抽象化
- 磁盘的寻址方式
- 磁盘控制器
- 磁盘数据传输
- 文件系统
- Inode
- 数据块(Data Blocks)
- 超级块(SuperBlock)
- 块组描述符(Group Descriptor)
磁盘构造
磁盘内部构造由磁头臂,磁头,主轴,盘片,盘面,磁道,柱面,扇区构成;
- 磁头臂:控制磁头的移动,可以精确地定位到磁盘上的特定磁道;
- 磁头:负责读取和写入数据;在读取数据时,磁头会检测盘片表面的磁性变化;在写入数据时,磁头会改变盘片表面的磁性,以存储数据;
- 主轴:带有马达,使盘片高速旋转;这种旋转允许磁头访问盘片上的任意位置;
- 盘片:数据存储的介质,通常有多个盘片叠加在一起,每个盘片有两个盘面;
- 盘面:盘片的每一面,用于存储数据;每个盘面都配备有对应的磁头;
- 磁道:盘面上的同心圆,数据以磁道为单位进行组织;每个磁道可以进一步被分割成多个扇区;
- 柱面:由所有盘面上相同磁道号的磁道组成;设想如果磁盘的多个盘片沿主轴方向穿透,形成的圆柱形结构即为柱面;
- 扇区:磁道进一步划分的单位,是磁盘存储数据的最小物理单元;每个扇区通常存储
512
字节或更多的数据;
磁盘抽象化
磁带设备的存储最终数据将被存储到磁带上;
将磁带拉直可以将其看成连续不断的空间;
将磁盘盘面进行抽象化;
由于盘片的高速转动+磁头臂带动磁头的移动可以将其看成是一个螺旋的运动;
螺旋化后拉直为一个以扇区为最小单位的连续不断的空间;
与磁带不同,磁带的访问为顺序访问,磁盘的访问通过盘片的高速旋转+磁头臂带动磁头可以进行随机访问;
磁盘的寻址方式
-
CHS(Cylinder-Head-Sector)寻址
CHS寻址方式使用柱面号,磁头号和扇区号来定位磁盘上的数据;
每个地址由三个部分组成:柱面号( C ),磁头号( H )和扇区号( S );
- 柱面号( C ):表示磁盘上的柱面,柱面是所有盘片上相同半径的磁道的集合(从0开始编号);
- 磁头号( H ):表示盘片上的磁头,磁头用于读取和写入数据(从0开始编号);
- 扇区号( S ):表示磁道上的扇区,扇区是数据存储的最小单位(从1开始编号);
-
LBA(Logical Block Addressing)寻址
LBA寻址方式使用逻辑块地址定位磁盘上的数据;
每个逻辑块地址是一个唯一的整数,表示磁盘上的一个逻辑块(这里指扇区);
- 逻辑块地址(LBA):表示磁盘上的一个逻辑块,逻辑块是数据存储的基本单位;
- LBA寻址方式将整个磁盘视为一个连续的逻辑块数组,每个逻辑块都有一个唯一的地址;
因物理原因, LBA(Logical Block Addressing) 寻址最终以算法转换为 CHS(Cylinder-Head-Sector) 寻址;
-
LBA寻址转换为CHS寻址公式
-
柱面号( C )
C = LBA / ( H * S );
-
磁头号( H )
H = ( LBA / S ) % H;
-
扇区号( S )
S = ( LBA % S ) + 1; #CHS中扇区号从1开始计算
-
例:
- 磁盘有3盘片(6盘面);
- 每个盘面有20000个扇区;
- 每个盘面有50个磁道;
- 每个磁道有400个扇区;
- 需要访问的扇区编号(LBA)为28888;
柱面号的计算:
28888 / ( 50 * 400 ) = 1
;相对扇区计算:
28888 % ( 50 * 400 ) = 8888
;磁道编号计算:
8888 / 400 = 22
;磁道上相对扇区编号:
8888 % 400 = 88
;结果:
- 盘面编号:
1
; - 磁道编号:
22
; - 扇区编号:
89
(从1
开始编号);
-
CHS寻址方式逆向得到LBA编号;
磁盘控制器
磁盘硬件中存在磁盘控制器(物理硬件);
- 磁盘控制器的功能:
- 数据传输
- 执行命令
- 错误检测和纠正
- 缓存管理
在磁盘控制器中存在一系列的寄存器(硬件):命令寄存器,数据寄存器,状态寄存器,地址寄存器等;
-
命令寄存器
用于存储来自计算机的操作命令;
-
数据寄存器
用于暂存从计算机写入磁盘的数据,或者从磁盘读取的数据;
-
状态寄存器
用于指示当前磁盘的状态,以及最近一次操作的结果;
-
地址寄存器
用于指定数据在磁盘上的位置,如扇区号或磁道号;
-
…
同时在磁盘硬件中为用户提供了一系列的 接口/串口 ;
接口/串口 作为驱动程序与磁盘控制器之间的桥梁,操作系统将通过磁盘接口(接口/串口)间接调用磁盘控制器从而操作磁盘;
磁盘数据传输
计算机中存在一种允许外部设备直接与系统内存进行数据传输的硬件组件DMA;
DMA(Direct Memory Access,直接内存访问);
DMA在进行数据传输时无需经过CPU因此可以减少CPU负担;
假设用户请求创建一个名为newfile.txt
新文件并写入数据:
-
用户请求
用户请求或进程调用
open("newfile.txt", O_CREAT | O_WRONLY)
; -
系统调用处理
CPU
将系统调用转发给OS
;OS
内核文件系统模块接收请求; -
文件系统操作
文件系统将维护对应的结构体并为其分配数据块;
-
数据写入
用户或应用程序调用
write
进行数据写入;文件系统写入分配的数据块;
-
DMA配置
OS
配置DMA控制器设置源地址和目标地址并启动DMA传输; -
驱动程序操作
驱动程序将写入命令和数据块地址发送给磁盘控制器;
驱动程序处理终端和状态信息;
-
磁盘控制器操作
磁盘控制器接收写入命令并解析;
磁盘控制器通过DMA从内存读取数据并写入磁盘扇区;
磁盘控制器报告状态;
文件系统
操作系统通常无法直接管理巨大的磁盘空间,因此需要对磁盘进行分区(类比Windows中对C,D,E,F盘的分区);
分区的方式一般通过操作系统维护其struct
结构体即可;
struct partion{int start;int end;//...
}
而实际每个分区的大小也会比较大,需要继续对分区进行区分管理;
一般情况下每个分区都由以下组成:
-
启动块(Boot Block)
一般情况下BootBlock为引导块,只存在于系统盘当中;
但有些情况也会存在其他分区当中;
存在该块的分区一般被称为引导分区;
-
块组(Block group)
每个分区由若干个块组组成,由
Blockgroup 0
至Blockgroup n
;块组是文件系统重最基本的管理单元,每个块组包含多个数据块,Inode表和其他元数据;
(该图为演示,与内核空间映像存在差异)
块组中所包含的多个数据块即为文件系统;
每个块组通常包含以下几个部分:
-
超级块(Superblock):
- 存储文件系统的全局元数据;
-
块组描述符表(Group Descriptor Table):
- 存储每个块组的描述符,描述块组的元数据位置和状态;
-
块位图(Block Bitmap):
- 跟踪块组中数据块的使用情况;
-
Inode位图(Inode Bitmap):
- 跟踪块组中Inode的使用情况;
-
Inode表(Inode Table):
- 存储文件和目录的元数据;
- Inode表中将存若干个Inode;
- Inode用于存储文件基本属性(如文件类型,文件权限,拥有者,所属组等);
-
数据块(Data Blocks):
- 存储实际的文件和目录内容;
假设存在一个文件系统,其应包含以下块组:
struct superblock {int fs_size; // 文件系统的大小int block_size; // 块大小int free_blocks; // 空闲块数量int free_inodes; // 空闲Inode数量// 其他文件系统元数据
};struct group_descriptor {int block_bitmap; // 块位图的起始位置int inode_bitmap; // Inode位图的起始位置int inode_table; // Inode表的起始位置int free_blocks; // 块组中的空闲块数量int free_inodes; // 块组中的空闲Inode数量// 其他块组元数据
};struct block_group {struct superblock sb; // 超级块struct group_descriptor gd; // 块组描述符char block_bitmap[128]; // 块位图char inode_bitmap[128]; // Inode位图struct inode inodes[1024]; // Inode表char data_blocks[4096][4096]; // 数据块
};struct inode {int file_type; // 文件类型int permissions; // 文件权限int size; // 文件大小int block_pointers[12]; // 数据块指针// 其他文件元数据
};
磁盘中最小的存储单位为扇区,而在文件系统中最小的单位为 文件系统块 ;
存储数据的数据块中存在若干个 文件系统块 ;
文件系统块的大小必须 >=
扇区大小,一般最常用为4kb
;
-
在数据的存储中,一般只有最后一个文件系统块会产生空间浪费:
假设一个文件的的大小为
5kb
,将会为其分配两个文件系统块用于存储,其中第二个块为最后一个块将浪费3kb
的空间;
Inode
在Linux中,文件的内容与文件的属性构成一个完整的文件;
其中文件的内容与文件的属性是分开管理的;
- 文件的内容管理在数据块(Data Blocks)中
- 文件的属性管理在Inode表中的Inode中;
Inode可以看作是一个结构体,其包含一个文件的所有属性:
struct inode {uint16_t i_mode; // 文件类型和权限uint16_t i_uid; // 文件所有者的用户IDuint32_t i_size; // 文件大小(字节)uint32_t i_atime; // 最后访问时间uint32_t i_ctime; // 创建时间uint32_t i_mtime; // 最后修改时间uint32_t i_dtime; // 删除时间uint16_t i_gid; // 文件所属组的组IDuint16_t i_links_count; // 链接计数uint32_t i_blocks; // 文件占用的块数uint32_t i_flags; // 文件标志uint32_t i_block[15]; // 数据块指针// 其他文件系统特定的信息
};
其中每个Inode都在对应的文件系统中存在一个唯一编号(不同文件系统之间的Inode编号可以重复);
在Linux当中可以采用ls -li
获取其Inode编号;
$ ls -li
total 0
2360117 -rw-rw-r-- 1 _XXX _XXX 0 Jun 1 18:02 newfile1.c
2360116 -rw-rw-r-- 1 _XXX _XXX 0 Jun 1 18:00 newfile.c
其中在每个块组中都存在一个Inode位图(Inode Bitmap)来跟踪该文件系统中的Inode的使用情况(利用位图可以大大节省跟踪时所使用的空间);
当需要访问一个文件时文件系统将根据Inode中的编号查找文件的目录和数据块指针从而访问文件的实际数据;
-
文件的文件名不存在于Inode之中,而是单独存放在目录的数据结构当中
每个目录在文件系统当中被视为一个特殊的文件,其自身拥有一个Inode与数据块;
目录中的数据块保存了文件名与对应Inode编号的映射关系,这些映射关系被称为目录条目;
-
目录条目的结构演示
struct directory_entry {uint32_t inode_number; // Inode编号char file_name[256]; // 文件名 };
当通过文件名访问文件时文件系统将会执行以下步骤:
假设文件路径为
/home/user/file.txt
;-
根目录(/)
查找根目录的Inode(通常为Inode2);
在根目录的数据块中查找
home
目录的目录条并获取其Inode编号; -
home
目录查找
home
目录的Inode;在
home
目录的数据块中,查找user
目录的目录条目并获取其Inode编号; -
user
目录查找
user
目录的Inode;在
user
目录的数据块中,查找file.txt
的目录条目并获取其Inode编号; -
file.txt
文件查找
file.txt
的Inode;通过Inode中的数据块指针访问其文件的实际数据;
-
-
数据块(Data Blocks)
数据块中存在若干个文件系统块;
在文件系统中的块位图(Bolck Bitmap)即为跟踪当前文件系统中数据块内各个文件系统块的使用情况;
在Inode中存在一组数据块指针数组,一般情况这个指针数组的大小为15;
这个指针数组中包含了三种数据块指针:
- 直接指针(Direct Pointers):
- 通常有12个直接指针,每个直接指针直接指向一个数据块(下标0-11)。
- 直接指针用于存储文件的前12个数据块。
- 单重间接指针(Single Indirect Pointer):
- 指向一个间接块,间接块中包含指向数据块的指针(下标12)。
- 单重间接指针用于存储更多的数据块。
- 双重间接指针(Double Indirect Pointer):
- 指向一个双重间接块,双重间接块中包含指向间接块的指针,间接块中再包含指向数据块的指针(下标13)。
- 双重间接指针用于存储更多的数据块。
- 三重间接指针(Triple Indirect Pointer):
- 指向一个三重间接块,三重间接块中包含指向双重间接块的指针,双重间接块中再包含指向间接块的指针,间接块中再包含指向数据块的指针(下标14)。
- 三重间接指针用于存储更多的数据块。
可以将其中的数据块指针数据看成是一种树状结构;
删除一个文件的本质是删除其对应文件系统中的Block Bitmap以及相关映射删除(所以理论上数据的删除可以被修复),当需要再此使用时只需要对文件系统块对应的内容进行覆盖即可;
超级块(SuperBlock)
超级块中包含了当前文件系统中的全局信息和元数据(文件系统大小,块大小,空闲块数量等);
通常超级块位于文件系统的起始位置,每个块组中都可以存在一个超级块以便于在超级块损坏时可以进行对超级块的恢复;
超级块通常存在以下内容:
- 文件系统元数据:
- 超级块包含文件系统的全局元数据,如文件系统的大小、块大小、空闲块数量、空闲Inode数量等。
- 这些元数据用于文件系统的管理和操作。
- 文件系统状态:
- 超级块包含文件系统的状态信息,如文件系统是否已挂载、是否已清除等。
- 这些状态信息用于文件系统的维护和恢复。
- 文件系统配置:
- 超级块包含文件系统的配置参数,如块大小、Inode大小、块组大小等。
- 这些配置参数用于文件系统的初始化和操作。
超级块的结构示例如下:
struct superblock {uint32_t s_inodes_count; // 文件系统中的Inode总数uint32_t s_blocks_count; // 文件系统中的块总数uint32_t s_r_blocks_count; // 保留块总数uint32_t s_free_blocks_count; // 空闲块总数uint32_t s_free_inodes_count; // 空闲Inode总数uint32_t s_first_data_block; // 第一个数据块的块号uint32_t s_log_block_size; // 块大小(以2的幂次表示)uint32_t s_log_frag_size; // 碎片大小(以2的幂次表示)uint32_t s_blocks_per_group; // 每个块组的块数uint32_t s_frags_per_group; // 每个块组的碎片数uint32_t s_inodes_per_group; // 每个块组的Inode数//...// 其他文件系统特定的信息
};
块组描述符(Group Descriptor)
块组描述符用于描述每个块组的元数据位置和状态;
其一般包含了所有块组的描述符,其中每个块组都有一个对应的块组描述符;
-
块组描述符的作用
-
描述块组元数据的位置
块组描述符一般包含组内重要的元数据(块位图,Inode位图,Inode表)的起始位置;
通过快描述符使得文件系统可以快速定位组内元数据;
-
跟踪块组的状态
块组描述符包含块组内空闲块和空闲Inode的数量;
这些信息一般用于文件系统的管理和优化;
-
块组描述符的结构示例:
struct group_descriptor {uint32_t block_bitmap; // 块位图的起始位置uint32_t inode_bitmap; // Inode位图的起始位置uint32_t inode_table; // Inode表的起始位置uint16_t free_blocks_count; // 块组中的空闲块数量uint16_t free_inodes_count; // 块组中的空闲Inode数量uint16_t used_dirs_count; // 块组中的已使用目录数量// 其他块组元数据
};