Linux -- 文件【下】

一、EXT2文件系统

1、宏观认识

所有的准备⼯作都已经做完,是时候认识下⽂件系统了。我们想要在硬盘上储⽂件,必须先把硬盘格式化为某种格式的⽂件系统,才能存储⽂件。⽂件系统的⽬的就是组织和管理硬盘中的⽂件。

在Linux 系统中,最常⻅的是 ext2 系列的⽂件系统。其早期版本为 ext2,后来⼜发展出 ext3 和 ext4。ext3 和 ext4 虽然对 ext2 进⾏了增强,但是其核⼼设计并没有发⽣变化,我们仍是以较⽼的 ext2 作为演示对象。

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

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

ext2⽂件系统会根据分区的⼤⼩划分为数个Block Group。⽽每个Block Group都有着相同的结构组成。

2、块组内部构成

2.1 Data Block

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

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

2.2 i节点表(Inode Table)

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

2.3 块位图(Block Bitmap)

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

2.4 inode位图(Inode Bitmap)

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

2.5 GDT(Group Descriptor Table)

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

  • 块组内的inode表的块位置
  • 块组内的块位图和inode位图的块位置
  • 块组内已使用和空闲的块数对inode数
  • 块组的块分配策略信息
cpp 复制代码
// 磁盘级blockgroup的数据结构
struct ext2_group_desc
{
    __le32 bg_block_bitmap;  /* Blocks bitmap block */
    __le32 bg_inode_bitmap;  /* Inodes bitmap block */
    __le16 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;
    __le16 bg_reserved[3];
};

2.6 超级块(Super Block)

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

  • 文件系统的大小(总块数)
  • 每个块的大小
  • inode的大小和数量
  • 块组的数量

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

3、目录与文件名

问题:那目录呢?目录如何存储的呢?

目录也是按照前面我们说的方式存储的,也就是说目录也有自己的inode,无非就是在inode结构体里面使用type标记位来区分,不管是目录还是文件,其他都和文件一样。

那目录的内容存储什么啊?即目录文件的数据块存储什么呢?答案是文件名->inode映射关系。

所以在磁盘中保存文件就没有目录的概念,都是inode和数据,具体是文件还是目录,可以通过inode区分。因为磁盘只认inode,所以我们今天打开一个code.cc一定是先找到文件所处的目录然后根据code.cc,查找目录映射关系的inode,再根据inode确定分区和分组,加载inode属性内容。此时我们Is就看到文件属性!

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

4、路径解析

5、路径缓存

6、挂载分区

问题:我们有一个分区->格式化->能否使用这个分区了?答案是不能。我们可以通过Is /dev/vda* -l查看云服务器的磁盘,可以看到我们云服务器有3个/dev/vad分区,并且我们无法直接进入这个分区。为什么?因为我们的分区需要和一个特定目录进行关联,之后进入这个目录就相当于进入这个分区。这也叫做把目录和分区进行挂载! 接下来我们做一个试验来理解:

这里我们命令相当于从/dev/zero这个文件里面读取内容,然后把内容设置为0,写入到指定文件disk.img里面,写入块大小为1M。分区就是一个磁盘空间,形成一个文件就相当于形成一个磁盘空间!所以这里我们就有一个disk.img的5M的文件!

然后我们使用mkfs.ext4 命令格式化分区内容为ext4文件系统,写入文件系统本质就是清空两个位图,然后给分区分组。可以看到我们分配了1280个inode、1280个blocks。此时他已经被我们格式化为一块盘了。

然后此时我们使用 df-h 命令查看挂载情况,同时还有文件系统的使用率、分区大小等。发现我们的/dev/vda3 和根目录挂载了,所以我只要登录操作系统就会处在/dev/vda3分区。

然后我们可以使用 mount 命令,把分区挂载到 /mnt/mydisk 目录里面。此时我们 df-h 查看,发现确实已经挂载上了。

所以此时我们在/mnt/mydisk 创建文件,就是在disk.img所在的分区里面创建文件了。

我们要取消挂载,就可以使用 umont 命令,然后再 rm disk.img 即可。

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

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

所以结论就是:

7、文件名与inode映射实验

我们说目录在磁盘上的数据块保存的是文件名和inode的映射关系,现在我们写了一份代码,先调用opendir 打开一个文件目录,然后调用 readdir 就可以使用一个文件的描述符,然后返回一个结构体,这个结构体包含目录中文件的属性相关信息,然后把里面包含的文件名和inode映射关系打印出来 。

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.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;
}

我们发现确实和我们 Is -li 查看到的文件名和inode是一样的!说明我们的目录内容就是保存文件名和inode的映射关系!

8、inode和datablock映射

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

我们在ext2_inode上可以发现存在一个i_blocks数组,这个数组的大小是15,这个就是我们说的inode和数据块的映射数组!

如果我们的数组只填写数据块编号的15*4kb=60kb,而平时我们的随便一个文件都是M为单位的,60kb根本不够看啊! 所以这个数组只有前12个位置存储数据块编号映射关系,后面三个存储一级、二级、三级块索引指针 。

