1.理解硬件
1.1磁盘,服务器,机柜,机房
服务器:

机柜:


机房:

1.1.1磁盘
- 磁盘是一种机械设备,分企业级磁盘和桌面级磁盘。
- 磁盘在冯诺依曼系统中属于外设
- IO速率慢
- 容量大,价格便宜

机械磁盘的内部物理结构




机械硬盘是依赖机械运动 + 磁性存储的设备,内部是高度密封的无尘环境,核心组件如下:
- 盘片(Platter)
- 硬盘的核心存储介质,通常由铝合金或玻璃制成基底,表面喷涂一层极薄的磁性材料薄膜(厚度仅几纳米)。
- 硬盘内会有 1~5 片盘片同轴堆叠,每片盘片的上下两个表面都可以存储数据(除非被电机占用)。
- 盘片以固定转速高速旋转(常见转速:5400 RPM、7200 RPM、10000 RPM、15000 RPM)。
- 扇区:是磁盘存储数据的基本单位,512字节,块设备。
- 磁头(Head)
- 负责读取 / 写入 盘片上的磁信号,体积微小(类似针尖),不会直接接触盘片表面,而是悬浮在盘片上方的气垫上(间隙约几微米)。
- 每一个盘片表面都对应一个磁头,所有磁头被固定在同一个磁头臂上。
- 磁头臂与驱动电机(Actuator Arm & Voice Coil Motor, VCM)
- 磁头臂连接所有磁头,由音圈电机驱动,可带动磁头在盘片的径向方向快速移动,定位到目标数据位置。
- 主轴电机(Spindle Motor)
- 驱动盘片以恒定转速高速旋转,为磁头读写数据提供稳定的运动载体。
- 控制电路(PCB)
- 硬盘的 "大脑",包含主控芯片、缓存芯片、驱动芯片等。
- 主控芯片负责解析主机发送的指令、管理磁头和电机的运动、处理数据的编码 / 解码、错误校验等。
- 缓存芯片用于临时存储高频访问的数据或待写入的数据,提升读写效率。
- 密封腔体
- 盘片、磁头、磁头臂、主轴电机等核心部件都被封装在金属密封腔体内,防止灰尘进入导致磁头或盘片损坏。
磁盘的存储结构
向磁盘中写入数据:CHS地址定位
- 找到对应的扇区。
- 通过磁头(header)编号,找到在哪一面。
- 在当前面中的哪一个磁道(cylinder)。
- 确认当前磁道的第几个扇区(sector)。
CHS寻址:
对早期的磁盘⾮常有效,知道⽤哪个磁头,读取哪个柱⾯上的第⼏扇区就可以读到数据了。
但是CHS模式⽀持的硬盘容量有限,因为系统⽤8bit来存储磁头地址,⽤10bit来存储柱⾯地
址,⽤6bit来存储扇区地址,⽽⼀个扇区共有512Byte,这样使⽤CHS寻址⼀块硬盘最⼤容量
为256 * 1024 * 63 * 512B = 8064 MB(1MB = 1048576B)(若按1MB=1000000B来算就是
8.4GB)
disks -l命令

