目录
- 一、理解硬件
-
- [1.1 磁盘、服务器、机柜、机房](#1.1 磁盘、服务器、机柜、机房)
- [1.2 磁盘的物理结构](#1.2 磁盘的物理结构)
- [1.3 磁盘的存储结构](#1.3 磁盘的存储结构)
- [1.4 磁盘的逻辑结构](#1.4 磁盘的逻辑结构)
-
- [1.4.1 CHS与LBA的相互转化](#1.4.1 CHS与LBA的相互转化)
- 二、引入文件系统
-
- [2.1 块概念](#2.1 块概念)
- [2.2 分区概念](#2.2 分区概念)
- [2.3 inode 概念](#2.3 inode 概念)
- [2.4 GDT](#2.4 GDT)
- [2.5 Super Block](#2.5 Super Block)

个人主页:矢望
个人专栏:C++、Linux、C语言、数据结构、Coze-AI
一、理解硬件
1.1 磁盘、服务器、机柜、机房
HDD机械磁盘 是计算机中唯一的一个机械设备,磁盘属于外部设备,简称外设。它的速度比SSD固态硬盘要慢,但它的容量大,价格便宜,所以在需要动辄需要几百万件磁盘的大型企业中,它们购买的基本都是机械硬盘,这样能够压缩成本。

如上是企业级磁盘,它拆开后内部结构是这样的:

我们见到的磁盘,密封好的磁盘,内部基本是无尘无菌的,生产磁盘的前提你就必须要有无尘无菌的生产车间。像上面这块拆开的硬盘,在我们的普通用户视角,一旦拆开,这块盘基本报废。
在上图中我们看到的反光镜面,我们的数据就保存在磁盘的光面上,它的正反两面都是光的,都可以存储数据。盘的上面有一个很长的杆状结构,它就是磁头臂,现在的磁盘每秒钟转几万转,所以才叫机械磁盘。磁盘在旋转的时候,那个磁头臂也会上下转动,在磁盘上读写数据。
下图展示的就是服务器:

上面的服务器可以装24块机械硬盘,假如一块盘4TB,那么这一台服务器就是96TB。服务器内部装磁盘的东西长这样:

下图就是机柜:

这一层空间里面就可以放一台服务器,假如这一台机柜可以存放8台服务器,那么一个服务器的容量乘以8就是这一台机柜的容量。
下图是服务器的背面:

它的背面就可以走线了,服务器只需要把网络线和电源线走好。服务器走线之后就有了一个整机柜,然后将机柜一个一个摆好,网线走好,路由器嫁接好,电灯空调一装就是机房了。

磁盘为什么能存储数据呢? 磁铁大家都见过,有N极有S极,你可以把磁盘想象成由无数个小磁针聚集形成的盘片,向这个盘片中写0写1就是改变这个小磁针的磁极。
当一块磁盘要报废的时候,大型互联网公司按照国家要求就需要对磁盘进行消磁。磁铁丢进火里烤上一会儿就没有磁性了,所以消磁最简单的方式就是高温消磁,当然公司有成本更低的方式。
我们的磁盘和磁头臂的磁头并没有紧挨着,因为一旦挨着,磁盘在高速转动,如果挨着就会造成高温,就会损坏盘片,造成数据的丢失,磁头和盘片之间有 0.005-0.01 微米之间的距离,灰尘颗粒大概是 0.5-100 微米。
1.2 磁盘的物理结构
正面 :

侧面 :

背面 :

1.3 磁盘的存储结构

扇区 :是磁盘存储数据的基本单位,512字节,块设备。也就是说操作系统对磁盘做IO操作,必须是以512字节为单位。

如上图,所有的磁头都被一个传动臂相连,它们是共进退的,所有的盘片也被一个主轴相连,它们是共旋转的。

如上面的左图,半径相同的分布在不同的盘片上的磁道共同组成柱面 。
如上面的右图,我们看到外圈的扇区比里面的扇区大,但我们认为它们的存储容量是相同的,都是512字节。

那么如何定位一个扇区呢?
CHS寻址法 :可以先定位磁头header,确定磁头要访问哪一个柱面(磁道)cylinder,再定位一个扇区sector。我们能定位一个扇区就可以定位任意一个扇区,就能够定位任意多个扇区。我们之前说过文件等于内容加属性,而它们都是数据,无非就是存储在几个扇区的问题。
扇区 是从磁盘读出和写入信息的最小单位,通常大小为 512 字节。磁头数 :每个盘片一般有上下两面分别对应1个磁头,每个盘片共2个磁头。磁道数 :磁道是从盘片外圈往内圈编号0磁道,1磁道等等,靠近主轴的同心圆用于停靠磁头,不存储数据。柱面数 :磁道构成柱面,数量上等同于磁道个数。扇区数 :每个磁道都被切分成很多扇形区域,每道的扇区数量相同。圆盘数 :就是盘片的数量。磁盘容量=磁头数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数 。细节:传动臂上的磁头是共进退的。
1.4 磁盘的逻辑结构

磁带我们都知道,它是一圈一圈的用于存储数据,我们可以把它拉成一个长条,而我们的磁盘也是由一个磁道一个磁道组成的,我们也可以抽象的把它拉直,这样就将它抽象成了一个一维数组。

那么磁盘的逻辑存储结构可以类似于:

如上,逻辑上将磁盘理解成一维数组,数组的元素就是扇区。这样每一个扇区,就有了一个线性地址(其实就是数组下标),这种地址叫做LBA。

- 真实情况

如上图所示,磁盘在进行访问的时候,首先要确定我们要访问哪一个柱面,磁盘整体是由柱面构成的 。

如上图,磁头摆动的本质是在访问某一个柱面或者磁道,盘片旋转的本质是要让某一个扇区旋转到磁头下方,就是定位到指定扇区的作用。而我们确定哪一个磁头就是读取哪一个扇区。
磁道是某一盘面的某一个磁道展开,它展开之后可抽象成一维数组 。而柱面是整个磁盘所有盘面上的同一半径磁道的展开,它可以抽象成二维数组。

整个磁盘是由很多个柱面构成的,把它展开之后就是一个个二维数组构成的,所以磁盘可以抽象成一个三维数组 。

所以磁盘的本质是一个三维数组,那么如何在磁盘里面定位一个扇区呢?首先要定位一个柱面cylinder,其次要选择某一个盘面也就是定位一个磁头head,最后就是要找到我们要找的扇区sector 。这就是CHS,所以如果磁盘是一个三维数组dist[][][],那么我们的整个过程就是找它的一维二维三维的位置,所以CHS就是这个三维数组的下标。
我们之前学过C/C++中的数组,我们直到二维和三维数组不都是由一维堆叠而成的吗? 所以三维数组的本质其实就是一维数组,所以我们就把磁盘抽象成一维数组了。

所以,每一个扇区都有一个下标,我们叫做LBA(Logical Block Address)地址,其实就是线性地址。
1.4.1 CHS与LBA的相互转化
OS只需要使用LBA地址就可以了。
CHS:dist[柱面号][磁道号][扇区号],LBA = 柱面号 * 单个柱面的扇区数 + 磁道号 * 单个磁道的扇区数 + 扇区号 - 1 ,因为扇区的编号是从1开始的,所以计算LBA时需要减去1。例如表示第一个扇区时,CHS:dist[0][0][1], LBA:0。
LBA转CHS:柱面号 = LBA / 单个柱面的扇区数,磁头号 = (LBA % 单个柱面的扇区数) / 单个磁道的扇区数,扇区号 = LBA % 单个磁道的扇区数 + 1。
举个例子,一个柱面有4个磁道16个扇区,一个磁道有4个扇区,当LBA等于23时,CHS就等于[1][1][4]。
由LBA转化成CHS的运算是由磁盘自己来做的。
所以磁盘就是一个的一维数组,这个一维数组中的元素就是扇区,数组的下标就是每一个扇区的LBA地址。OS使用磁盘,就可以用一个数字访问磁盘扇区了。
二、引入文件系统
2.1 块概念
磁盘是典型的块设备,操作系统读取磁盘数据的时候,其实是不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个块block。
硬盘的每个分区是被划分为一个个的块。一个块的大小是由格式化的时候确定的,并且不可以更改,最常见的是4KB,也就是4096字节,即连续八个扇区组成一个块。块是文件存取的最小单位。
每个扇区都有自己的LBA地址,一个扇区512字节,那么8个扇区就是一个块,所以每一块的地址我们也能算出来,块号 = LBA / 8。那么LBA = 块号 * 8 + k(0=<k<=7)。

那么为什么操作系统不直接使用512字节也就是一个扇区去访问磁盘呢? 原因如下:1、为了提高效率,逐个扇区去访问效率太慢了,一次读取一个块效率高。2、这样能够软硬件解耦,引入块之后,操作系统和磁盘之间不是强相关的,这样在操作系统这款软件中没有扇区的概念,磁盘的更改对操作系统没有影响。
2.2 分区概念
一个磁盘的空间是很大的,比如一个1TB的磁盘,操作系统要对它进行管理是比较困难的,空间太大了,所以这时候就有了分治的思路。将一个磁盘分成很多个区域,这叫做分区,这样将一个区管好,所有的区都是一样的,就可以把整个磁盘管理好 。以Windows观点来看,你可能会有一块磁盘并且将它分区成C,D,E盘。那个C,D,E就是分区。分区从实质上说就是对硬盘的一种格式化。
但是一个区还是太大了,可能这一个区也有好几百GB,所以接下来就将一个区分成很多个组。这样管理起来就方便多了。

我们知道文件 = 文件内容 + 文件属性,而在Linux系统中,文件内容和文件属性是分开存储的。所以在分好的每个组里面,想要把文件管理好,这个组里面就需要有管理文件数据的信息 和文件数据信息 ,而它们都是数据,所以都会在每个组中体现。

要管理文件,首先就要有对应的管理办法。所以磁盘分区分组之后的下一步就是向每一个组中写入管理信息,这个写入管理信息的过程就叫做写入文件系统,在日常生活中也叫做格式化。格式化就是把你的所有分区分组的数据全部清理掉,然后将管理信息全部写入,所以格式化的过程就是写入全新文件系统的过程。
所以在一个分区内,将所有分组都写入管理信息,这一匹管理信息我们统称为文件系统 。文件系统 通过将分区划分为多个分组,并在每个分组中写入相应的管理信息,来实现对整个分区的数据组织和管理。

2.3 inode 概念
文件 = 内容 + 属性,文件的内容将来在分组的Data Blocks中存储,文件的属性将来在分组的inode Table中存储。它们里面都是很多4kb为单位的小块。所以文件 = Data Blocks + inode Table。

将来每一个Data Blocks里面的块都有自己的编号,文件的内容就会在这个里面挑选几个块存储。而文件的属性就存储在inode中,理论上一个文件一个inode,所以在这个描述文件属性的inode中肯定有区分特定文件的id。
cpp
struct inode
{
// 所有文件的属性
int inode_number;
};
一般而言,一个文件的inode只有128字节,所以在存储的时候一个4kb的块可以容纳很多个文件的inode。
注意 :文件名不是文件的inode属性,它不在inode中存储,因为inode的大小是固定的就是128,而文件名是变长的,如果inode中存储文件名,就不好固定大小了。

如上图,前面的数字就是文件特定的inode_number的值。
下面是Linux内核中inode的核心代码。
cpp
/*
* 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)
那么如何找到文件的内容和属性呢?
首先在一个分组里面找到对应的文件,根据文件的inode号:inode_number就可以找到它的inode结构体,这个里面存储的就是文件的所有属性,然后这个属性中有一个i_block[]。
cpp
__le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
如上,这个数组存储着该文件的内容占据着Data Blocks中的那几个块,这几个块中存储的就是文件的内容,这样我们也就找到了文件内容。

如上,Data Blocks有很多数据块,有的数据块被占用了,有的没有,那么操作系统怎么知道哪个数据块没有被占用呢? 上图的左侧有一个Block BitMap,这个位图的比特位的位置代表着块编号,比特位的位置内容为0或为1代表这个块是否被占用。所以Block BitMap的本质是对Data Blocks做管理,它记录着Data Blocks中哪个数据块已经被占用,哪个数据块没有被占用。同理inode BitMap是用来管理inode Table的。
扩展 :所以在Linux系统中删除一个文件,只需要在inode BitMap和Block BitMap里将文件的数据由1置为0即可,所以我们的rm并没有真正删除这个文件!
2.4 GDT

GDT(Group Descriptor Table)是块组描述符表,它记录了所处块组的关键信息 。例如这个块组中从哪里开始是inode Table,从哪里开始是Data Blocks,空闲的inode和数据块还有多少个等等,有了GDT,所有存储位置一目了然!
Linux内核中的相关代码:
cpp
// 磁盘级blockgroup的数据结构
/*
* Structure of a blocks group descriptor 块组描述符表
*/
struct ext2_group_desc
{
__le32 bg_block_bitmap; /* Blocks bitmap block */
__le32 bg_inode_bitmap; /* Inodes bitmap */
__le32 bg_inode_table; /* Inodes table block*/
__le16 bg_free_blocks_count; /* Free blocks count */
__le16 bg_free_inodes_count; /* Free inodes count */
__le16 bg_used_dirs_count; /* Directories count */
__le16 bg_pad;
__le32 bg_reserved[3];
};
2.5 Super Block

上面我们所讲的都是在这一个小的块组内的管理,那还不够,我们还不知道这整个分区的使用管理情况。
Super Block它是文件系统的第一个元数据结构,包含了管理整个文件系统所需的所有全局信息 。它存放着文件系统本身的结构信息,描述整个分区的文件系统信息。记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息一旦被破坏,可以说整个文件系统结构就被破坏了。
超级块在每个块组的开头都有一份拷贝(第一个块组必须有,后面的块组可以没有)。 为了保证文件系统在磁盘部分扇区出现物理问题的情况下还能正常工作,就必须保证文件系统的Super Block信息在这种情况下能正常访问。所以一个文件系统的Super Block会在多个Block Group中进行备份,这些Super Block区域的数据保持一致。
cpp
struct ext2_super_block {
/* ============ 基本标识信息 ============ */
__le32 s_inodes_count; // 1. inode总数
__le32 s_blocks_count; // 2. 总块数
__le32 s_r_blocks_count; // 3. 保留块数(root专用)
__le32 s_free_blocks_count; // 4. 空闲块数
__le32 s_free_inodes_count; // 5. 空闲inode数
/* ============ 布局信息 ============ */
__le32 s_first_data_block; // 6. 第一个数据块号(通常是1)
__le32 s_log_block_size; // 7. 块大小对数(0=1KB,1=2KB,2=4KB)
__le32 s_log_frag_size; // 8. 片段大小对数(已废弃)
/* ============ 块组信息 ============ */
__le32 s_blocks_per_group; // 9. 每块组的块数
__le32 s_frags_per_group; // 10. 每块组的片段数
__le32 s_inodes_per_group; // 11. 每块组的inode数
/* ============ 时间信息 ============ */
__le32 s_mtime; // 12. 最后挂载时间
__le32 s_wtime; // 13. 最后写入时间
__le16 s_mnt_count; // 14. 挂载计数
__le16 s_max_mnt_count; // 15. 最大挂载次数(-1=无限)
/* ============ 签名和版本 ============ */
__le16 s_magic; // 16. 魔数(0xEF53)
__le16 s_state; // 17. 文件系统状态
__le16 s_errors; // 18. 错误处理方式
__le16 s_minor_rev_level; // 19. 次版本号
/* ============ 检查和修复 ============ */
__le32 s_lastcheck; // 20. 最后检查时间
__le32 s_checkinterval; // 21. 强制检查间隔
__le32 s_creator_os; // 22. 创建操作系统
__le32 s_rev_level; // 23. 主版本号
/* ============ 用户和组 ============ */
__le16 s_def_resuid; // 24. 默认保留用户ID
__le16 s_def_resgid; // 25. 默认保留组ID
/* ============ 扩展信息(EXT2后续版本)============ */
__le32 s_first_ino; // 26. 第一个非保留inode(通常是11)
__le16 s_inode_size; // 27. inode大小(字节)
__le16 s_block_group_nr; // 28. 本超级块所在块组
__le32 s_feature_compat; // 29. 兼容特性标志
__le32 s_feature_incompat; // 30. 不兼容特性标志
__le32 s_feature_ro_compat; // 31. 只读兼容特性标志
__u8 s_uuid[16]; // 32. 128位UUID(唯一标识)
char s_volume_name[16]; // 33. 卷标(最多16字符)
char s_last_mounted[64]; // 34. 最后挂载路径
__le32 s_algorithm_usage_bitmap; // 35. 压缩算法位图
/* ============ 性能优化 ============ */
__u8 s_prealloc_blocks; // 36. 预分配块数(文件)
__u8 s_prealloc_dir_blocks; // 37. 预分配块数(目录)
__le16 s_padding1; // 38. 填充
/* ============ 日志支持 ============ */
__u8 s_journal_uuid[16]; // 39. 日志UUID
__le32 s_journal_inum; // 40. 日志文件inode号
__le32 s_journal_dev; // 41. 日志设备号
__le32 s_last_orphan; // 42. 孤儿inode链表
/* ============ 哈希函数 ============ */
__le32 s_hash_seed[4]; // 43. 目录哈希种子
__u8 s_def_hash_version; // 44. 默认哈希版本
__u8 s_reserved_char_pad; // 45. 填充
/* ============ 其他扩展 ============ */
__le16 s_desc_size; // 46. 块组描述符大小
__le32 s_default_mount_opts; // 47. 默认挂载选项
__le32 s_first_meta_bg; // 48. 第一个元块组
__u8 s_reserved[760]; // 49. 保留区域
};
总结:
以上就是本期博客分享的全部内容啦!如果觉得文章还不错的话可以三连支持一下,你的支持就是我前进最大的动力!
技术的探索永无止境! 道阻且长,行则将至!后续我会给大家带来更多优质博客内容,欢迎关注我的CSDN账号,我们一同成长!
(~ ̄▽ ̄)~