磁盘与文件系统

1.理解硬件

1.1磁盘,服务器,机柜,机房

服务器:

机柜:

机房:

1.1.1磁盘

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

机械磁盘的内部物理结构

机械硬盘是依赖机械运动 + 磁性存储的设备,内部是高度密封的无尘环境,核心组件如下:

  1. 盘片(Platter)
    • 硬盘的核心存储介质,通常由铝合金或玻璃制成基底,表面喷涂一层极薄的磁性材料薄膜(厚度仅几纳米)。
    • 硬盘内会有 1~5 片盘片同轴堆叠,每片盘片的上下两个表面都可以存储数据(除非被电机占用)。
    • 盘片以固定转速高速旋转(常见转速:5400 RPM、7200 RPM、10000 RPM、15000 RPM)。
    • 扇区:是磁盘存储数据的基本单位,512字节,块设备。
  2. 磁头(Head)
    • 负责读取 / 写入 盘片上的磁信号,体积微小(类似针尖),不会直接接触盘片表面,而是悬浮在盘片上方的气垫上(间隙约几微米)。
    • 每一个盘片表面都对应一个磁头,所有磁头被固定在同一个磁头臂上。
  3. 磁头臂与驱动电机(Actuator Arm & Voice Coil Motor, VCM)
    • 磁头臂连接所有磁头,由音圈电机驱动,可带动磁头在盘片的径向方向快速移动,定位到目标数据位置。
  4. 主轴电机(Spindle Motor)
    • 驱动盘片以恒定转速高速旋转,为磁头读写数据提供稳定的运动载体。
  5. 控制电路(PCB)
    • 硬盘的 "大脑",包含主控芯片、缓存芯片、驱动芯片等。
    • 主控芯片负责解析主机发送的指令、管理磁头和电机的运动、处理数据的编码 / 解码、错误校验等。
    • 缓存芯片用于临时存储高频访问的数据或待写入的数据,提升读写效率。
  6. 密封腔体
    • 盘片、磁头、磁头臂、主轴电机等核心部件都被封装在金属密封腔体内,防止灰尘进入导致磁头或盘片损坏。

磁盘的存储结构

向磁盘中写入数据:CHS地址定位

  1. 找到对应的扇区。
  2. 通过磁头(header)编号,找到在哪一面。
  3. 在当前面中的哪一个磁道(cylinder)。
  4. 确认当前磁道的第几个扇区(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地址(逻辑地址)

修正:

在读写数据时,是磁头在工作,其中一个磁头移动,其他磁头都会一起跟着移动,所以在确定扇区读写数据时,其实先定位的是在哪一个柱面上(不同盘面中相同半径的所有磁道)

磁盘读写数据时所有盘片同步移动

真实过程:

  1. 磁头摆动:确定要访问哪个柱面
  2. 盘片转动:使磁头定位到指定扇区(磁盘的转速==》磁盘的读写速率)
  3. 盘片转动最后选出的扇区数 = 盘片数 * 2;
  4. 最后选定特定的磁头,确定读写的扇区

磁盘在物理上分成了很多柱面,我们要把磁盘理解为由柱面卷成的柱形。

柱面:

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

磁盘:

  • 整个磁盘展开后就是一个三维数组
  • 在磁盘中定位一个扇区: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?

  1. 提高效率(主要)
  2. 软硬件解耦(次要)

如果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文件创建

  1. 文件创建时,系统在内存中创建一个struct inode,把文件的信息属性填好
  2. 把磁盘中的inode bitmap的数据块加载到内存
  3. 在内存中遍历inode bitmap找到为0的位置
  4. 把struct inode刷新到磁盘特定位置,把inode bitmap该位置编号的bit位置1.
  5. 向文件写内容时在block bitmap中找未被使用的数据块,在把数据块的编号保存在struct inode中。

3.1.2文件删除

  1. 文件创建时都会得到自己的inode编号。
  2. 通过inode编号在inode bitmap中找存储文件属性的inode table中的数据块。
  3. 在文件属性中找到i_block[EXT2_N_BLOCKS]获取文件内容的数据块编号
  4. 得到inode和block编号后,把inode bitmap和block bitmap中特定编号的bit位置0

3.1.3文件修改

  1. 通过inode编号找到该文件使用的data block编号,将这些数据块加载到内存,创建struct file
  2. 把结构体指针存储在进程的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下的快捷方式

相关推荐
十日十行1 小时前
Linux和window共享文件夹
linux
李广坤4 小时前
MySQL 大表字段变更实践(改名 + 改类型 + 改长度)
数据库
木心月转码ing8 小时前
WSL+Cpp开发环境配置
linux
爱可生开源社区1 天前
2026 年,优秀的 DBA 需要具备哪些素质?
数据库·人工智能·dba
蝎子莱莱爱打怪1 天前
Centos7中一键安装K8s集群以及Rancher安装记录
运维·后端·kubernetes
崔小汤呀1 天前
最全的docker安装笔记,包含CentOS和Ubuntu
linux·后端
随逸1771 天前
《从零搭建NestJS项目》
数据库·typescript
何中应1 天前
vi编辑器使用
linux·后端·操作系统
何中应1 天前
Linux进程无法被kill
linux·后端·操作系统
何中应1 天前
rm-rf /命令操作介绍
linux·后端·操作系统