- 扇区是从磁盘读出和写入信息的最小单位,通常大小为512字节。
- 磁头((head)数:每个盘片一般有上下两面,分别对应1个磁头,共2个磁头
- 磁道(track)数:磁道是从盘片外圈往内圈编号0磁道,1磁道,靠近主轴的同心圆用于停靠磁头,不存储数据
- 柱面(cylinder)数:磁道构成柱面,数量上等同于磁道个数
- 扇区(sector)数:每个磁道都被切分成很多扇形区域,每道的扇区数量相同
- 圆盘(platter)数:就是盘片的数量
- 磁盘容量=磁头数×磁道(柱面)数×每道扇区数×每扇区字节数
- 细节:传动臂上的磁头是共进退的(这点比较重要,后面会说明)
磁盘的逻辑抽象

磁带也是一种存储数据的结构,磁带上面带有磁性,可以存储二进制的0/1,当磁带没有被使用时,全都被缠绕在塑料柱子上,形成一个个的同心圆,当工作时,或我们把磁带拆下来,磁带会被拉成直的,但其中存储的数据不变,形成一种线性结构。
磁盘的盘片也是一个个的同心圆的磁道,虽然我们没办法将这些磁道在物理层面上一个个的拉直,但我们可以这些磁道想象成是连续的
抽象:我们把最内圈的磁道拿下来,拉直方平,再把下一圈磁道拿下来,拉直,连接在第一个磁道的末尾,以此类推,一个盘面中的所有磁道我们都可以想象成一个连续的线性结构



- 一个盘面我们可以把它想象为一个一维数组
- 数组的下标就是扇区的编号
- 一整个磁盘有很多盘面,每一个盘面是一个一维数组,再把这些一维数组连接起来,这样整个磁盘我们都可以把它理解成一个一维数组
- 数组中的每一个元素就是一个扇区
- 所以定位一个扇区,只需要一个数组下标
- 这种地址被称为LBA地址(逻辑地址)
修正:

在读写数据时,是磁头在工作,其中一个磁头移动,其他磁头都会一起跟着移动,所以在确定扇区读写数据时,其实先定位的是在哪一个柱面上(不同盘面中相同半径的所有磁道)
磁盘读写数据时所有盘片同步移动
真实过程:
- 磁头摆动:确定要访问哪个柱面
- 盘片转动:使磁头定位到指定扇区(磁盘的转速==》磁盘的读写速率)
- 盘片转动最后选出的扇区数 = 盘片数 * 2;
- 最后选定特定的磁头,确定读写的扇区
磁盘在物理上分成了很多柱面,我们要把磁盘理解为由柱面卷成的柱形。
柱面:

- 一个柱面贯通多个盘面
- 柱面上的每一个磁道,扇区个数是一样的
磁盘:

- 整个磁盘展开后就是一个三维数组
- 在磁盘中定位一个扇区:arr[柱面cylinder][磁头head][扇区sector]
- 此时才是真正的CHS寻址,CHS本质就是三维数组的三个下标
再进一步抽象:
再C/C++中,不管是一维,二维,还是三维数组,我们都是以一维数组对待的
例如:第3行,第4列,高为5,位置的元素
映射在一维数组下标:index = (4*(总行数)*(总列数))+2*(总列数)+4;
在实际内存中不管是几维的数组,都是线性结构。
最终磁盘被抽象为一维数组

此时该数组的下标才是LBA
LBA地址 && CHS
CHS转成LBA:
-
·磁头数*每磁道扇区数=单个柱面的扇区总数
-
LBA=柱面号C*单个柱面的扇区总数+磁头号H*每磁道扇区数+扇区号S-1
-
即:LBA=柱面号C*(磁头数*每磁道扇区数)+磁头号H*每磁道扇区数+扇区号S-1
-
扇区号通常是从1开始的,而在LBA中,地址是从O开始的
-
柱面和磁道都是从0开始编号的
-
·总柱面,磁道个数,扇区总数等信息,在磁盘内部会自动维护,上层开机的时候,会获取到这些参数。
LBA转成CHS:
-
·柱面号C=LBA//(磁头数*每磁道扇区数)【就是单个柱面的扇区总数】
-
磁头号H=(LBA%(磁头数*每磁道扇区数))//每磁道扇区数
-
扇区号S =(LBA%每磁道扇区数)+1
-
"//":表示除取整
所以:从此往后,在磁盘使用者看来,根本就不关心CHS地址,而是直接使用LBA地址,磁盘内部自己转换。
所以:
从现在开始,磁盘就是一个元素为扇区的一维数组,数组的下标就是每一个扇区的LBA地址。OS使用磁盘,就可以用一个数字访问磁盘扇区了。
2.引入文件系统
2.1引入块概念
其实硬盘是典型的"块"设备,数据读写时以扇区为单位。操作系统读取硬盘数据时,其实不会一个个扇区的读取。 因为每读一个扇区都要出发一次硬件访问,可硬件的读写速率是非常慢的,所以操作系统会一次性访问多个扇区,即一次读取一个"块"。
硬盘的每个分区是被划分为一个个的"块"。一个"块"的大小是由格式化的时候确定的,并且不可
以更改,最常见的是4KB,即连续八个扇区组成一个"块"。"块"是文件存取的最小单位。
注意:
- 磁盘就是一个三维数组,我们把它看待成为一个"一维数组",数组下标就是LBA,每个元素都是扇区
- 每个扇区都有LBA,那么8个扇区一个块,每一个块的地址我们也能算出来。
- 知道LBA:块号=LBA/8
- 知道块号:LAB=块号*8+n.(n是块内第几个扇区)

为什么操作系统不直接用扇区访问磁盘呢?4kb?
- 提高效率(主要)
- 软硬件解耦(次要)
如果OS使用扇区的概念,那么磁盘的扇区大小变了,比如扇区变成1kb,那么操作系统也要跟着变。OS使用块概念,磁盘扇区大小改变,只需要改变转化算法即可。
2.2引入"分区"概念(分治)
磁盘的存储容量都比较大,扇区的数量太多,OS直接管理整个磁盘比较难,所以磁盘会被分成多个"区",就像国家被分为很多省来管理,每一个分区管理好,合起来整个磁盘也就管理好了。
以Windows观点来看,⼀块磁盘将它分区成C,D,E盘。那个C,D,E就是分区。分区从实质上说就是对硬盘的⼀种格式化。
以linux系统来看:柱面是分区的最小单位,我们可以利用参考柱面号码的方式来进行分区,其本质就是设置每个区的起始柱面和结束柱面号码。此时我们可以将硬盘上的柱面(分区)进行平铺,将其想象成一个大的平面。

注意:
柱面大小一致,扇区个数一致,那么其实只要知道每个分区的起始和结束柱面号,知道每一个柱面多少个扇区,那么该分区多大,其实和解释LBA是多少也就清楚了。
分区后还是比较大,就可以继续分组管理
最后管理整块磁盘变为管理好其中一个分组。
虽然分区分组可以使OS管理磁盘变得更容易,但是分出来组变多了。
例如:把一个国家分成非常多的省份去管理,每个省份在分成多个市,市的面积比较小相对便于管理,但是省已经够多了,每个省还要分成多个市,所以就会出现非常多的市长,那么我们的国家就要对这些省长和市长进行管理。
先描述,再组织。管理分组。
2.3对每一个分组内部进行管理

1. linux系统一切皆文件,对分组管理也是以文件形式管理的
2.linux系统,文件的内容和属性分开存储
3.一个组中应该包含什么内容
- 文件数据
- 管理文件的管理信息
**Super Block,GDT,Block Bitmap,inode Bitmap :**这4个存储空间用来存储文件的管理信息。
**inode Table:**保存文件的属性。
**Data Blocks:**保存文件的内容。
每一个组都有自己的管理信息,每一个组的管理信息只对自己的组负责。
即使一个组中一个文件都没有,这个组的管理信息也要提前写进来。
向组中写入管理信息的过程叫做"写入文件系统",也叫做"格式化"。
3.文件系统
文件系统中的文件管理数据只占整个文件系统(其中一个分组)中的一小部分。绝大部分都是Data Blocks(文件内容)。
Data Blocks
data blocks是一个个的4kb的数据块,用来存储文件的内容,比如一个文件的内容有16kb,那么就会在该区域选取4个数据块来存储,每一个数据块都有自己的编号。
inode Table
- 文件的属性也是数据,被保存在inode table区域。
- 文件属性在Linux内核中有一个名为struct inode的结构体来保存。
- inode结构体的大小是固定的,128字节
- 文件名不属于inode。
- 理论上一个文件有一个inode,struct inode中会有一个inode编号
- inode table区域也是由4kb的数据块构成
- inode结构体中不包括文件名,这就使得不同文件的inode大小是固定的,便于将inode table看作一个struct inode数组。
- 文件的inode中有一个i_block[]的数组,其中存储的就是data blocks的编号

结构体中都是int类型的数据,通过二次解析,找到具体的文件属性。

每一行的第一个数据就是文件的Inode值
文件=inode + data block
在一个分组里,找到一个文件,根据文件的inode number -> inode -> i_block[] ->datablock
block bitmap
它是一张位图,bit位的位置是datablock的编号,位图中的内容为0/1是该编号的数据块是否被使用。
inode bitmap
每个bit表⽰⼀个inode是否空闲可⽤
删除一个文件:把该文件inode对应的位图置0,再把文件使用的所有数据块号在block bitmap中置0。删除文件只需要做位图操作即可
GDT
块组描述符表,描述块组属性信息,整个分区分成多个块组就对应有多少个块组描述符。每个块组描述符存储一个块组的描述信息,如在这个块组中从哪里开始是inodeTable,从哪里开始是Data
Blocks,空闲的inode和数据块还有多少个等等。块组描述符在每个块组的开头都有一份拷贝。
cpp
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];
};
- 从哪个块号开始是block bitmap,inode bitmap,inode table,data blocks等
- 整个分组中有多少空闲的块,有多少空闲的inode
- 管理整个分组的描述表
super block
存放文件系统本身的结构信息,描述整个分区的文件系统信息。记录的信息主要有:bolck和inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。SuperBlock的信息被破坏,可以说整个文件系统结构就被破坏了
超级块在每个块组的开头都有一份拷贝(第一个块组必须有,后面的块组可以没有)。为了保证文
件系统在磁盘部分扇区出现物理问题的情况下还能正常工作,就必须保证文件系统的superblock信
息在这种情况下也能正常访问。所以一个文件系统的superblock会在多个blockgroup中进行备份,
这些super block区域的数据保持一致
cpp
struct ext2_super_block {
__le32 s_inodes_count; /* Inodes count */
__le32 s_blocks_count; /* Blocks count */
__le32 s_r_blocks_count; /* Reserved blocks count */
__le32 s_free_blocks_count; /* Free blocks count */
__le32 s_free_inodes_count; /* Free inodes count */
__le32 s_first_data_block; /* First Data Block */
__le32 s_log_block_size; /* Block size */
__le32 s_log_frag_size; /* Fragment size */
__le32 s_blocks_per_group; /* # Blocks per group */
__le32 s_frags_per_group; /* # Fragments per group */
__le32 s_inodes_per_group; /* # Inodes per group */
__le32 s_mtime; /* Mount time */
__le32 s_wtime; /* Write time */
__le16 s_mnt_count; /* Mount count */
__le16 s_max_mnt_count; /* Maximal mount count */
__le16 s_magic; /* Magic signature */
__le16 s_state; /* File system state */
__le16 s_errors; /* Behaviour when detecting errors */
__le16 s_minor_rev_level; /* minor revision level */
__le32 s_lastcheck; /* time of last check */
__le32 s_checkinterval; /* max. time between checks */
__le32 s_creator_os; /* OS */
__le32 s_rev_level; /* Revision level */
__le16 s_def_resuid; /* Default uid for reserved blocks */
__le16 s_def_resgid; /* Default gid for reserved blocks */
/*
* These fields are for EXT2_DYNAMIC_REV superblocks only.
*
* Note: the difference between the compatible feature set and
* the incompatible feature set is that if there is a bit set
* in the incompatible feature set that the kernel doesn't
* know about, it should refuse to mount the filesystem.
*
* e2fsck's requirements are more strict; if it doesn't know
* about a feature in either the compatible or incompatible
* feature set, it must abort and not try to meddle with
* things it doesn't understand...
*/
__le32 s_first_ino; /* First non-reserved inode */
__le16 s_inode_size; /* size of inode structure */
__le16 s_block_group_nr; /* block group # of this superblock */
__le32 s_feature_compat; /* compatible feature set */
__le32 s_feature_incompat; /* incompatible feature set */
__le32 s_feature_ro_compat; /* readonly-compatible feature set */
__u8 s_uuid[16]; /* 128-bit uuid for volume */
char s_volume_name[16]; /* volume name */
char s_last_mounted[64]; /* directory where last mounted */
__le32 s_algorithm_usage_bitmap; /* For compression */
/*
* Performance hints. Directory preallocation should only
* happen if the EXT2_COMPAT_PREALLOC flag is on.
*/
__u8 s_prealloc_blocks; /* Nr of blocks to try to preallocate*/
__u8 s_prealloc_dir_blocks; /* Nr to preallocate for dirs */
__u16 s_padding1;
/*
* Journaling support valid if EXT3_FEATURE_COMPAT_HAS_JOURNAL set.
*/
__u8 s_journal_uuid[16]; /* uuid of journal superblock */
__u32 s_journal_inum; /* inode number of journal file */
__u32 s_journal_dev; /* device number of journal file */
__u32 s_last_orphan; /* start of list of inodes to delete */
__u32 s_hash_seed[4]; /* HTREE hash seed */
__u8 s_def_hash_version; /* Default hash version to use */
__u8 s_reserved_char_pad;
__u16 s_reserved_word_pad;
__le32 s_default_mount_opts;
__le32 s_first_meta_bg; /* First metablock block group */
__u32 s_reserved[190]; /* Padding to the end of the block */
};
3.1文件的创建,删除,修改,查找----inode角度
cpp
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 */
};
3.1.1文件创建
- 文件创建时,系统在内存中创建一个struct inode,把文件的信息属性填好
- 把磁盘中的inode bitmap的数据块加载到内存
- 在内存中遍历inode bitmap找到为0的位置
- 把struct inode刷新到磁盘特定位置,把inode bitmap该位置编号的bit位置1.
- 向文件写内容时在block bitmap中找未被使用的数据块,在把数据块的编号保存在struct inode中。
3.1.2文件删除
- 文件创建时都会得到自己的inode编号。
- 通过inode编号在inode bitmap中找存储文件属性的inode table中的数据块。
- 在文件属性中找到i_block[EXT2_N_BLOCKS]获取文件内容的数据块编号
- 得到inode和block编号后,把inode bitmap和block bitmap中特定编号的bit位置0
3.1.3文件修改
- 通过inode编号找到该文件使用的data block编号,将这些数据块加载到内存,创建struct file
- 把结构体指针存储在进程的struct file数组中,进程就可以通过fd来对文件内容进行修改
3.2理解目录----文件名保存位置
我们对文件的"读改写"都是使用文件名,但是文件名不在inode中保存。
linux下一切皆文件,目录也是文件,也有自己的inode number,也有文件内容和属性

