初识文件系统

1、概述

文件存储在硬盘上,硬盘的最小存储单位叫做“扇区”(Sector)。每个扇区储存512字节

操作系统读取硬盘的时候,不会一个个扇区的读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个“块”(block)。这种由多个扇区组成的“块”,是文件存取的最小单位。“块”的大小,最常见的是4KB,即连续八个sector组成一个block。

操作系统与内存进行交互时,是按照页为基本单位的,一页也就等于4KB,这里和block的大小一致,我猜应该是为了方便对齐吧。

文件=文件属性+文件内容

文件内容都存在于block中,而文件属性存放在文件控制块中,在Linux下又称为inode(index node),它主要就存放文件的创建者、文件的创建日期、文件的大小等信息。

以ext系列文件系统为例,看看它们主要包含哪些结构:

在这里插入图片描述

Boot Block:文件系统的起始位置,存储了一些重要的引导信息,包括文件系统的元数据和加载程序等。

每一个Block Group都是一个分区,每个分区都可以实现不同的文件系统,这里是以ext系列的文件系统为例。

Superblock(超级块):在ext2/ext3/ext4文件系统中,Superblock存储了文件系统的整体信息,如文件系统的类型、大小、空闲和已填满的块数量等。Superblock通常位于文件系统的第一个块组中。

Group Descriptor Table(组描述符表):描述了文件系统中每个块组的属性,如块组的块大小、块数量等。每个块组都有一个与之关联的组描述符,存储在组描述符表中。

Block BitMap:用于记录Block块的使用情况。

inode BitMap:用于记录inode的使用情况。

Inode Table(索引节点表):在ext2/ext3/ext4文件系统中,Inode Table存储了文件系统的索引信息,包括文件的元数据(如文件大小、创建时间等)和文件数据块的地址。

Data Blocks(数据块):存储了文件系统的实际数据,这些数据块是文件系统中的基本存储单元。

以上的数据都是会占用磁盘空间的

2、inode结构

前面谈到文件=文件属性+文件内容

一般来说,文件属性和内容都是分开存储的,存放属性的结构又叫文件控制块,在Linux下,文件控制块就是inode

以下是Linux-4.9.145中定义的,文件系统ext4的inode结构体

//linux-4.9.145\fs\ext4\ext4.h
/*
 * Structure of an inode on the disk
 */
struct ext4_inode {
	__le16	i_mode;		/* File mode */
	__le16	i_uid;		/* Low 16 bits of Owner Uid */
	__le32	i_size_lo;	/* Size in bytes */
	__le32	i_atime;	/* Access time */
	__le32	i_ctime;	/* Inode Change 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_lo;	/* Blocks count */
	__le32	i_flags;	/* File flags */
	union {
		struct {
			__le32  l_i_version;
		} linux1;
		struct {
			__u32  h_i_translator;
		} hurd1;
		struct {
			__u32  m_i_reserved1;
		} masix1;
	} osd1;				/* OS dependent 1 */
	__le32	i_block[EXT4_N_BLOCKS];/* Pointers to blocks */
	__le32	i_generation;	/* File version (for NFS) */
	__le32	i_file_acl_lo;	/* File ACL */
	__le32	i_size_high;
	__le32	i_obso_faddr;	/* Obsoleted fragment address */
	union {
		struct {
			__le16	l_i_blocks_high; /* were l_i_reserved1 */
			__le16	l_i_file_acl_high;
			__le16	l_i_uid_high;	/* these 2 fields */
			__le16	l_i_gid_high;	/* were reserved2[0] */
			__le16	l_i_checksum_lo;/* crc32c(uuid+inum+inode) LE */
			__le16	l_i_reserved;
		} linux2;
		struct {
			__le16	h_i_reserved1;	/* Obsoleted fragment number/size which are removed in ext4 */
			__u16	h_i_mode_high;
			__u16	h_i_uid_high;
			__u16	h_i_gid_high;
			__u32	h_i_author;
		} hurd2;
		struct {
			__le16	h_i_reserved1;	/* Obsoleted fragment number/size which are removed in ext4 */
			__le16	m_i_file_acl_high;
			__u32	m_i_reserved2[2];
		} masix2;
	} osd2;				/* OS dependent 2 */
	__le16	i_extra_isize;
	__le16	i_checksum_hi;	/* crc32c(uuid+inum+inode) BE */
	__le32  i_ctime_extra;  /* extra Change time      (nsec << 2 | epoch) */
	__le32  i_mtime_extra;  /* extra Modification time(nsec << 2 | epoch) */
	__le32  i_atime_extra;  /* extra Access time      (nsec << 2 | epoch) */
	__le32  i_crtime;       /* File Creation time */
	__le32  i_crtime_extra; /* extra FileCreationtime (nsec << 2 | epoch) */
	__le32  i_version_hi;	/* high 32 bits for 64-bit version */
	__le32	i_projid;	/* Project ID */
};

