Linux系统编程—Ext系列文件系统

Linux Ext文件系统详解

第一章:理解硬件

1-1 磁盘、 服务器、 机柜、 机房

  • 机械磁盘是计算机中唯一的一个机械设备
  • 磁盘--- 外设
  • 容量⼤,价格便宜

1-2 磁盘物理结构

1-3 磁盘的存储结构

扇区:是磁盘存储数据的基本单位,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 = 8064 MB (1MB = 1048576B)(若按1MB=1000000B来算就是8.4GB)

1-4 磁盘的逻辑结构

1-4-1 理解过程

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

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

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

1-4-2 真实过程

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

柱面是⼀个逻辑上的概念,其实就是每一面上,相同半径的磁道逻辑上构成柱面。
所以,磁盘物理上分了很多面,但是在我们看来,逻辑上,磁盘整体是由“柱面”卷起来的。

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

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

即:一维数组

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

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

整盘:

整个磁盘不就是多张二维的扇区数组表(三维数组?)
所以,寻址一个扇区:先找到哪一个柱面(Cylinder),再确定柱面内哪一个磁道(其实就是磁头位置,Head),再确定扇区(Sector),所以就有了 CHS。
我们之前学过 C/C++ 的数组,在我们看来,其实全部都是一维数组:

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

OS只需要使用LBA就可以了!!LBA地址转成CHS地址,CHS如何转换成为LBA地址。谁做啊??磁盘自己来做!固件(硬件电路,伺服系统)

1-5 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-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 的时候看到的除了看到文件名,还能看到文件元数据(属性)。

[root@localhost linux]# ls -l
总⽤量 12
-rwxr-xr-x. 1 root root 7438 "9⽉ 13 14:56" a.out
-rw-r--r--. 1 root root 654 "9⽉ 13 14:56" test.c

每行包含7列:

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

ls -l读取存储在磁盘上的文件信息,然后显示出来

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

[root@localhost linux]# stat test.c
File: "test.c"
Size: 654 Blocks: 8 IO Block: 4096 普通⽂件
Device: 802h/2050d Inode: 263715 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2017-09-13 14:56:57.059012947 +0800
Modify: 2017-09-13 14:56:40.067012944 +0800
Change: 2017-09-13 14:56:40.069012948 +0800

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

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

注意:

  • Linux 下文件的存储是属性和内容分离存储的。
  • Linux 下,保存文件属性的集合叫做 inode,一个文件对应一个 inode,inode 内有一个唯一的标识符,叫做 inode 号。

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

/*
* 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,又是如何放置的呢?文件系统就是为了组织管理这些的!

第三章: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)

存放文件系统本身的结构信息,描述整个分区的文件系统信息。记录的信息主要有:block 和 inode 的总量,未使用的 block 和 inode 的数量,一个 block 和 inode 的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block 的信息被破坏,可以说整个文件系统结构就被破坏了。

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

/*
* 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,从哪里开始是 DataBlocks,空闲的 inode 和数据块还有多少个等等。块组描述符在每个块组的开头都有一份拷贝。

// 磁盘级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];
};

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编号以分区为单位,整体划分,不可跨分区

inode编号

int_block[]

Inode_Table和struct_inode1

Inode_Table和struct_inode2

3-3-6 Data Block

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

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

Linux磁盘分区与块组结构解析

3-4 inode和datablock映射

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

/*
* 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 */
};
#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 的⼤⼩通常是 128 字节 或 256 字节

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

结论:

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

下⾯,通过touch一个新文件来看看如何工作

[root@localhost linux]# touch abc
[root@localhost linux]# ls -i abc
263466 abc

为了说明问题,我们将上图简化:

创建一个新文件主要有以下 4 个操作:

  1. 存储属性
    内核先找到一个空闲的 i 节点(这里是 263466)。内核把文件信息记录到其中。

  2. 存储数据
    该文件需要存储在三个磁盘块,内核找到了三个空闲块:300、500、800。将内核缓冲区的第一块数据复制到 300,下一块复制到 500,以此类推。

  3. 记录分配情况
    文件内容按顺序 300、500、800 存放。内核在 inode 上的磁盘分布区记录了上述块列表。

  4. 添加文件名到目录
    新的文件名 abc。Linux 如何在当前的目录中记录这个文件?内核将入口(263466,abc)添加到目录文件。文件名和 inode 之间的对应关系将文件名和文件的内容及属性连接起来。