目录的内容
目录也有自己的data block,目录保存:当前目录下的文件名和inode编号的映射表。
所以在对文件进行操作时需要有文件名和目录
文件名+目录=相对或绝对路径
3.3重谈inode编号和块号
- inode编号和块号:不是组内有效,而是整个分区内唯一的,这两个编号可以跨组,但是不能跨分区。
- 例如:第一个组中有10000个数据块,可能该组的inode table中有编号为10001的数据块编号
- 在一个分区内部,一个文件系统内部,有多少inode,有多少数据块,都是固定的,都是提前设计好的。
特殊情况:
如果一个分区中大文件特别多会出现inode table还有,但是文件无法被创建。
如果小文件特别多,会出data block还有很多,inode table没有剩余,也无法创建新的文件。
- 操作系统查找一个文件,拿到inode后不是直接到该分区的组0中开始找,而是将inode除以每一个组的inode编号数,直接到特定组中开始找。
- 可能存在一个文件的inode在分区的第一个组,而该文件的块号是最后一个组的情况
3.4路径解析和路径缓存
路径解析:
访问一个文件的内容或属性的过程
- 首先要做的是打开当前目录的内容,访问当前目录的数据块,找到要访问文件的inode
- 要访问当前目录的数据块,就要拿到当前目录的inode
- 想要拿到当前目录的inode,就要访问上级目录的数据块,拿到当前目录的inode.
- 要访问上级目录的数据块,就要拿到上级目录的inode
- 然后访问上上级的数据块,循环往复。
- 直到访问到根目录,根目录的inode是已知的,可以直接根据根目录的inode,访问其数据块,找到下一级目录的inode,以此循环,直到找到目标文件的inode
- 所以要打开一个文件,就要把该文件的绝对路径上的所有目录文件都打开一遍。类似于递归。
当我们要访问任何文件时,linux内核都要为我们做"从/开始的路径解析",所以访问文件必须要有路径。
路径缓存:
我们每次访问文件并不是次次都要做路径解析。
- 对于用户访问过的路径,linux是会做路径缓存的
- 用户在访问文件后,文件的路径上的节点都会被缓存(打开的目录文件在内存中,不会被轻易的关闭)。
- 这些在内存中的路径节点(打开的目录文件),有些是刚打开的,有些路径节点是被高频使用的,有些路径节点被使用一次后不再被访问;即将被淘汰的节点。
所以操作系统就要对这些路径节点的缓存进行管理(先描述,再组织)。
在linux内核中,维护树状路径结构的内核结构体叫做:struct dentry
cpp
struct dentry {
atomic_t d_count;
unsigned int d_flags; /* protected by d_lock */
spinlock_t d_lock; /* per dentry lock */
struct inode *d_inode; /* Where the name belongs to - NULL is
* negative */
/*
* The next three fields are touched by __d_lookup. Place them here
* so they all fit in a cache line.
*/
struct hlist_node d_hash; /* lookup hash list */
struct dentry *d_parent; /* parent directory */
struct qstr d_name;
struct list_head d_lru; /* LRU list */
/*
* d_child and d_rcu can share memory
*/
union {
struct list_head d_child; /* child of parent list */
struct rcu_head d_rcu;
} d_u;
struct list_head d_subdirs; /* our children */
struct list_head d_alias; /* inode alias list */
unsigned long d_time; /* used by d_revalidate */
struct dentry_operations *d_op;
struct super_block *d_sb; /* The root of the dentry tree */
void *d_fsdata; /* fs-specific data */
#ifdef CONFIG_PROFILING
struct dcookie_struct *d_cookie; /* cookie, if any */
#endif
int d_mounted;
unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */
};
- 用户每打开一个目录,在内核中都会创建一个dentry的对象
- 可以看到dantry中有一个struct inode* d_inode的结构体指针
- 还包含了当前文件的文件名:struct qstr d_name;
- linux内核会在内存中构建一颗dentry多叉树缓存