一些相关字段的说明:

i_mode:该字段表示文件的访问权限和类型。它包括读取、写入和执行权限的信息,以及条目是否为常规文件、目录、符号链接等
i_uid和i_gid:分别存储文件所有者的用户ID(UID)和组ID(GID)
i_size_lo:文件的大小
i_atime:记录最后一次访问文件的时间
i_ctime:记录最后一次修改文件属性(大小,创建时间,创建者等信息)的时间/或者叫做最后一次修改inode的时间 (i_mtime的改变必然会引起i_ctime的改变,因为文件的大小发生了改变)
i_mtime:记录最后一次修改文件内容的时间
i_dtime:文件删除的事件
i_links_count:硬链接的数量
i_blocks_lo:使用了多少的Block块
__le32 i_block[EXT4_N_BLOCKS]:一个指针数组,里面的元素是指向Block块的物理地址

在Linux下,使用命令 stat + 文件名,就可以查看文件的详细信息

在这里插入图片描述

Size: 文件的大小,以字节为单位。
Blocks: 被用于存储文件的数据块的数量。
IO Block: 这是用于文件读写的内存块的大小。
Device: 文件所在的设备。
Inode: 文件的inode编号。
Links: 硬链接的数量。
Access: 最后一次访问文件的时间。
Modify: 最后一次修改文件的时间。
Change: 最后一次更改文件的inode信息的时间。
Birth: 文件的创建时间,因为test.c没有被记录(所以显示为"-")。

在Linux中,每创建一个文件,都会与一个inode对应,并且Linux系统内部不使用文件名,而是使用inode编号识别文件,文件名和inode编号的映射关系是存放在数据块中(Block),对于系统来说文件名只是inode编号便于识别的别称。除此之外,inode编号就对应着inode table的下标。

查看一个文件的inode编号

在这里插入图片描述

通过下面这张图,就能进一步的理解它们之间的关系:

在这里插入图片描述

因为文件的大小不会仅仅就使用一个block,也就是4kb,有可能会使用很多个block,因此ext4_inode中有这样一个字段,__le32 i_block[EXT4_N_BLOCKS],它是一个指针数组,用来存放block的地址的,但这个数组的大小仅仅为15

/*
 * Constants relative to the data blocks
 */
#define	EXT4_NDIR_BLOCKS		12
#define	EXT4_IND_BLOCK			EXT4_NDIR_BLOCKS
#define	EXT4_DIND_BLOCK			(EXT4_IND_BLOCK + 1)
#define	EXT4_TIND_BLOCK			(EXT4_DIND_BLOCK + 1)
#define	EXT4_N_BLOCKS			(EXT4_TIND_BLOCK + 1)

所以对多只能存储15*block(4个kb)=60kb的数据,如果存储的数据大于60kb呢?如何存储呢?
那么在最后一个block中,会存放下一个block的地址,如果下一个block也不够用,那么它又会存放下下一个block的地址,以此类推,就形成了链式结构。

并且ext4_inode中,i_blocks_loi_size_lo分别表示使用的block块数和总的文件大小(包括inode(大小固定,一般为128字节)和blocks),因此在读取文件时,就能知道是否读到了最后一个block,并且最后一个block应该读取多少字节

假设一个文件需要用到17个block,那么它的存储形式如下:

在这里插入图片描述

此外还有一个问题?

