一、磁盘
1.磁盘的物理结构
2.磁盘的存储结构
- 盘片:是机械硬盘存储数据的主要介质,一般由铝合金或玻璃等材料制成,表面涂有一层磁性材料。数据通过磁头在盘片的磁性涂层上进行磁化来记录,磁化的不同方向代表二进制的 0 和 1。
- 盘面:机械硬盘通常包含多个盘片,每个盘片有上下两个盘面,数据可以存储在这些盘面上。盘面是磁盘存储数据的实际表面,磁头通过在盘面上移动来读写数据。
- 盘面编号:在机械硬盘中,为了方便对多个盘面进行管理和区分,会对每个盘面进行编号。一般来说,最上面的盘面编号为 0,然后依次向下递增。例如,一个有 2 个盘片的机械硬盘,就会有 0、1、2、3 共 4 个盘面。
- 磁道:盘片上的圆形轨迹,每个盘片通常有大量的磁道,从盘片的中心向外边缘辐射。磁道是数据存储的基本单位之一,数据按顺序存储在磁道上。
- 磁道编号:磁道是盘面上的圆形轨迹,从盘片的中心向外边缘分布。为了准确地定位和访问数据,每个磁道都有一个唯一的编号。通常,最靠近盘片中心的磁道编号为 0,然后随着磁道从内向外依次递增。磁道编号是操作系统和磁盘控制器用于定位数据的重要依据,通过指定磁道编号,磁头可以快速地移动到相应的磁道上进行数据读写操作。
- 扇区:是磁道的进一步划分,每个磁道被划分为若干个扇区。扇区是磁盘进行数据读写的最小单位,通常每个扇区的大小为 512 字节或 4096 字节等。
- 扇区编号:扇区编号是从磁道的某个起始位置开始,按顺时针方向依次递增。不同的磁盘格式和文件系统可能对扇区编号的具体规则有所不同,但一般都是连续编号。扇区编号与磁道编号、盘面编号等一起,构成了磁盘数据存储的完整地址信息,操作系统和应用程序通过这些编号来准确地定位和读写磁盘上的数据。
- 柱面:由不同盘片上相同位置的磁道组成,在进行数据读写时,磁头可以在同一柱面上的不同盘片的磁道之间快速切换,提高数据访问效率。
- 柱面编号:柱面编号与磁道编号相对应,因为同一柱面上的磁道在不同盘面上的位置是相同的,所以柱面编号实际上也是从内向外依次递增,最内侧的柱面编号为 0。在进行数据读写时,操作系统和磁盘控制器可以通过指定柱面编号,同时对多个盘面上的同一柱面的磁道进行操作,提高数据传输效率。例如,在读取大量连续数据时,可能会先在一个柱面的所有磁道上读取数据,然后再移动到下一个柱面,这样可以减少磁头的移动距离和寻道时间。
- 磁头:负责在盘片上进行数据的读写操作,通过电磁感应原理,将电信号转换为磁信号记录在盘片上,或者将盘片上的磁信号转换为电信号读取出来。
- 传动臂:传动臂是机械硬盘中的一个重要部件,它的一端连接着磁头,另一端由电机和控制电路驱动。传动臂的磁头是共进退的。
在虚拟机上磁盘位置:/dev/sda
在云服务器上磁盘位置:/dev/vda
命令:fdisk -l /dev/sda,查看磁盘信息,需要root权限
- 磁盘基本信息:
- 磁盘
/dev/sda
大小为 21.5GB ,总字节数是 2147483648 ,总扇区数 41943040 。- 扇区大小逻辑和物理均为 512 字节 ,I/O 最小和最佳大小也都是 512 字节 。
- 磁盘标签类型为
dos
,磁盘标识符为0x000cecdc
。- 分区信息:
/dev/sda1
分区:是启动分区(有*
标识),起始扇区为 2048 ,结束扇区为 616647 ,块数 307200 ,分区 ID 为 83 ,系统类型为Linux
。/dev/sda2
分区:起始扇区为 616648 ,结束扇区为 4810751 ,块数 2097152 ,分区 ID 为 82 ,系统类型为Linux swap / Solaris
(交换分区)。/dev/sda3
分区:起始扇区为 4810752 ,结束扇区为 41943039 ,块数 18566144 ,分区 ID 为 83 ,系统类型为Linux
。
3.CHS寻址
文件=内容+属性,都是数据,无非就是占用几个扇区的事,如果定位某个扇区,就能定位某些数据。
通过柱面(cylinder)-> 磁头(head) -> 扇区(sector),显然就可以定位数据了,这种数据定位的方式称之为CHS寻址方式,这是物理结构上的寻址。
CHS的局限性:由于系统对磁头地址(8bit,可表示 2⁸ = 256 个磁头)、柱面地址(10bit,可表示 2¹⁰ = 1024 个柱面)、扇区地址(6bit,可表示 2⁶ = 63 个扇区 )的存储位数限制,结合每个扇区 512Byte 的大小,导致其支持的硬盘最大容量有限。按二进制换算(1MB = 1048576B)为 256 * 1024 * 63 * 512B = 8064MB;按十进制换算(1MB=1000000B) 则是 8.4GB。
4.磁盘的逻辑结构与LBA地址
我们熟知的磁带上面可以存储数据,我们可以把磁带“拉直”,形成线性结构。
磁盘在逻辑上也可以想象成“拉直”,形成线性结构;以每个扇区作为元素,组成一个数组,这样线性地址其实就是数组下标,这种地址叫做LBA。
柱面属于逻辑上的概念,其实每一面都是由相同半径的磁道逻辑上构成柱面。磁盘物理上分了很多面,但在逻辑结构上,可以认为磁盘整体是由柱面卷起来的。
柱面展开:
整盘展开:
所以寻址一个扇区,先找到哪一个柱面C,再确定柱面内哪一个磁道H,再确定扇区S,所以就有了CHS。
对于逻辑上的三维数组,实际上可以合为一个维度,变成一维数组:
所以每一个扇区都有一个下标,我们叫做LBA(Logical Block Address)地址,其实就是线性地址,LBA属于逻辑上的地址。
LBA 将硬盘上的所有扇区从 0 开始进行线性编号,把存储设备看作是一个连续的线性存储空间,不再像 CHS(柱面 - 磁头 - 扇区)寻址那样依赖磁盘的物理结构。操作系统和软件通过这些编号来访问特定的扇区,简化了对存储设备的操作。
OS
5.CHS与LBA的转换
CHS --> LBA:
- LBA = 柱面编号C * 单个柱面的扇区总数(磁头数 * 每个磁道扇区数) + 磁头编号H * 每个磁道扇区数 + 扇区号 - 1
- 扇区号通常是从1开始,在LBA中,地址是从0开始的
- 柱面和磁道都是从0开始编号的
LBA --> CHS:
- 柱面编号C = LBA // 单个柱面的扇区总数(磁头数 * 每个磁道扇区数)注://:表示取整除
- 磁头编号H = LBA % 单个柱面的扇区总数(磁头数 * 每个磁道扇区数)
- 扇区编号S = LBA % 每个磁道扇区数 + 1
OS只需要直接使用LBA与磁盘数据交互,LBA与CHS互相转化由磁盘自己来做(硬件电路,伺服系统) ,所以磁盘使用者需要注意的是LBA地址即可,对于使用者和OS来说,磁盘现在就是一个元素为扇区的一维数组,数组的下标就是每一个扇区的LBA地址,OS使用磁盘时就可以用一个数字访问磁盘扇区了。
二、文件系统
1.块
其实硬盘是典型的“块”设备,操作系统读取硬盘数据的时候,其实是不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个”块”(block)。
硬盘的每个分区是被划分为一个个的”块”。一个”块”的大小是由格式化的时候确定的,并且不可以更改,最常见的是4KB,即连续八个扇区(512KB)组成一个”块”。”块”是文件存取的最小单位。
命令:
stat
命令用于显示文件或文件系统的详细状态信息。stat [选项] 文件或目录 -c:指定输出格式,可自定义显示的信息字段。 -f:显示文件系统的状态信息,而不是文件本身的信息。 -L:跟随符号链接,显示符号链接所指向的目标文件的状态信息。 -t:以简洁的表格形式输出信息。
- 文件基本信息
- File:文件名。
- Size:文件大小,以字节为单位。
- Blocks:文件实际占用的磁盘块数。
- IO Block:文件系统的输入 / 输出块大小(块是文件存取的最小单位)。
- 文件权限和属性
- Device:文件所在设备的编号。
- Inode:文件的 inode 编号,用于在文件系统中唯一标识文件。
- Links:文件的硬链接数。
- Access:文件的访问权限,以八进制和字符形式表示。
- Uid:文件所有者的用户 ID。
- Gid:文件所有者的组 ID。
- 时间戳
- Access:文件的最后访问时间。
- Modify:文件内容的最后修改时间。
- Change:文件的元数据(如权限、属性等)的最后更改时间。
把磁盘的三维数组看出一维数组,数组下标就是LBA,每一个元素都是扇区,8个扇区一个块,那么每一个块的地址我们也能算出来:
- 知道LBA : 块号 = LBA / 8
- 知道块号:LBA = 块号 * 8 + n(n是块号第几个扇区)
2.分区
其实磁盘是可以被分成多个分区(partition)的,以Windows观点来看,你可能会有一块磁盘并且将它分区成C,D,E盘。那个C,D,E就是分区。分区从实质上说就是对硬盘的一种格式化。
Linux 分区是对存储设备(如硬盘、固态硬盘)进行逻辑划分的过程。通过分区,可将存储设备分割成多个独立区域,便于数据分类存储、系统安装、提高管理效率和保障数据安全。
分区类型:
- 主分区
- 定义:主分区是硬盘上直接可以使用的分区,它可以用来安装操作系统,也可以用来存储用户的数据文件等。
- 特点:每个硬盘最多可以划分 4 个主分区。主分区具有独立的引导记录,计算机在启动时可以直接从主分区加载操作系统,从而引导系统启动。比如我们通常将 Windows 系统安装在 C 盘,这个 C 盘一般就是一个主分区。
- 扩展分区
- 定义:扩展分区是一种特殊的分区,它不能直接用来存储数据和安装操作系统,其主要作用是为了突破主分区数量的限制,为创建更多的逻辑分区提供空间。
- 特点:一个硬盘上最多只能有一个扩展分区。扩展分区可以被看作是一个容器,它可以进一步划分成多个逻辑分区。
- 逻辑分区
- 定义:逻辑分区是在扩展分区内创建的分区,它是从扩展分区中划分出来的逻辑区域。
- 特点:逻辑分区可以像主分区一样用于存储数据和安装一些软件等。逻辑分区的数量没有严格限制,用户可以根据自己的需求在扩展分区内创建多个逻辑分区,例如 D 盘、E 盘等通常可以是逻辑分区。
分区表:
- 定义:分区表是存储在硬盘上的一种数据结构,它记录了硬盘上各个分区的相关信息,如分区的起始位置、大小、类型等。
- 特点:分区表对于操作系统识别和访问硬盘上的分区至关重要。常见的分区表格式有 MBR(Master Boot Record)和 GPT(GUID Partition Table)。
- MBR(主引导记录)
- 传统分区表格式,位于硬盘第一个扇区,包含分区信息和引导程序。
- 最大支持 2TB 硬盘容量,最多创建 4 个主分区或 3 个主分区加 1 个扩展分区。
- GPT(全局唯一标识分区表)
- 较新的分区表格式,支持更大硬盘容量(理论上无上限),可创建更多分区。
- 具备更好的容错性和数据安全性,是现代大容量硬盘常用分区表格式。
分区操作流程:
- 查看磁盘信息:命令 sudo fdisk -l,列出系统中所有磁盘设备及其分区信息。
- 进入分区操作界面:命令sudo fdisk /dev/sda,进入fdisk交互界面。
- 创建分区:在交互界面输入n创建新分区;选择分区类型(主分区或扩展分区),按提示设置分区起始和结束位置(设置的位置都是以扇区为单位计量的,并且是闭区间)。
- 设置分区类型:输入t,选择分区号后输入对应代码设置分区类型,如:Linux文件系统(83),swap交换分区(82);
- 保存分区设置:输入w保存分区设置并退出fdisk。
- 格式化分区:创建分区后,需格式化为合适的文件系统,如将/dev/sda1格式化为ext4:
sudo mkfs.ext4 /dev/sda1
3.inode
使用命令 ls -l 时,可以看见文件名,还能看见文件元数据(属性):
从左到右的共七列:模式、硬链接数、文件所有者、组、大小、最后修改时间、文件名
ls -l是读取存储在磁盘上的文件信息,然后显示出来。除此之外,还有刚才介绍的stat命令能看到更多的信息
文件数据都储存在“块”中,那么很显然,我们还必须找到一个地方储存文件的元信息(属性信息),比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为”索引节点”。
命令ls的选项-i,可以查看每个文件对应的inode,里面包含了与该文件有关的一些信息。
- Linux下文件的存储是属性和内容分离存储的。
- Linux下,保存文件属性的集合叫做inode,一个文件对应一个inode,inode内有一个唯一的标识符,叫做inode号。
- 文件名属性并没有在inode数据结构内部!!!
- indoe的大小一般是128字节或者256字节,下面统一128字节
- 任何文件的内容大小可以不同,但是属性大小一定是相同的
ext2_inode源码:
struct ext2_inode {
__le16 i_mode; /* 文件模式,存储文件类型和访问权限信息 */
__le16 i_uid; /* 文件所有者的用户ID,存储低16位 */
__le32 i_size; /* 文件大小,单位是字节 */
__le32 i_atime; /* 文件的访问时间 */
__le32 i_ctime; /* 文件的创建时间或inode状态变更的时间 */
__le32 i_mtime; /* 文件内容最后一次被修改的时间 */
__le32 i_dtime; /* 文件删除时间 */
__le16 i_gid; /* 文件所有者的组ID,存储低16位 */
__le16 i_links_count; /* 链接计数,指向该inode的硬链接数量 */
__le32 i_blocks; /* 文件所占的块数量,单位是512字节块 */
__le32 i_flags; /* 文件标志,标识文件的特定属性 */
union {
struct {
__le32 l_i_reserved1;
} linux1;
struct {
__le32 h_i_translator;
} hurd1;
struct {
__le32 m_i_reserved1;
} masix1;
} osd1; /* 操作系统依赖的字段 */
__le32 i_block[EXT2_N_BLOCKS]; /* 文件数据块的指针数组 */
__le32 i_generation; /* 文件版本,用于NFS */
__le32 i_file_acl; /* 文件ACL */
__le32 i_dir_acl; /* 目录ACL */
__le32 i_faddr; /* 片段地址 */
union {
struct {
__u8 l_i_frag; /* 片段编号 */
__u8 l_i_fsize; /* 片段大小 */
__u16 i_pad1;
__le16 l_i_uid_high;
__le16 l_i_gid_high;
__u32 l_i_reserved2;
} linux2;
struct {
__u8 h_i_frag;
__u8 h_i_fsize;
__le16 h_i_mode_high;
__le16 h_i_uid_high;
__le16 h_i_gid_high;
__le32 h_i_author;
} hurd2;
struct {
__u8 m_i_frag;
__u8 m_i_fsize;
__u16 m_pad1;
__u32 m_i_reserved2[2];
} masix2;
} osd2; /* 操作系统依赖的字段 */
};
结构与内容:
- 基本信息:包括文件的类型(普通文件、目录、字符设备文件、块设备文件等)、权限(如读写执行权限)、所有者和所属组。
- 时间戳:记录文件的访问时间(atime)、修改时间(mtime,文件内容被修改的时间)和状态改变时间(ctime,inode 信息改变的时间,如权限修改)。
- 文件大小:以字节为单位的文件大小。
- 链接计数:硬链接的数量,即有多少个文件名指向这个 inode。
- 数据块指针:指向存储文件实际数据的磁盘块的指针。根据文件大小不同,可能采用直接指针、间接指针或双重间接指针等方式来定位数据块。
作用:
- 文件系统管理:inode 为文件系统提供了一种高效的文件管理方式。通过 inode,文件系统可以快速定位和访问文件的属性信息以及文件的数据块,从而实现对文件的各种操作,如文件的打开、关闭、读写等。
- 数据安全和完整性:inode 中记录的文件权限、所有者等信息有助于保证系统的安全性和数据的完整性。系统可以根据 inode 中的权限信息来判断用户是否有权对文件进行相应的操作,防止非法访问和数据篡改。
- 文件系统组织:inode 在文件系统的组织和布局中起着关键作用。它使得文件系统能够以一种有序的方式存储和管理大量的文件,方便用户和系统对文件进行查找、分类和管理。
工作机制:
- 文件访问:当用户通过文件名访问文件时,文件系统首先通过目录项找到对应的 inode 号,然后根据 inode 号找到对应的 inode 结构,从 inode 中获取文件的权限、数据块位置等信息,进而读取或写入文件数据。
- 文件创建与删除:创建文件时,文件系统会分配一个新的 inode,并在目录中添加一个指向该 inode 的目录项;删除文件时,会减少 inode 的链接计数,如果链接计数变为 0,则释放 inode 和相关的数据块。
inode 号:
- 每个 inode 都有一个唯一的 inode 号,在整个文件系统中用于标识该 inode。文件系统通过 inode 号来查找和操作 inode,而不是文件名。文件名只是用户方便识别和操作文件的一个标签,真正在底层文件系统中进行数据处理和管理时,使用的是 inode 号。
与文件的特殊关系:
- 硬链接:多个文件名可以指向同一个 inode,这种情况被称为硬链接。多个硬链接文件共享同一个 inode 及文件数据,对其中一个文件的修改会反映到其他硬链接文件上,因为它们本质上是同一个文件的不同名称,都指向同一个 inode。
4.ext2文件系统
我们已经知道,硬盘是典型的“块”设备,操作系统读取硬盘数据的时候,读取的基本单位是“块”,”块“同时也是硬盘的每个分区下的结构,那么”块“在分区中是如何排布的?要通过什么方式找到”块“呢?存储文件属性的inode又是以什么方式存在的?
文件系统就是为了组织管理这些的!
宏观认识:
在硬盘上存储文件,必须先把硬盘格式化为某种格式的文件系统才能存储文件,文件系统的目的就是组织和管理硬盘中的文件的;在Linux中,最常见的是ext2系列的文件系统。
ext2文件系统将整个分区划分成若干个同样大小的块组(Block Group),如下图所示,只要能管理一个分区就能管理所有分区,也就能管理所有磁盘文件。
上图中的启动块(Boot Block/Sector)的大小是确定的,为1KB,由PC标准规定,用来存储磁盘 分区 信息和启动信息,任何文件系统都不能修改启动块,启动块之后才是ext2文件系统的开始。
Block Group(块组):
ext2文件系统会根据分区的大小划分若干个大小相同的块组Block Group,而每个Block Group都有着相同的结构组成。
1.磁盘层面(Disk):
- MBR(主引导记录):位于磁盘的第一个扇区,大小通常为 512 字节。它承担着极其关键的双重任务。一方面,包含了引导加载程序(Bootloader),这是计算机启动时运行的第一段代码,负责加载操作系统内核到内存中,从而启动操作系统。例如常见的 GRUB(Grand Unified Bootloader),它就可以安装在 MBR 中,支持多操作系统的启动管理。另一方面,MBR 还包含了一个分区表,这个表记录了磁盘上各个分区的起始位置、大小以及分区的类型等信息。不过,MBR 分区表最多只能记录 4 个主分区的信息,如果需要更多分区,就需要使用扩展分区等方式来实现。图中可以看到,在 MBR 之后紧接着就是 4 个分区,分别标记为 Partition 1 到 Partition 4 。
2.分区层面(Partition):
- 引导扇区(Boot Sector):每个分区的开头部分就是引导扇区。它同样包含了一小段引导代码,其作用是在计算机启动过程中,从该分区加载操作系统的引导程序。不同的操作系统可能会对引导扇区有不同的格式要求和使用方式。比如,Windows 系统和 Linux 系统的引导扇区在结构和功能上就存在一定差异。
- EXT2 文件系统区域:引导扇区之后就是 EXT2 文件系统占据的空间。EXT2 文件系统会将这部分空间进一步组织和管理,以实现对文件和目录的存储与访问。
3.文件系统层面(File System):
- 块组(Block Group)划分:EXT2 文件系统把自身的存储空间划分为多个块组,从图中可以看到从 Block Group 0 到 Block Group N 。这样做有几个重要的好处。首先,每个块组都包含了文件系统的一些关键元数据和数据存储区域,当某个块组出现损坏时,其他块组的数据仍然有可能保持完整,从而提高了文件系统的可靠性。其次,在进行文件操作时,系统可以在相对较小的块组范围内查找和管理资源,减少了搜索整个文件系统的开销,提高了操作效率。
4.块组内部结构(Block Group):
- 超级块(Super Block):存储着关于整个文件系统的全局信息,是文件系统管理的核心数据结构之一。这些信息包括文件系统的块大小(例如常见的 1KB、2KB 或 4KB ),这决定了数据存储和读取的基本单位;inode 的总数,它关系到文件系统能够容纳的文件和目录数量;块组的数量,用于文件系统的内部组织和管理;以及文件系统的状态信息,如是否被正确卸载、是否需要进行一致性检查等。
为了防止超级块损坏导致整个文件系统无法访问,超级块会在多个块组中进行备份,但并非所有块组都有超级块备份,主要是基于空间利用和实际需求的考量:一方面,每个超级块内容相同,全部块组都备份会造成大量空间浪费。另一方面,日常使用中,少量备份足以应对大部分损坏情况,如在部分块组受损时,仍可从其他备份恢复。- 组描述符表(GDT,Group Descriptor Table):记录了每个块组的详细属性信息,比如块位图在块组中的位置、inode 位图的位置、inode 表的位置以及数据块区域的起始位置等。通过组描述符表,文件系统可以快速定位和访问各个块组内的关键数据结构。
- 块位图(Block Bitmap):是一个二进制位序列,每一位对应一个数据块。如果某一位的值为 0,表示对应的那个数据块目前处于空闲状态,可以被分配用来存储文件数据;如果值为 1,则表示该数据块已经被占用。文件系统在分配和释放数据块时,会通过更新块位图来记录数据块的使用情况。
- inode 位图(inode Bitmap):与块位图类似,也是一个二进制位序列,每一位对应一个 inode。0 表示该 inode 未被使用,可以分配给新的文件或目录;1 表示该 inode 已经被占用,对应着一个已存在的文件或目录。inode 位图帮助文件系统有效地管理 inode 的分配和回收。
- inode 表(inode Table):是一个包含了块组内所有 inode 的表格。每个 inode 都是一个数据结构,用于存储一个文件或目录的详细元数据信息。这些信息包括:
- inode 编号(Inode Number):在整个文件系统中唯一标识一个 inode ,用于快速定位和查找对应的 inode。
- 文件类型(File Type):指明该 inode 对应的是普通文件(用 “-” 表示)、目录(用 “d” 表示)、字符设备文件、块设备文件还是符号链接等。
- 权限(Permission):以数字或符号的形式表示文件或目录的访问权限,例如 644 表示所有者有读写权限,组用户和其他用户只有读权限;755 表示所有者有读写执行权限,组用户和其他用户有读执行权限。
- 链接计数(Link count):记录了指向该 inode 的硬链接的数量。当创建一个硬链接时,链接计数会增加;删除一个硬链接时,链接计数会减少,当链接计数为 0 时,文件系统会认为该文件没有被引用,可以释放相关的 inode 和数据块资源。
- 用户 ID(UID)和组 ID(GID):分别表示文件或目录的所有者的用户 ID 和所属的组 ID,用于权限控制和资源管理。
- 文件大小(size):以字节为单位记录文件的实际大小。
- 指针(pointer):指向存储文件实际数据的数据块。根据文件大小的不同,可能会有直接指针(直接指向数据块)、间接指针(指向一个存储数据块地址的块)甚至双重间接指针、三重间接指针等,以适应不同大小文件的数据存储需求。
- 数据块(Data Blocks):是实际存储文件和目录内容的地方。文件的数据会被分割成若干个小块,存储在这些数据块中。文件系统通过 inode 中的指针信息来定位和访问这些数据块,从而实现对文件的读取和写入操作。
需要注意:
- inode编号以分区为单位,整体划分,不可以跨分区
- Block号按照分区划分,不可跨分区
- 对于普通文件,文件的数据存储在数据块中
- 对于目录,该目录下的所有文件名和目录名存储在所在目录的数据块中,除了文件名外,ls -l命令看到的其他信息保存在该文件的inode中
inode与Data Block映射 :
通过上面的介绍,我们知道文件系统通过inode中的指针信息来定位和访问这些数据块,从而实现对文件的读取和写入操作。inode内部存在下图所示结构,就是用来进行inode和block映射的;其中的EXT2_N_BLOCKS = 15。根据文件大小的不同,可能会有直接指针(直接指向数据块)、间接指针(指向一个存储数据块地址的块)甚至双重间接指针、三重间接指针等,以适应不同大小文件的数据存储需求。
1. 直接块指针:
图中 inode 结构包含 “12 个直接块指针”。假设每个数据块的大小为 B 字节(常见的块大小有 1KB = 2^10 字节、 4KB = 2^12 字节等),那么通过这 12 个直接块指针能管理的文件大小为 12×B 字节。例如,当块大小 B = 4KB 时,直接块能容纳的文件大小是 12×4KB=48KB ,对于一些小型文本文件、简单配置文件等,它们的数据量往往较小,可直接通过这 12 个指针快速定位到相应数据块。
2. 一级间接块索引表指针:
一级间接块索引表指针指向的是一个特殊的数据块,该数据块用于存储其他数据块的指针。假设每个指针的大小为 P 字节(通常为 4 字节或 8 字节,在 64 位系统中常见为 8 字节 ),那么一个大小为 B 字节的数据块能存放的指针数量为 B/P 个。这些指针再分别指向真正存储文件数据的普通数据块。所以,通过一级间接块索引表能管理的文件大小为 B/P × B 字节。比如,在块大小 B=4KB=2^12 字节,指针大小 P=4 字节的情况下,一级间接块索引表能管理的文件大小为 (2^12) / 4 × 2^12 = 4MB 。3. 二级间接块索引表指针:
二级间接块索引表指针指向的块中存储的是指向一级间接块索引表的指针。先由二级间接块索引表指针指向的块找到若干个一级间接块索引表,每个一级间接块索引表再指向普通数据块。此时能管理的文件大小为 (B/P)^2×B 字节。同样以 B=4KB=2^12 字节, P=4 字节为例,二级间接块索引表能管理的文件大小为 [(2^12) /4]^2 × 2^12=4GB 。4. 三级间接块索引表指针:
三级间接块索引表指针指向的块存储着指向二级间接块索引表的指针。能管理的文件大小为 (B/P)^3×B 字节。若还是 B=4KB=2^12 字节, P=4 字节,三级间接块索引表能管理的文件大小为 [(2^12) /4]^3×2^12=4TB 。
- 分区之后的格式化操作,就是对分区进行分组,在每个分组中写入SB、GDT、Block Bitmap、Inode Bitmap等管理信息,这些管理信息统称为文件系统。
- 只要知道文件的inode号,就能在指定分区中确定是哪一个分组,进而在哪一个分组确定是哪一个indoe。
- 拿到inode文件属性和内容就全部都有了
创建一个新文件主要有以下几个操作:
- 存储属性:内核先找到一个空闲的i节点(这里是263466),内核把文件信息记录到其中
- 存储数据:该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800,将内核缓冲区的第一块复制到300,下一块复制到500,以此类推
- 记录分配情况:文件内容按顺序300、500、800存放,内核在inode上的磁盘分布区记录了上述块列表
- 添加文件名到目录:新的文件名为abc,仔细想象linux如何在当前的目录中记录这个文件?内核将入口(263466,abc)的映射关系添加到目录文件,文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。
5.目录与文件名
仔细想想我们访问文件时,使用的是文件名,从来没有使用过inode号?目录为什么能保存文件?目录也是文件吗?
- 目录也是文件,但磁盘上没有目录的概念,只有文件属性+文件内容的概念
- 目录的属性不用多说,内容保存的是:文件名和inode号的映射关系
下面看一段代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <directory>\n", argv[0]);
exit(EXIT_FAILURE);
}
DIR *dir = opendir(argv[1]);
if (!dir) {
perror("opendir");
exit(EXIT_FAILURE);
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
// Skip the "." and ".." directory entries
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}
printf("Filename: %s, Inode: %lu\n", entry->d_name, (unsigned long)entry->d_ino);
}
return 0;
}
该代码使用opendir
函数打开指定目录,若打开失败则打印错误信息并退出;通过readdir
函数循环读取目录中的每一项,跳过.
(当前目录)和..
(上级目录)这两个特殊目录项。对于其他目录项,打印其文件名和对应的 inode 编号。
运行结构:
ayanami@ayanami-virtual-machine:~/file_lession$ ./proc /
Filename: sbin, Inode: 18
Filename: lib32, Inode: 15
Filename: bin, Inode: 13
Filename: lib64, Inode: 16
Filename: dev, Inode: 393217
Filename: lib, Inode: 14
Filename: swapfile, Inode: 12
Filename: opt, Inode: 917505
Filename: sys, Inode: 1048579
Filename: snap, Inode: 655362
Filename: usr, Inode: 262148
Filename: var, Inode: 786433
Filename: tmp, Inode: 917507
Filename: mnt, Inode: 655361
Filename: libx32, Inode: 17
Filename: root, Inode: 524290
Filename: run, Inode: 393218
Filename: srv, Inode: 917506
Filename: etc, Inode: 131073
Filename: cdrom, Inode: 918174
Filename: boot, Inode: 1048577
Filename: media, Inode: 262145
Filename: proc, Inode: 131075
Filename: lost+found, Inode: 11
Filename: home, Inode: 524289
所以访问文件,必须打开当前目录,根据文件名,获取对应的inode号,然后进行文件访问;必须知道当前工作目录,本质是必须能打开当前工作目录文件,查看目录文件的内容。例如:要访问proc.c,就必须打开当前目录(file_lession),然后才能获取proc.c对应的inode进而对文件进行访问。
6.路径解析
打开当前工作目录文件,查看当前工作目录文件的内容?当前工作目录不也是文件吗?我们访问当前工作目录不也是只知道当前工作目录的文件名吗?要访问它,不也得知道当前工作目录的inode吗?
- 获取当前工作目录的inode就必须打开当前工作目录的上级目录,打开当前工作目录的上级目录,就需要它的inode,获取上级工作目录的inode就必须打开当前上级工作目录的上级目录...
- 这是类似”递归“的方式,需要把路径中所有的目录全部解析,出口是"/"根目录。
- 所以任何文件都有路径,访问目标文件如:"/home/ayanami/file_lession/proc.c",都需要从根目录开始,依次打开每一个目录,根据目录名,依次访问每个目录下指定的目录,直到访问到目标文件proc.c,这个过程叫做Linux路径解析。
- 所以访问文件,必须要有 目录 + 文件名 = 路径。
- 根目录固定文件名和inode号,无需查找,系统开机之后就必须直到。
那么路径由谁提供?
- 我们进行文件访问时,都是指令/工具访问,本质就是进程访问,进程有CWD,进程提供路径。
- 使用系统调用时,我们传参也提供了路径。
最开始的路径从哪里来?
Linux提供了根目录和家目录,新建任何文件,都在你或者系统指定的目录下新建,这就是最开始的路径。所以 系统 + 用户 共同构建Linux路径结构。
7.路径缓存
我们已经知道,Linux磁盘中,不存在目录的概念,全部都是文件,只保存文件属性 + 文件内容
我们访问任何文件,都需要指明路径,进行路径解析,那么都要从根目录/开始进行路径解析吗?
原则上是得从根目录/开始解析,但这样太慢了,所以Linux会缓存历史路径结构。在Linux中,内核中维护树状路径结构的内核结构体叫做:struct dentry
- 每个文件都有对应的dentry结构,包括普通文件,这样所有被打开的文件,就可以在内存中形成整个树形结构。
- 整个树形节点也同时会隶属于LRU(Least Recently Used,最近最少使用)的结构中,进行节点淘汰。
- 整个树形节点也会同时隶属于Hash,方便快速查找。
- 更重要的是,这个树形结构,整体构成了Linux的路径缓存结构,打开访问任何文件,都先在在这棵树下根据路径进行查找,找到就返回属性inode和内容,没找到就从磁盘加载路径,添加dentry结构,缓存新路径。
8.挂载分区
我们已经能够根据inode号在指定分区找文件了,也已经能根据目录文件内容找指定的inode了,在指定的分区内,我们可以为所欲为了。可是inode不是不能跨分区吗?Linux不是可以有多个分区吗?我怎么知道我在哪一个分区?
- 分区写入文件系统,无法直接使用,需要和指定的目录关联,进行挂载才能使用。
- 所以可以根据访问目标文件的”路径前缀“准确判断我在哪一个分区。