- 上面的dentry多叉树,是linux目录结构的子集
- dentry多叉树就是路径缓存。
- dentry多叉树是会动态变化的,可能变多可能变少
- 并不是只有目录有dentry结构体,普通文件也有自己的dentry结构体,在dentry的多叉树中属于叶子节点
- 访问文件,搜索文件,可能第一次比较卡顿,第二次就快了,路上的路径节点被缓存到内存中了
访问任何文件都要有路径,那么路径怎么获取的?
- 访问文件,都是指令/工具访问,本质是进程访问,进程有CWD!进程提供路径。
- open文件,提供了路径
最开始的路径从哪里来?
- 所以Linux为什么要有根目录,根目录下为什么要有那么多缺省目录?
- 为什么要有家目录?自己可以新建目录?
- 上面所有行为:本质就是在磁盘文件系统中,新建目录文件。而你新建的任何文件,都在你或者系统指定的目录下新建,就是天然就有路径了
- 系统+用户共同构建Linux路径结构
存储大文件问题?
如果文件太大,一个分组保存不下怎么办。


一个文件inode结构体中有一个数据块数组,存储保存文件内容用到的数据块编号。

- i_block数组中确实只有15个元素,但这15个元素是不一样的。
- 前12个直接保存对应数据块的块号
- 规定前12个数组内容指向的数据块直接保存文件内容
- 第13个(下标为12)数组内容保存的为一级间接块索引:说白了也是一个数据块号,但是该数据块不直接保存文件内容,而是保存其他块号,该数据块共可以索引到4096/4=1024个用来直接保存文件内容的数据块。
- 第14个(下标为13)数组内容保存二级间接块索引:该数据块中保存的都是一级间接块的块号,也就是1024*1024个直接保存文件内容的块。
- 第15个间接索引了1024*1024*1024个直接保存文件内容的数据块。
- 其次块号是在整个分区有效。
3.5挂载分区
分区格式化本质是向分区中写入管理信息,也就是写入文件系统。
当分区完成格式化后,这个文件系统或分区并不能直接使用,还需要把分区或文件系统挂载到指定的目录下。