想要查找一个文件,就需要找到它的inode编号,而文件名称和inode编号的映射关系是存放在当前目录下的block中的,这样就需要查找当前目录,而当前目录的名称和inode编号的映射关系又存放在上级目录的block中,这样就会形成递归查找,总会查找根目录。然而根目录已经没有上级目录,那么就无法查找到根目录的inode节点?

例如现在有一个文件fl.txt,文件内容为hello fl,目录结构为 /root/fl.txt,如果想要查看这个文件,就必须递归式的查找,直到查找到根目录为止

在这里插入图片描述

但是根目录没有上级目录,如何知道根目录的inode编号呢?

为了解决这个问题,大家就将根目录的inode编号设定为一个固定值,在Linux系统中,根目录的inode编号通常是2,Linux下文件系统的都必须遵守这个规定

3、block BitMap 和 inode BitMap

在创建文件时,无论是申请inode还是block,是如何知道对应的inode或者block是否被占用呢?
这里就采用了bitmap这样的数据结构,通过二进制的0和1判断是否被占用,具体的原理可以看一下我的另外一篇博客位图的实现原理

block BitMap用于判断block是否被占用

inode BitMap 用于判断inode是否被占用

BitMap只能快速判断某个block或者inode是否被使用,如果想要申请空闲的block或者inode,就需要遍历整个bitmap,时间复杂度就是O(n),对此内核一定做了其他的优化,例如使用hash结构,这个我暂时不知道,等后面知道了,再回来补充。

因此对应创建文件test.c,本质就是修改位图+填充inode信息

  1. 遍历inode bitmap位图结构,找到空闲数据块;
  2. 把test.c的inode填入inode数据块里,由于是空文件所以不需要block block。

而需要写入内容时:根据其inode找到它对应的空间,发现为空,就通过遍历block bitmap位图结构找到空闲的block,然后把该block的地址写入到inode结构中的i_block数组中,然后就把文件内容写到block即可。

对于删除文件而言

并不需要清空inode和block的内容,只需要修改两个位图,由1->0即可“删除该文件”,这也就将该位置定义为可覆盖的文件数据;这也就是为什么平时删除文件是可以恢复的。

4、软链接和硬链接

为解决文件的共享使用,Linux 系统引入了两种链接:硬链接 (hard link) 与软链接(又称符号链接,即 soft link 或 symbolic link)。链接为 Linux 系统解决了文件的共享使用,还带来了隐藏文件路径、增加权限安全及节省存储等好处。

4.1 硬链接

linux下的文件是通过索引节点(Inode)来识别文件的,硬链接可以认为是一个指针,指向文件索引节点的指针,系统并不为它重新分配inode。每添加一个一个硬链接,ext4_inode结构中的i_links_count就会增加1

硬连接之间没有主次之分,删除某个硬链接,只是将其从目录的数据块中删除相关信息,并且文件链接数减一。不会从inode表中删除inode,除非只剩下一个链接数。

举例说明:

#创建一个目录
[root@VM-4-4-centos ~]# mkdir hardlink
#查看该目录的文件详细信息,可以看到对应的硬链接数为2
[root@VM-4-4-centos ~]# stat hardlink/
  File: ‘hardlink/’
  Size: 4096      	Blocks: 8          IO Block: 4096   directory