一级索引指针指向一个4kb的block,这4kb的block不是直接存储文件数据,而是存储文件数据块的地址。假设指针大小为4字节,就有了4kb/4字节=1024个数据块索引。二级索引指向的就是一级索引,以此类推所以我们就可以扩展我们保存的数据块索引!

9、 问题总结

通过上面的学习,我们就可以回答一下几个问题:

1. 如何理解创建一个空文件?

  1. 遍历inode Bitmap,找到比特位为0的位置,申请一个未被使用的inode。
  2. 将inode表中找到对应的inode, 并将文件的属性信息填到inode结构当中。
  3. 将该文件的文件名和inode指针添加到目录文件的数据块当中。

2. 如何理解向文件写入信息?

  1. 通过文件的inode编号找到对应的inode结构。
  2. 通过inode结构找到存储该文件内容的数据块,并将数据写入数据块。
  3. 若不存在数据块或者申请的数据块已经写满了,就需要遍历block Bitmap找到一个空的块号,并在数据区当中找到对应的空闲块,再把数据写入到数据块当中,最后还需要建立数据块和inode结构的对应关系。

3. 删除文件做了些什么?

  1. 将该文件对应的inode在Inode map当中设置为无效。
  2. 将该文件申请过的Data Block在Block map当中置为无效。

这也是我们为什么删除一个软件的速度比下载同一个软件的速度快的多的原因。当然因为文件内容并没有被删除,所以我们可以在对应内容被其他文件内容覆盖之前,通过一些技术手段复原已删除文件。

4. 如何理解目录?

目录也是一种文件,是文件就有对应的文件属性与文件内容,其中对应的文件属性就是我们的inode存储的就是目录的大小,目录的拥有者等。而对应的文件内容存储的就是该目录下的文件名以及对应文件的inode指针。


二、软硬链接

1、软连接

# 软链接 又叫做符号链接,软链接文件是一个独立的文件,该文件有自己的inode号,但是该文件只包含了源文件的路径名,所以软链接文件一般就是对应路径文件的一种快捷访问方式。其中在Windows系统中,我们桌面上软件图标就是访问对应程序的快捷方式,本质其实就是一个软连接文件。

在Linux中,我们可以通过指令ln -s 文件名 软链接名设置软连接:

我们向code.c追加重定向,发现通过软连接也可以查看code.c的内容,所以软连接其实就是类似我们的快捷方式!

通过快捷方式就可以找到我们的可执行程序,而我们的快捷方式是一个独立的文件,所以我们删除快捷方式并没有真正删除软件,所以我们软连接也是一个独立的文件,所以我们可以看到code.c和code-soft的inode不同!

并且我们也能通过指令unlink 软连接名取消对应的软连接,并且如果一旦删除软连接所指向的文件,那么该软连接文件也将毫无意义。

2、硬链接

硬链接 文件就是源文件的一个别名,它与源文件之间具有相同的inode大小。一旦为某个文件建立硬链接,那么对应的硬链接数就会加1。

在Linux中,我们可以通过指令ln 文件名 硬链接名建立对应的硬链接,同样我们也能通过unlink 硬链接名取消对应的硬链接。

硬链接没有独立的inode,并不是一个独立的文件, 本质是在特定的目录下,添加一个文件名和inode编号的映射关系。


三、文件时间-ACM

在Linux中,我们可以使用指令stat 文件名来查看对应文件的信息。

以下是对应关于文件时间的信息:

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

当我们修改文件内容时,文件的大小一般也会随之改变,所以一般情况下Modify的改变会影响Change一起改变,但修改文件属性一般不会影响到文件内容,所以一般情况下Change的改变不会影响Modify的改变。

我们若是想将文件的这三个时间都更新到最新状态,可以使用指令touch 文件名。


四、文件系统总结

下⾯⽤⼏张图总结,主要想从不同⻆度说明:

相关推荐
破刺不会编程4 小时前
socket编程UDP
linux·运维·服务器·网络·c++·网络协议·udp
NEXU510 小时前
Linux:套接字
linux·服务器·网络
morliz子轩11 小时前
基于WSL搭建Ubuntu 22.04.x LTS开发环境
linux·运维·ubuntu
Janspran12 小时前
嵌入式linux学习 -- 进程和线程
linux·运维·学习
FreeBuf_12 小时前
CERT/CC警告:新型HTTP/2漏洞“MadeYouReset“恐致全球服务器遭DDoS攻击瘫痪
服务器·http·ddos
Cosmoshhhyyy12 小时前
linux远程部署dify和mac本地部署dify
linux·运维·macos
麦兜*14 小时前
【swift】SwiftUI动画卡顿全解:GeometryReader滥用检测与Canvas绘制替代方案
服务器·ios·swiftui·android studio·objective-c·ai编程·swift
路多辛14 小时前
Debian新一代的APT软件源配置文件格式DEB822详解
linux·运维·ubuntu·debian
-VE-14 小时前
Linux线程控制
linux