- vda这个磁盘被分为了两个分区vda1和vda2
- 其中vda2被挂载(Mounted on)在"/"根目录下。
- 只要我在/根目录下进行的文件操作都是在使用vda2这个分区
证明:
bash
$ dd if=/dev/zero of=./disk.img bs=1M count=5 #制作⼀个⼤的磁盘块,就当做⼀个分区
$ mkfs.ext4 disk.img # 格式化写⼊⽂件系统
$ mkdir /mnt/mydisk # 建⽴空⽬录
$ df -h # 查看可以使⽤的分区
Filesystem Size Used Avail Use% Mounted on
udev 956M 0 956M 0% /dev
tmpfs 198M 724K 197M 1% /run
/dev/vda1 50G 20G 28G 42% /
tmpfs 986M 0 986M 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 986M 0 986M 0% /sys/fs/cgroup
tmpfs 198M 0 198M 0% /run/user/0
tmpfs 198M 0 198M 0% /run/user/1002
$ sudo mount -t ext4 ./disk.img /mnt/mydisk/ # 将分区挂载到指定的⽬录
$ df -h
Filesystem Size Used Avail Use% Mounted on
udev 956M 0 956M 0% /dev
tmpfs 198M 724K 197M 1% /run
/dev/vda1 50G 20G 28G 42% /
tmpfs 986M 0 986M 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 986M 0 986M 0% /sys/fs/cgroup
tmpfs 198M 0 198M 0% /run/user/0
tmpfs 198M 0 198M 0% /run/user/1002
/dev/loop0 4.9M 24K 4.5M 1% /mnt/mydisk
$ sudo umount /mnt/mydisk # 卸载分区
whb@bite:/mnt$ df -h
Filesystem Size Used Avail Use% Mounted on
udev 956M 0 956M 0% /dev
tmpfs 198M 724K 197M 1% /run
/dev/vda1 50G 20G 28G 42% /
tmpfs 986M 0 986M 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 986M 0 986M 0% /sys/fs/cgroup
tmpfs 198M 0 198M 0% /run/user/0
tmpfs 198M 0 198M 0% /run/user/1002
3.6文件系统总结