Device: fd01h/64769d	Inode: 451124      Links: 2
Access: (0755/drwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2023-12-12 08:53:26.917803512 +0800
Modify: 2023-12-12 08:53:26.917803512 +0800
Change: 2023-12-12 08:53:26.917803512 +0800
 Birth: -
#进入该目录,并创建文件
[root@VM-4-4-centos ~]# cd hardlink/
[root@VM-4-4-centos hardlink]# touch fl.txt
#查看该文件的硬链接数,为1
[root@VM-4-4-centos hardlink]# ll fl.txt 
-rw-r--r-- 1 root root 0 Dec 12 08:54 fl.txt
#向fl.txt写内容
[root@VM-4-4-centos hardlink]# echo "hell fl" > fl.txt 
[root@VM-4-4-centos hardlink]# cat fl.txt 
hell fl
#为fl.txt创建一个硬链接,再次查看文件的硬链接数,结果都为2,并且它们的inode编号都是一样的
[root@VM-4-4-centos hardlink]# ln fl.txt fl.link
[root@VM-4-4-centos hardlink]# ll -i
total 8
422632 -rw-r--r-- 2 root root 8 Dec 12 08:56 fl.link
422632 -rw-r--r-- 2 root root 8 Dec 12 08:56 fl.txt
#删除fl.txt,fl.link依旧可以正常查看内容
[root@VM-4-4-centos hardlink]# rm -f fl.txt 
[root@VM-4-4-centos hardlink]# cat fl.link 
hell fl
#再次查看fl.link的硬链接数,结果为1
[root@VM-4-4-centos hardlink]# ll
total 4
-rw-r--r-- 1 root root 8 Dec 12 08:56 fl.link

当我们删除fl.txt时,只是把hardlink所对应的block里面的fl.txt和所对应的inode编号的映射关系给删除了,但此时还有fl.link和所对应的inode编号的映射关系,因此我们依旧能找到fl.link所对应的inode,以及inode对应的block,从而就能找到里面的数据。如图所示:

在这里插入图片描述

通过这张图,同时也验证了在Linux下是使用inode编号识别文件,而非文件名

当删除了fl.txt之后,fl.link的硬链接数为1,如果再将fl.link也删除,对应的inode也会被回收。具体的操作就是,在block BitMap中,将inode所使用的block所对应的位置改为0,表示改block块空闲,再将inode结构中的i_blocks_lo改为0,表示inode不再关联任何block,最终在inode BitMap,将inode所对应的位置改为0,表示该inode空闲。

这也说明了,Linux下删除文件,只是将inode和block所处BitMap的位置,从1改为0,表示空闲,并解除inode和block的关联,而真正的数据是不会被删除的,直到新数据的覆盖,因此Linux中被删除的文件数据是可以被找回的,只要相应的数据块没有被再次覆盖使用

这里还有一个问题,为什么目录hardlink的硬链接数为2呢?

因为在hardlink目录下,有一个名为.的隐藏目录,而该目录也就是当前目录的意思。即hardlink目录。所以才会有两个链接数

[root@VM-4-4-centos hardlink]# ll -a
total 12
drwxr-xr-x   2 root root 4096 Dec 12 09:00 .
dr-xr-x---. 39 root root 4096 Dec 12 08:53 ..
-rw-r--r--   1 root root    8 Dec 12 08:56 fl.link

硬链接存在的问题

无法跨分区,跨设备创建硬链接

[root@VM-4-4-centos hardlink]# df -h .
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        40G   33G  4.8G  88% /
#当前目录所在的分区为/dev/vda1,在分区/sys/下,为当前目录下的fl.link建立一个硬链接,结果为失败
[root@VM-4-4-centos hardlink]# ln fl.link /sys/link
ln: failed to create hard link ‘/sys/link’ => ‘fl.link’: Invalid cross-device link

因为每个分区都有自己的文件系统,inode的查找规则也不一样。假设A分区的文件在B分区做了一个硬链接,此时访问B分区的此链接,按照我们想的是需要它访问A分区的inode,进行数据查询,但是它只会根据B分区的inode,在B数据块中查找数据。就相当于数据库中的两张表,你不可能拿A表的主键去在B表中查找数据。

无法创建目录的硬链接

[root@VM-4-4-centos ~]# ln hardlink/ hardlink.link
ln: ‘hardlink/’: hard link not allowed for directory

无法创建目录的硬链接,因为这可能把目录树变为环形图。例如假设存在目录 /A/B.link 和 /B/A.link。如果B.link是B目录的硬链接,A.link是A目录的硬链接。那A.link既然是/A的链接,那它里面肯定有B.link。同理B.link里面肯定有A.link。这样依次循环 /A/B.link/A.link/B.link/A.link/…。就造成了死循环的现象。

4.2 软链接

为了克服硬链接的这些限制,从而引入了软链接,也称符号链接。它相当于我们 Windows 中的快捷方式,即如果你软链接一个目录,只是一个目录的快捷方式到指定位置,操作系统找这个快捷方式会直接找到真实目录下的文件。

举例说明:

[root@VM-4-4-centos hardlink]# touch test.txt
[root@VM-4-4-centos hardlink]# echo "hello fl" > test.txt 
#为test.txt创建一个软链接
[root@VM-4-4-centos hardlink]# ln -s test.txt test.slink
[root@VM-4-4-centos hardlink]# ll -i
total 4
422669 lrwxrwxrwx 1 root root 8 Dec 12 10:17 test.slink -> test.txt
422658 -rw-r--r-- 1 root root 9 Dec 12 10:16 test.txt

如上可以看出,软连接与原文件并不是同一inode,链接数也没有增加。并且文件大小也不一样。

因为软链接的inode指向的block保存的是原文件的路径,如果保存的不是路径,而是文件名,默认会在软链接所在路径查找

在这里插入图片描述

当原文件被删除,软链接里面存放的路径也就是失效了,软链接从而也就失效了

在这里插入图片描述

创建软链接时,既能使用绝对路径创建,也能使用相对路径创建

当使用相对路径创建软链接时,需要注意如下问题:

在这里插入图片描述

在上图中,通过 ln -s …/test.txt /tmp/test.slink,在/tmp下创建了软链接,当试图通过test.slink查看test.txt文件的内容时,发现竟然报错,原因是找不到test.txt。因为/tmp/test.slink在指向…/test.txt 的过程中,它会以自己的路径为初始点去寻找test.txt。即 /tmp/test.slink -> …/test.txt,在系统看来,它会理解成以test.slink所在路径为起点,回到上一级目录,去寻找test.txt,很显然,上级目录不存在test.txt,所以报错

创建的软连接,指向的文件,默认会以软链接的路径为主,去寻找指向的文件,所以创建时,请以软链接的路径作为起点路径 去写原文件的相对路径

而由于软链接 inode指向的数据块只保存 原文件的地址字符串,所以可以跨分区、跨设备创建,并且文件夹也可以创建。当然如果原文件被删除了,链接则也会失效

5、Linux下的文件类型

  1. 普通文件,硬链接也是普通文件,文件标识符为-
  2. 目录,文件标识符为d
  3. 软链接(符号链接),文件标识符为l
  4. 面向块的设备文件,例如磁盘,驱动器、网卡,文件标识符为b
  5. 面向字符的设备文件,例如/dev/下的tty0、tty1、tty2等,它们就对应着键盘的每个字符,文件表示符为c
  6. 管道(匿名管道)和命名管道,文件标识符为p
  7. socket,文件标识符为s

即使是磁盘,驱动器,网卡,显示器这样的硬件设备,在Linux下也将其抽象成文件,这就符合Linux下一切皆文件的概念

  • 20
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。在编写C程序时,需要注意变量的声明和定义、指针的使用、内存的分配与释放等问题。C语言中常用的数据结构包括: 1. 数组:一种存储同类型数据的结构,可以进行索引访问和修改。 2. 链表:一种存储不同类型数据的结构,每个节点包含数据和指向下一个节点的指针。 3. 栈:一种后进先出(LIFO)的数据结构,可以通过压入(push)和弹出(pop)操作进行数据的存储和取出。 4. 队列:一种先进先出(FIFO)的数据结构,可以通过入队(enqueue)和出队(dequeue)操作进行数据的存储和取出。 5. 树:一种存储具有父子关系的数据结构,可以通过中序遍历、前序遍历和后序遍历等方式进行数据的访问和修改。 6. 图:一种存储具有节点和边关系的数据结构,可以通过广度优先搜索、深度优先搜索等方式进行数据的访问和修改。 这些数据结构在C语言中都有相应的实现方式,可以应用于各种不同的场景。C语言中的各种数据结构都有其优缺点,下面列举一些常见的数据结构的优缺点: 数组: 优点:访问和修改元素的速度非常快,适用于需要频繁读取和修改数据的场合。 缺点:数组的长度是固定的,不适合存储大小不固定的动态数据,另外数组在内存中是连续分配的,当数组较大时可能会导致内存碎片化。 链表: 优点:可以方便地插入和删除元素,适用于需要频繁插入和删除数据的场合。 缺点:访问和修改元素的速度相对较慢,因为需要遍历链表找到指定的节点。 栈: 优点:后进先出(LIFO)的特性使得栈在处理递归和括号匹配等问题时非常方便。 缺点:栈的空间有限,当数据量较大时可能会导致栈溢出。 队列: 优点:先进先出(FIFO)的特性使得

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值