【Linux系统编程】Ext系列文件系统

【Linux系统编程】Ext系列文件系统

  • [1. 理解硬件](#1. 理解硬件)
    • [1.1 磁盘物理结构](#1.1 磁盘物理结构)
    • [1.2 磁盘的存储结构](#1.2 磁盘的存储结构)
    • [1.3 磁盘的逻辑结构](#1.3 磁盘的逻辑结构)
      • [1.3.1 理解过程](#1.3.1 理解过程)
      • [1.3.2 真实过程](#1.3.2 真实过程)
    • [1.4 CHS && LBA地址](#1.4 CHS && LBA地址)
  • [2. 引入文件系统](#2. 引入文件系统)
    • [2.1 引入"块"概念](#2.1 引入“块”概念)
    • [2.2 引入"分区"概念](#2.2 引入“分区”概念)
    • [2.3 引入"inode"概念](#2.3 引入“inode”概念)
  • [3. ext2 文件系统](#3. ext2 文件系统)
    • [3.1 宏观认识](#3.1 宏观认识)
    • [3.2 Block Group](#3.2 Block Group)
    • [3.3 块组内部构成](#3.3 块组内部构成)
      • [3.3.1 超级块(Super Block)](#3.3.1 超级块(Super Block))
      • [3.3.2 GDT(Group Descriptor Table)](#3.3.2 GDT(Group Descriptor Table))
      • [3.3.3 块位图(Block Bitmap)](#3.3.3 块位图(Block Bitmap))
      • [3.3.4 inode位图(Inode Bitmap)](#3.3.4 inode位图(Inode Bitmap))
      • [3.3.5 i节点表(Inode Table)](#3.3.5 i节点表(Inode Table))
      • [3.3.6 Data Block](#3.3.6 Data Block)
    • [3.4 inode和data block映射(弱化)](#3.4 inode和data block映射(弱化))
    • [3.5 目录与文件名](#3.5 目录与文件名)
    • [3.6 路径解析](#3.6 路径解析)
    • [3.7 路径缓存](#3.7 路径缓存)
    • [3.8 挂载分区](#3.8 挂载分区)
      • [3.8.1 一个实验](#3.8.1 一个实验)
      • [3.8.2 一个结论](#3.8.2 一个结论)
  • [4. 软硬链接](#4. 软硬链接)
    • [4.1 硬链接](#4.1 硬链接)
    • [4.2 软链接](#4.2 软链接)

1. 理解硬件

1.1 磁盘物理结构

1.2 磁盘的存储结构

扇区:是磁盘存储结构的基本单位,512字节,块设备

如何定位一个扇区呢?

  • 可以先定位磁头(header)
  • 确定磁头要访问哪一个柱面(磁道)(cylinder)
  • 定位一个扇区(sector)
  • CHS地址定位

文件 = 内容 + 属性 都是数据,无非就是占据哪几个扇区的问题!能定位一个扇区了,能不能定位多个扇区呢?

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

柱面(cylinder),磁头(head),扇区(sector),显然可以定位数据了,这就是数据定位(寻址)方式之⼀,CHS寻址方式。

CHS寻址

对早期的磁盘非常有效,知道用哪个磁头,读取哪个柱⾯上的第⼏扇区就可以读到数据了。

但是CHS模式⽀持的硬盘容量有限,因为系统用8bit来存储磁头地址,用10bit来存储柱面地址,⽤6bit来存储扇区地址,而⼀个扇区共有512Byte,这样使⽤CHS寻址⼀块硬盘最⼤容量为256 * 1024 * 63 * 512B = 8064MB(1MB = 1048576B)(若按1MB=1000000B来算就是8.4GB)

1.3 磁盘的逻辑结构

1.3.1 理解过程

磁带上面可以存储数据,我们可以把磁带"拉直",形成线性结构

那么磁盘本质上虽然是硬质的,但是逻辑上我们可以把磁盘想象成为卷在⼀起的磁带,那么磁盘的逻辑存储结构我们也可以类似于:

这样每一个扇区,就有了一个线性地址(其实就是数组下标),这种地址叫做LBA

1.3.2 真实过程

⼀个细节:传动臂上的磁头是共进退的

柱面是⼀个逻辑上的概念,其实就是每⼀面上,相同半径的磁道逻辑上构成柱面。

所以,磁盘物理上分了很多面,但是在我们看来,逻辑上,磁盘整体是由"柱面"卷起来的。

所以,磁盘的真实情况是:

磁道:

某⼀盘面的某⼀个磁道展开:

即:一维数组

柱面:

整个磁盘所有盘面的同一磁道,即柱面展开

  • 柱面上的每个磁道,扇区个数都是一样的
  • 这不就是二维数组吗

整盘

整个磁盘不就是多张二维的扇区数组表(三维数组?)

所以,寻址⼀个扇区:先找到哪⼀个柱面(Cylinder),再确定柱面内哪⼀个磁道(其实就是磁头位置,Head),再确定扇区(Sector),所以就有了CHS。

我们之前学过C/C++的数组,在我们看来,其实全部都是⼀维数组:

所以,每⼀个扇区都有⼀个下标,我们叫LBA(Logical Block Address)地址,其实就是线性地址。所以怎么计算得到这个LBA地址呢?

CHS如何转换成为LBA地址。谁做啊??磁盘自己来做!固件(硬件电路,伺服系统)

1.4 CHS && 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使用磁盘,就可以用⼀个数字访问磁盘扇区了。

2. 引入文件系统

2.1 引入"块"概念

其实硬盘是典型的"块"设备,操作系统读取硬盘数据的时候,其实是不会一个个扇区地读取,这样效率太低,而是⼀次性连续读取多个扇区,即⼀次性读取⼀个"块"(block)。

硬盘的每个分区是被划分为⼀个个的"块"。⼀个"块"的大小是由格式化的时候确定的,并且不可以更改,最常见的是4KB,即连续⼋个扇区组成⼀个"块"。"块"是文件存取的最小单位。

注意:

  • 磁盘就是⼀个三维数组,我们把它看待成为⼀个"⼀维数组",数组下标就是LBA,每个元素都是扇区
  • 每个扇区都有LBA,那么8个扇区⼀个块,每⼀个块的地址我们也能算出来。
  • 知道LBA:块号 = LBA / 8
  • 知道块号:LBA = 块号 * 8 + n.(n是块内第⼏个扇区)

2.2 引入"分区"概念

其实磁盘是可以被分成多个分区(partition)的,以Windows观点来看,你可能会有⼀块磁盘并且将它分区成C,D,E盘。那个C,D,E就是分区。分区从实质上说就是对硬盘的⼀种格式化。但是Linux的设备都是以文件形式存在,那是怎么分区的呢?

柱面是分区的最小单位,我们可以利⽤参考柱⾯号码的方式来进行分区,其本质就是设置每个区的起始柱⾯和结束柱⾯号码。此时我们可以将硬盘上的柱⾯(分区)进⾏平铺,将其想象成⼀个⼤的平面,如下图所示:

注意:

柱面大小一致,扇区个数⼀致,那么其实只要知道每个分区的起始和结束柱⾯号,知道每⼀个柱⾯多少个扇区,那么该分区多⼤,其实和解释LBA是多少也就清楚了.

2.3 引入"inode"概念

之前我们说过 文件 = 数据 + 属性,我们使用 ls -l 的时候除了看到文件名,还能看到文件元数据(属性)。

每行包含7列:

  • 模式
  • 硬链接数
  • 文件所有者
  • 大小
  • 最后修改时间
  • 文件名

其实这个信息除了通过这种方式来读取,还有⼀个stat命令能够看到更多信息

到这我们要思考⼀个问题,文件数据都储存在"块"中,那么很显然,我们还必须找到⼀个地方储存文件的元信息(属性信息),比如文件的创建者、⽂件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为"索引节点"。

每⼀个文件都有对应的inode,里面包含了与该文件有关的⼀些信息。为了能解释清楚inode,我们需要深入了解⼀下文件系统。

注意:

Linux下文件的存储是属性和内容分离存储的

Linux下,保存文件属性的集合叫做inode,⼀个文件,⼀个inode,inode内有⼀个唯⼀的标识符,叫做inode号

所以⼀个文件的属性inode长什么样子呢?

c 复制代码
/*
 * 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字节

任何文件的内容大小可以不同,但是属性大小⼀定是相同的

到目前为止,相信大家还有两个问题:

  1. 我们已经知道硬盘是典型的"块"设备,操作系统读取硬盘数据的时候,读取的基本单位是"块"。"块"又是硬盘的每个分区下的结构,难道"块"是随意的在分区上排布的吗?那要怎么找到"块"呢?
  2. 还有就是上面提到的存储文件属性的inode,又是如何放置的呢?

文件系统就是为了组织管理这些的!

3. ext2 文件系统

3.1 宏观认识

所有的准备工作都已经做完,是时候认识下文件系统了。我们想要在硬盘上存储文件,必须先把硬盘格式化为某种格式的文件系统,才能存储文件。文件系统的目的就是组织和管理硬盘中的文件。在Linux 系统中,最常见的是ext2系列的文件系统。其早期版本为ext2,后来又发展出ext3和ext4。ext3 和ext4虽然对ext2进行了增强,但是其核心设计并没有发⽣变化,我们仍是以较⽼的ext2作为演示对象。

ext2文件系统将整个分区划分成若⼲个同样大小的块组(Block Group),如下图所示。只要能管理⼀个分区就能管理所有分区,也就能管理所有磁盘文件。

上图中启动块(Boot Block/Sector)的大小是确定的,为1KB,由PC标准规定,⽤来存储磁盘分区信息和启动信息,任何文件系统都不能修改启动块。启动块之后才是ext2文件系统的开始。

3.2 Block Group

ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成。

3.3 块组内部构成

3.3.1 超级块(Super Block)

存放文件系统本身的结构信息,描述整个分区的文件系统信息。记录的信息主要有:bolck 和 inode 的总量,未使用的 block 和 inode 的数量,⼀个 block 和 inode 的大小,最近⼀次挂载的时间,最近⼀次写

入数据的时间,最近⼀次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了

超级块在每个块组的开头都有⼀份拷贝(第⼀个块组必须有,后⾯的块组可以没有)。为了保证文件系统在磁盘部分扇区出现物理问题的情况下还能正常⼯作,就必须保证文件系统的super block信息在这种情况下也能正常访问。所以⼀个文件系统的super block会在多个block group中进行备份,这些super block区域的数据保持⼀致。

c 复制代码
/*
 * Structure of the super block
 */
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.3.2 GDT(Group Descriptor Table)

块组描述符表,描述块组属性信息,整个分区分成多个块组就对应有多少个块组描述符。每个块组描述符存储⼀个块组的描述信息,如在一个块组中从哪⾥开始是inode Table,从哪⾥开始是Data Blocks,空闲的inode和数据块还有多少个等等。块组描述符在每个块组的开头都有⼀份拷贝。

c 复制代码
/*
 * Structure of a blocks group descriptor
 */
struct ext2_group_desc
{
	__le32	bg_block_bitmap;	/* Blocks bitmap block */
	__le32	bg_inode_bitmap;	/* Inodes bitmap block */
	__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;			/* Padding to 32 bits */
	__le32	bg_reserved[3];		/* Reserved for future use */
};

3.3.3 块位图(Block Bitmap)

Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用

3.3.4 inode位图(Inode Bitmap)

每个bit表示⼀个inode是否空闲可用。

3.3.5 i节点表(Inode Table)

  • 存放文件属性如文件大小,所有者,最近修改时间等
  • 当前分组所有Inode属性的集合
  • inode编号以分区为单位,整体划分,不可跨分区

3.3.6 Data Block

数据区:存放文件内容,也就是⼀个⼀个的Block。根据不同的文件类型有以下几种情况:

  • 对于普通文件,文件的数据存储在数据块中。
  • 对于目录,该目录下的所有文件名和目录名存储在所在目录的数据块中,除了文件名外,ls -l命令看到的其它信息保存在该文件的inode中。
  • Block 号按照分区划分,不可跨分区

3.4 inode和data block映射(弱化)

  • inode内部存在 __le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */ = 15,就是⽤来进⾏inode和block映射的
  • 这样文件 = 内容 + 属性,就都能找到了。

思考:

请解释:知道inode号的情况下,在指定分区,请解释:对文件进行增、删、查、改是在做什么?
结论:

  • 分区之后的格式化操作,就是对分区进行分组,在每个分组中写⼊SB、GDT、Block Bitmap、Inode Bitmap等管理信息,这些管理信息统称:文件系统
    只要知道⽂件的inode号,就能在指定分区中确定是哪⼀个分组,进⽽在哪⼀个分组确定是哪⼀个inode
    拿到inode⽂件属性和内容就全部都有了

3.5 目录与文件名

问题:

  • 我们访问文件,都是用的文件名,没用过inode号啊?
  • ⽬录是文件吗?如何理解?

答案:

  • 目录也是文件,但是磁盘上没有目录的概念,只有文件属性 + 文件内容的概念。
  • 目录的属性不用多说,内容保存的是:文件名和Inode号的映射关系
c 复制代码
// readdir.c
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
	if (argc != 2) {
		fprintf(stderr, "Usage: %s <directory>\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	DIR *dir = opendir(argv[1]); // 系统调用,自行查阅
	if (!dir) {
		perror("opendir");
		exit(EXIT_FAILURE);
	}

	struct dirent *entry;
	while ((entry = readdir(dir)) != NULL) { // 系统调用,自行查阅
		// Skip the "." and ".." directory entries
		if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
			continue;
		}

		printf("Filename: %s, Inode: %lu\n", entry->d_name, (unsigned long)entry->d_ino);
	}

	closedir(dir);
	return 0;
}

所以,访问文件,必须打开当前目录,根据文件名,获得对应的inode号,然后进行文件访问

所以,访问文件必须要知道当前⼯作目录,本质是必须能打开当前⼯作目录文件,查看目录文件的内容!

比如:要访问readdir.c,就必须打开lesson22(当前工作目录),然后才能获取readdir.c对应的inode,进而对文件进行访问。

3.6 路径解析

问题:打开当前工作目录文件,查看当前工作目录文件的内容?当前工作目录不也是文件吗?我们访问当前工作目录不也是只知道当前工作目录的文件名吗?要访问它,不也得知道当前工作目录的inode吗?

答案1:所以也要打开:当前工作目录的上级目录,...,上级目录不也是目录吗??不还是上面的问题吗?

答案2:所以类似"递归",需要把路径中所有的目录全部解析,出口是"/"根目录。

最终答案3:而实际上,任何文件,都有路径,访问目标文件,比如: /home/lsb/linux/lesson22

都要从根目录开始,依次打开每⼀个目录,根据目录名,依次访问每个目录下指定的目录,直到访问到readdir.c。这个过程叫做Linux路径解析。

注意:

  • 所以,我们知道了:访问文件必须要有 目录 + 文件名 = 路径 的原因
  • 根目录固定文件名,inode号,无需查找,系统开机之后就必须知道

可是路径谁提供?

  • 你访问文件,都是指令/工具访问,本质是进程访问,进程有CWD!进程提供路径。
  • 你open文件,提供了路径

可是最开始的路径从哪⾥来?

  • 所以Linux为什么要有根目录,根目录下为什么要有那么多缺省目录?
  • 你为什么要有家目录,你自己可以新建目录?
  • 上面所有行为:本质就是在磁盘文件系统中,新建目录文件。而你新建的任何文件,都在你或者系统指定的目录下新建,这不就是天然就有路径了嘛!
  • 系统 + 用户 共同构建Linux路径结构.

3.7 路径缓存

问题1:Linux磁盘中,存在真正的目录吗?

答案:不存在,只有文件。只保存文件属性 + 文件内容

问题2:访问任何文件,都要从/目录开始进行路径解析?

答案:原则上是,但是这样太慢,所以Linux会缓存历史路径结构

问题3:Linux目录的概念,怎么产生的?

答案:打开的文件是目录的话,由OS自己在内存中进行路径维护

Linux中,在内核中维护树状路径结构的内核结构体叫做:struct dentry

c 复制代码
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结构,包括普通⽂件。这样所有被打开的文件,就可以在内存中形成整个树形结构
  • 整个树形节点也同时会隶属于LRU(Least Recently Used,最近最少使用)结构中,进行节点淘汰
  • 整个树形节点也同时会隶属于Hash,方便快速查找
  • 更重要的是,这个树形结构,整体构成了Linux的路径缓存结构,打开访问任何文件,都要先在这棵树下根据路径进行查找,找到就返回属性inode和内容,没找到就从磁盘加载路径,添加dentry结构,缓存新路径

3.8 挂载分区

我们已经能够根据inode号在指定分区找文件了,也已经能根据目录文件内容,找指定的inode了,在指定的分区内,我们可以为所欲为了。可是:

问题:inode不是不能跨分区吗?Linux不是可以有多个分区吗?我怎么知道我在哪⼀个分区???

3.8.1 一个实验

注意:
/dev/loop0在Linux系统中代表第⼀个循环设备(loop device)。循环设备,也被称为回环设备或者loop back设备,是⼀种伪设备(pseudo-device),它允许将文件作为块设备(block device)来使用。这种机制使得可以将文件(比如ISO镜像文件)挂载(mount)为

文件系统,就像它们是物理硬盘分区或者外部存储设备⼀样

3.8.2 一个结论

  • 分区写入文件系统,无法直接使用,需要和指定的目录关联,进行挂载才能使用。
  • 所以,可以根据访问目标文件的"路径前缀"准确判断我在哪⼀个分区。

4. 软硬链接

4.1 硬链接

我们看到,真正找到磁盘上文件的并不是文件名,而是inode。其实在linux中可以让多个文件名对应于同⼀个inode。

  • test.c和test-hard的链接状态完全相同,他们被称为指向文件的硬链接。内核记录了这个链接数,inode 790970 的硬链接数为2。
  • 我们在删除文件时干了两件事情:1.在目录中将对应的记录删除,2.将硬链接数-1,如果为0,则将对应的磁盘释放。
  • 硬链接只是文件名和目标文件inode的映射关系
  • .和...就是硬链接
  • 硬链接的用途:文件备份

4.2 软链接

软链接是通过名字引用另外⼀个文件,新的文件和被引用的文件的inode不同,可以把软链接想象成⼀个快捷方式。

相关推荐
q***01771 小时前
Linux 下安装 Golang环境
linux·运维·golang
zhangphil2 小时前
Android宽高不均等Bitmap缩放为指定宽高FitCenter到正方形Bitmap,Kotlin
android·kotlin
企鹅侠客2 小时前
Linux性能调优使用strace来分析文件系统的性能问题
linux·运维·服务器
别或许2 小时前
13.用户管理
android
奔跑吧邓邓子3 小时前
CentOS 7性能飞升秘籍:实战系统优化与调优
linux·运维·centos·实战·系统优化·性能调优
qinyia3 小时前
WisdomSSH如何高效检查服务器状态并生成运维报告
linux·运维·服务器·数据库·人工智能·后端·ssh
q***96584 小时前
springboot3整合knife4j详细版,包会!(不带swagger2玩)
android·前端·后端
laocooon5238578865 小时前
实现了一个新闻数据采集与分析系统python
linux·服务器·windows
海棠蚀omo5 小时前
解读Linux进程的“摩尔斯电码”:信号产生的原理与实践,掌控进程的生死时速
linux·操作系统