磁盘与文件系统

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下的快捷方式

相关推荐
win x2 小时前
Redis集群
java·数据库·redis
夏沫mds2 小时前
基于hyperledger fabric的葡萄酒溯源系统
运维·fabric
水天需0102 小时前
Linux PS4 环境变量详解
linux
qq_12498707532 小时前
基于Spring Boot的“味蕾探索”线上零食购物平台的设计与实现(源码+论文+部署+安装)
java·前端·数据库·spring boot·后端·小程序
小新ya2 小时前
vscode增删改查文件,一直等待中...
linux·vscode
小李独爱秋2 小时前
计算机网络经典问题透视:电子邮件的安全协议PGP主要都包含哪些措施?
运维·服务器·网络·网络协议·计算机网络·安全
江上月5132 小时前
JMeter中级指南:从数据提取到断言校验全流程掌握
java·前端·数据库
晨旭缘2 小时前
零基础后端入门:JDK21 + PostgreSQL+Java项目
java·数据库·postgresql
萧曵 丶2 小时前
MySQL InnoDB 实现 MVCC 原理
数据库·mysql·mvcc