3-5 目录与文件名

问题:

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

答案:

  • 目录也是文件,但是磁盘上没有目录的概念,只有文件属性 + 文件内容的概念。
  • 目录的属性不用多说,内容保存的是:文件名和 inode 号的映射关系。

所以,访问文件,必须打开当前目录,根据文件名,获得对应的 inode 号,然后进行文件访问。
所以,访问文件必须要知道当前工作目录,本质是必须能打开当前工作目录文件,查看目录文件的内容!

whb@bite:~/code/test/test$ pwd
/home/whb/code/test/test
whb@bite:~/code/test/test$ ls -li
total 24
1596260 -rw-rw-r-- 1 whb whb 814 Oct 28 20:32 test.c
比如:要访问test.c, 就必须打开test(当前工作目录),然后才能获取test.c对应的inode进而对文件进行访问。

3-6 路径解析

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

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

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

最终答案3:而实际上,任何文件都有路径。访问目标文件,比如:/home/whb/code/test/test/test.c
都要从根目录开始,依次打开每一个目录,根据目录名,依次访问每个目录下指定的目录,直到访问到 test.c。这个过程叫做 Linux 路径解析。

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

可是路径谁提供?
• 你访问文件,都是指令/工具访问,本质是进程访问,进程有 CWD(当前工作目录)!进程提供路径。
• 你 open 文件,提供了路径。

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

3-7 路径缓存

问题1:Linux 磁盘中,存在真正的目录吗?
答案:不存在,只有文件。只保存文件属性 + 文件内容。

问题2:访问任何文件,都要从 / 目录开始进行路径解析?
答案:原则上是,但是这样太慢,所以 Linux 会缓存历史路径结构。

问题3:Linux 目录的概念,怎么产生的?
答案:打开的文件是目录的话,由 OS 自己在内存中进行路径维护。
Linux 中,在内核中维护树状路径结构的内核结构体叫做:struct dentry

//代码块
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 一个实验

$ 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

注意:
/dev/loop0 在 Linux 系统中代表第一个循环设备(loop device)。循环设备,也被称为回环设备或者 loopback 设备,是一种伪设备(pseudo-device),它允许将文件作为块设备(block device)来使用。这种机制使得可以将文件(比如 ISO 镜像文件)挂载(mount)为文件系统,就像它们是物理硬盘分区或者外部存储设备一样。

whb@bite:/mnt$ ls /dev/loop* -l
brw-rw---- 1 root disk 7, 0 Oct 17 18:24 /dev/loop0
brw-rw---- 1 root disk 7, 1 Jul 17 10:26 /dev/loop1
brw-rw---- 1 root disk 7, 2 Jul 17 10:26 /dev/loop2
brw-rw---- 1 root disk 7, 3 Jul 17 10:26 /dev/loop3
brw-rw---- 1 root disk 7, 4 Jul 17 10:26 /dev/loop4
brw-rw---- 1 root disk 7, 5 Jul 17 10:26 /dev/loop5
brw-rw---- 1 root disk 7, 6 Jul 17 10:26 /dev/loop6
brw-rw---- 1 root disk 7, 7 Jul 17 10:26 /dev/loop7
crw-rw---- 1 root disk 10, 237 Jul 17 10:26 /dev/loop-control

3-8-2 一个结论

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

3-9 文件系统总结

下面用几张图总结,从不同角度说明。

第四章:软硬连接

4-1 硬链接

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

[root@localhost linux]# touch abc
[root@localhost linux]# ln abc def
[root@localhost linux]# ls -li abc def
263466 abc
263466 def
  • abc 和 def 的链接状态完全相同,他们被称为指向文件的硬链接。内核记录了这个连接数,inode263466 的硬连接数为 2。
  • 我们在删除文件时干了两件事情:1. 在目录中将对应的记录删除,2. 将硬连接数 -1,如果为 0,则将对应的磁盘释放。

4-2 软链接

硬链接是通过 inode 引用另外一个文件,软链接是通过名字引用另外一个文件,但实际上,新的文件和被引用的文件的 inode 不同。应用上常见可以想象成一个快捷方式。在 shell 中的做法:

[root@localhost linux]# ln -s abc.s abc
[root@localhost linux]# ls -li
263563 -rw-r--r--. 2 root root 0 9⽉ 15 17:45 abc
261678 lrwxrwxrwx. 1 root root 3 9⽉ 15 17:53 abc.s -> abc
263563 -rw-r--r--. 2 root root 0 9⽉ 15 17:45 def

acm
下面解释一下文件的三个时间:

  • Access:最后访问时间
  • Modify:文件内容最后修改时间
  • Change:属性最后修改时间

4-3 软硬连接对比

  • 软连接是独立文件
  • 硬链接只是文件名和目标文件inode的映射关系

4-4 软硬连接的用途

硬链接

  • ... 就是硬链接
  • 文件备份

软连接

  • 类似快捷方式

补充:

目录硬链接数

目录中点存在的原因

补充:打开文件和文件系统的文件产生关联

一个磁盘文件在被访问之前部分属性已经加载到内存了。 

内存磁盘交换单位

页号掩码解释

数据写入磁盘文字流程

数据写入磁盘流程图

数据写入磁盘分阶段解释

块号与基数的映射

数据从C标准库到磁盘的完整流程

进程管理文件与磁盘文件

进程、打开的文件、磁盘文件_的关系

作业

1. 查看文件file的inode号,正确的是:

A.ls -l file
B.ls -a file
C.ls -i file
D.ls -d file

答案:C
ls常见选项:

  • - l 表示查看文件详细信息
  • - a 表示查看所有包含以.开头的文件(隐藏文件)
  • - i 打印每个文件的inode索引号
  • - d 针对目录产生效果,表示查看目录文件自身信息,而并非目录内的文件信息

2. 下面关于Linux文件系统的inode描述错误的是:

A.inode和文件名是一一对应的
B.inode描述了文件大小和指向数据块的指针
C.通过inode可获得文件占用的块数
D.通过inode可实现文件的逻辑结构和物理结构的转换

答案:A
根据答案理解正确选项

  • A选项错误,这里说的文件名指的是文件的目录项,一个文件inode是有可能有多个目录项的,比如给一个文件创建多个硬链接,因此并非一一对应
  • B选项正确,inode中包含了文件的各种描述信息,权限,大小,时间属性,数据块指针....等等都包含在内
  • C选项正确,inode中包含了文件数据所占据的存储位置的信息,因此可以获得we你按占用的数据块数
  • D选项正确,inode就像是文件的一个整体的描述,有了这个描述,上层就可以重新组织虚拟逻辑结构,通过inode映射其物理结构(简单理解可以联想类似于虚拟地址空间与物理内存之间的页表)

3. Linux中包括两种链接:硬链接(Hard Link)和软连接(Soft Link),下列说法正确的是()

A.软连接可以跨文件系统进行连接,硬链接不可以
B.当删除原文件的时候软连接文件仍然存在,且指向的内容不变
C.硬链接被删除,磁盘上的数据文件会同时被删除
D.硬链接会重新建立一个inode,软链接不会

答案:A
软链接文件是一个独立的文件有自己的inode节点,文件中保存了源文件路径,通过数据中保存的源文件路径访问源文件
硬链接是文件的一个目录项,与源文件共用同一个inode节点,直接通过自己的inode节点访问源文件(其实本质上来说与源文件没区别)

  • A正确 不同分区有可能有不同文件系统,就算系统相同,也会导致节点号有歧义冲突,因此硬链接不能跨分区建立,正确
  • B错误 删除源文件,软链接文件失效
  • C错误 硬链接被删除,则inode中的链接数 - 1,并不会直接删除文件数据,而是等链接数为0的时候才会实际删除对应文件的inode,将所占用数据块置为空闲
  • D错误 硬链接与源文件共用inode

4. 使用In命令将生成了一个指向文件old的符号链接new,如果你将文件old删除,是否还能够访问文件中的数据?

A.不可能再访问
B.仍然可以访问
C.能否访问取决于文件的所有者
D.能否访问取决于文件的权限

答案:A
ln生成符号链接文件,指的是通过 ln -s 命令生成软链接文件,软链接文件是一个独立的文件,有自己的inode节点,这个文件数据中保存的是源文件路径,通过保存的路径访问源文件,因此源文件被删除则无法再访问,通过路径将找不到源文件,这时候软链接就会失效。
根据以上对于软链接的理解,A选项正确,其他选项都错误

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值