Linux系统中,文件系统是连接硬件与用户的核心桥梁。本文将从磁盘硬件基础讲起,层层拆解 Ext 系列文件系统的核心设计,再到分区、挂载、路径解析等实操流程,最后深入软硬链接的原理与应用,让你从底层到实战彻底掌握 Linux 文件存储的核心逻辑。1,盘片:存储数据的载体,一个磁盘可能有多个盘片,每个盘片有上下两个盘面2,磁头:类似“读写笔”,每个盘面对应一个磁头,负责读写数据,所有磁头固定在同一个磁臂上,同步移动3,磁道:盘片上的同心圆轨道,是数据存储的环形区域,从外圈到内圈编号(0磁道,1磁道.....)4,扇区:磁道被划分的扇形区域,是磁盘读写的最小物理单位(默认512字节),所有磁道的扇区数量相同5,柱面:所有盘片上半径相同的磁道组成的逻辑结构(磁头同步移动,同时访问所有盘面的同编号磁道)关键特性:操作系统无法直接修改扇区的单个bit,必须将整个512字节扇区加载到内存修改后,再完整写回磁盘。核心结论:磁盘存储数据的物理单元是扇区,而磁头,柱面,扇区三者共同构成了数据的物理定位坐标。CHS 寻址(物理地址)通过柱面(Cylinder)、磁头(Head)、扇区(Sector) 三个参数定位扇区,是早期磁盘的寻址方式:
磁盘容量 = 磁头数 * 磁道数 * 每个磁道扇区数 * 512字节磁盘的逻辑结构是操作系统对物理硬件的 “简化抽象”,核心是将复杂的三维物理结构(盘片→磁道→扇区)转化为易于管理的线性结构,这也是 LBA 寻址能实现的基础。磁带上面可以存储数据,我们可以把磁带“拉直”,形成线性结构
那么磁盘本质上虽然是硬质的,但是逻辑上我们可以把磁盘想象成为卷在一起的磁带,那么磁盘的逻辑存储结构我们也可以类似于:
这样每一个扇区,就有了一个线性地址(其实就是数组下标),这种地址叫做LBA
所有,寻址一个扇区:先找到哪一个柱面(Cylinder) ,在确定柱面内哪一个磁道(其实就是磁头位置,Head),在确定扇区(Sector),所以就有了CHS 。
我们之前学过C/C++的数组,在我们看来,其实全部都是一维数组:
所以,每一个扇区都有一个下标,我们叫做LBA(Logical Block Address) 地址,其实就是线性地址。所以怎么计算得到这个LBA地址呢?
CHS 转成 LBA:
- 磁头数 * 每磁道扇区数 = 单个柱面的扇区总数
- LBA = 柱面号 C * 单个柱面的扇区总数 + 磁头号 H * 每磁道扇区数 + 扇区号 S - 1
- 即: LBA = 柱面号 C*(磁头数 * 每磁道扇区数) + 磁头号 H * 每磁道扇区数 + 扇区号 S - 1
- 扇区号通常是从 1 开始的,而在 LBA 中,地址是从 0 开始的
- 柱面和磁道都是从 0 开始编号的
- 总柱面,磁道个数,扇区总数等信息,在磁盘内部会自动维护,上层开机的时候,会获取到这些参数。
LBA 转成 CHS:
- 柱面号 C = LBA // (磁头数 * 每磁道扇区数) 【就是单个柱面的扇区总数】
- 磁头号 H = (LBA % (磁头数 * 每磁道扇区数)) // 每磁道扇区数
- 扇区号 S = (LBA % 每磁道扇区数) + 1
- "//": 表示除取整
所以:从此往后,在磁盘使用者看来,根本就不关心 CHS 地址,而是直接使用 LBA 地址,磁盘内部自己转换。所以:
从现在开始,磁盘就是一个元素为扇区的一维数组,数组的下标就是每一个扇区的 LBA 地址。OS 使用磁盘,就可以用一个数字访问磁盘扇区了。
逻辑结构的核心优势
硬盘是典型的 “块” 设备,操作系统读取硬盘数据的时候,其实是不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个” 块”(block)。
与块设备相反的就是字符设备,区别在于:字符设备不支持随机读取
文件系统访问磁盘,不以扇区位单位,而是以"块"为单位,一般是4KB(连续8个扇区)的,可以调整
硬盘的每个分区是被划分为一个个的” 块”。一个” 块” 的大小是由格式化的时候确定的,并且不可以更改,最常见的是 4KB,即连续八个扇区组成一个” 块”。” 块” 是文件存取的最小单位。
注意:
• 磁盘就是一个三维数组,我们把它看待成为一个"一维数组",数组下标就是LBA,每个元素都是扇区
• 每个扇区都有LBA,那么8个扇区一个块,每一个块的地址我们也能算出来。
• 知道LBA:块号 = LBA/8
• 知道块号:LBA=块号*8 + n. (n是块内第几个扇区)
其实磁盘是可以被分成多个分区(partition)的,以 Windows 观点来看,你可能会有一块磁盘并且将它分区成 C,D,E 盘。那个 C,D,E 就是分区。分区从实质上说就是对硬盘的一种格式化。但是 Linux 的设备都是以文件形式存在,那是怎么分区的呢?
柱面是分区的最小单位,我们可以利用参考柱面号码的方式来进行分区,其本质就是设置每个区的起始柱面和结束柱面号码。此时我们可以将硬盘上的柱面(分区)进行平铺,将其想象成一个大的平面,如下图所示:
注意:
• 柱面大小一致,扇区个位一致,那么其实只要知道每个分区的起始和结束柱面号,知道每一个柱面多少个扇区,那么该分区多大,其实和解释LBA是多少也就清楚了.
文件 = 内容 + 属性。在Linux下内容和属性是分开存储的。Linux,任何正常文件,都要有自己的属性集合(本质是一个struct结构体),描述文件属性的结构体,称为inode,只要是个结构体,他们的大小都是固定的,一般是128字节。一个文件的属性,其实就是一个struct inode结构体对象文件名会不会作为属性,保存在文件的inode当中?->不会,文件名是字符串,有长有短,会导致inode大小的浮动。
文件内容保存在Data Block里。文件属性保存在inode Table当中。inode Table以4KB为单位的数据块,一个4KB的数据块会保存32个inode.
每一个inode都要有自己的inode编号
之前我们说过 文件 = 数据 + 属性,我们使用 ls -l 的时候看到的除了看到文件名,还能看到文件元数据(属性)。
[root@localhost linux]# ls -l总用量 12-rwxr-xr-x. 1 root root 7438 "9月 13 14:56" a.out-rw-r--r--. 1 root root 654 "9月 13 14:56" test.c
行包含 7 列:
ls -l 读取存储在磁盘上的文件信息,然后显示出来
其实这个信息除了通过这种方式来读取,还有一个 stat 命令能够看到更多信息
[root@localhost linux]# stat test.cFile: "test.c"Size: 654 Blocks: 8 IO Block: 4096 普通文件Device: 802h/2050d Inode: 263715 Links: 1Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)Access: 2017-09-13 14:56:57.059012947 +0800Modify: 2017-09-13 14:56:40.067012944 +0800Change: 2017-09-13 14:56:40.069012948 +0800
可以通过ls -li来看到文件的inode编号
所以一个文件的属性 inode 长什么样子呢?
/* * Structure of an inode on the disk */struct ext2_inode { __le16 i_mode; /* File mode */ __le16 i_uid; /* Low 16 bits of Owner Uid */ __le32 i_size; /* Size in bytes */ __le32 i_atime; /* Access time */ __le32 i_ctime; /* Creation time */ __le32 i_mtime; /* Modification time */ __le32 i_dtime; /* Deletion Time */ __le16 i_gid; /* Low 16 bits of Group Id */ __le16 i_links_count; /* Links count */ __le32 i_blocks; /* Blocks count */ __le32 i_flags; /* File flags */ union { struct { __le32 l_i_reserved1; } linux1; struct { __le32 h_i_translator; } hurd1; struct { __le32 m_i_reserved1; } masix1; } osd1; /* OS dependent 1 */ __le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */ __le32 i_generation; /* File version (for NFS) */ __le32 i_file_acl; /* File ACL */ __le32 i_dir_acl; /* Directory ACL */ __le32 i_faddr; /* Fragment address */ union { struct { __u8 l_i_frag; /* Fragment number */ __u8 l_i_fsize; /* Fragment size */ __u16 i_pad1; __le16 l_i_uid_high; /* these 2 fields */ __le16 l_i_gid_high; /* were reserved2[0] */ __u32 l_i_reserved2; } linux2; struct { __u8 h_i_frag; /* Fragment number */ __u8 h_i_fsize; /* Fragment size */ __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; /* Fragment number */ __u8 m_i_fsize; /* Fragment size */ __u16 m_pad1; __u32 m_i_reserved2[2]; } masix2; } osd2; /* OS dependent 2 */};/* * Constants relative to the data blocks */#define EXT2_NDIR_BLOCKS 12#define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS#define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1)#define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1)#define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1)备注: EXT2_N_BLOCKS = 15
再次注意:
- 文件名属性并未纳入到 inode 数据结构内部
- inode 的大小一般是 128 字节或者 256,我们后面统一 128 字节
- 任何文件的内容大小可以不同,但是属性大小一定是相同的
到目前为止,相信大家还有两个问题:
- 我们已经知道硬盘是典型的 “块” 设备,操作系统读取硬盘数据的时候,读取的基本单位是” 块”。“块” 又是硬盘的每个分区下的结构,难道 “块” 是随意的在分区上排布的吗?那要怎么找到 “块” 呢?
- 还有就是上面提到的存储文件属性的 inode,又是如何放置的呢?
文件系统就是为了组织管理这些的!!