4.软硬链接
4.1硬链接
添加硬链接

- 文件属性,读写可执行后的数字就是硬链接数
什么是硬链接

- 硬链接和原文件的inode是相同的
- 本质其实是同一个文件,因为一个文件一个Inode
- 硬链接本质就是在当前目录下(在目录文件内容中)添加"一个字符串(文件名)与目标文件inode(这个数字)的映射关系"
- 硬链接数表示有多少文件名指向这个inode号
- 删除一个文件:硬链接数从1减少到0时,OS才会真正删除这个文件,类似于"引用计数"。

硬链接有什么用
- 为一个重要文件做备份,避免误删。
- 一个文件有两个硬链接,删除其中一个,文件其实没有被真正删除,只是硬链接数-1.
目录硬链接数为2


当前目录硬链接数为3:上级目录中有leasson12,当前目录的" . " , dir目录中的" .. "
如果当前目录再新建一个目录,当前目录的硬链接数会+1.
linux系统规定:OS不能给目录做硬链接。
会形成环状路径,此时查找文件会造成死循环,导致系统崩溃。" . "和" .. "系统可以处理好这种环形关系,".. 和."是隐藏文件名,当查询文件时OS会自动忽略,做了特殊处理。
4.2软链接

- 软链接文件是一个独立的文件,有自己的inode
- 有自己的内容+属性
- 软链接内容保存的是目标文件对应的路径。
为什么要有软链接
软链接类型于windows下的快捷方式