0%

Linux文件系统(一)基本概念

原图

声明:本文转载自文件系统的原理Linux中的VFS实现 [一]

没有文件系统,访问磁盘上的数据就需要直接读写磁盘的sector(繁琐),而文件系统存在的意义,就是能更有效的组织、管理和使用磁盘上的raw data。

文件系统的组成

因为磁盘上的数据要和内存交互,而内存通常是以4KB为单位管理的,所以把磁盘按照4KB划分比较方便(称为一个block)。现在假设由一个文件系统管理64个blocks的一个磁盘区域:

文件

文件系统的基础要素自然是文件,而文件作为一个数据容器的逻辑概念,本质上是字节构成的集合,这些字节就是文件的user data(对应下图的"D")。

除了文件本身包含的数据,还有文件的访问权限、大小和创建时间等控制信息,这些信息被称为文件的meta data。这些meta data的数据结构就是inode(对应下图的"I",有些文件系统称之为dnode或fnode)。

假设一个inode占据256字节,那么一个4KB的block可以存放16个inodes,使用5个blocks可以存放80个inodes,也就是最多支持80个文件。

bitmap

需要追踪这些inodes和data blocks的分配和释放情况,判断哪些是已用的,哪些是空闲的。最简单的办法就是使用bitmap,包括记录inode使用情况的bitmap(对应下图的"i"),和记录data block使用情况的bitmap(对应下图的"d")。空闲就标记为0,正在使用就标记为1。

superblock

superblock包含了一个文件系统所有的控制信息,比如文件系统中有多少个inodes和data blocks,inode的信息起始于哪个block(这里是第3个),可能还有一个区别不同文件系统类型的magic number。因此,superblock可理解为是文件系统的meta data。

文件寻址

寻址过程

这5个blocks中的80个inodes构成了一个inode table。假设一个inode的大小是256字节,现在我们要访问第32个文件,也就是第32个inode所在的磁盘位置。它应该在相对inode table起始地址的8KB处(32*256=8192),而inode table的起始地址是12KB,所以实际位置是20KB。

磁盘同内存不同,它在物理上不是按字节寻址的,而是按sector。一个sector的大小通常是512字节,因此换算一下就是第40个sector(20*1024/512)。找到inode后,inode里就有指针指向保持文件数据的data block就查找到了文件。

对于ext2/3/4文件系统,以上介绍的这些inode bitmap, data block bitmap和inode table,都可以通过一个名为"dumpe2fs"的工具来查看其在磁盘上的具体位置:

如果只需要查看inode的使用情况,那么直接使用"df -i"命令即可:

寻址方式

两种寻址方式:

  • inode里通过指针指向一个block,假设一个inode最多能包含12个指针,那么文件的大小不能超过48KB。那如果超过了怎么办?可由inode先指向一个中间block,这个block再指向分散的data block,这种方法称为multi-level index。假设一个指针占据4个字节,那么一个中间block可存储1024个指针,二级index的寻址范围就可超过4MB,三级index则可超过4GB。

这种只使用block指针的方式(可被称为"pointer-based")被ext2和ext3文件系统所采用,但它存在一个问题,对于占据多个data block的文件,需要较多的meta data。

  • 另一种实现是使用一个block指针加上一个length来表示一组物理上连续的blocks(称为一个extent,其中length以block为单位计),一个文件则由若干个extents构成。这种"extent-based"的方式被后来的ext4文件系统所采用。
1
2
3
4
5
struct ext4_extent {
__le32 ee_block; /* first logical block extent covers */
__le16 ee_len; /* number of blocks covered by extent */
...
};

相比"pointer-based"而言,"extent-based"由于需要磁盘上连续的free space,灵活性稍差,适用于磁盘空闲空间比较充足的场景。

目录和路径

各级目录构成了访问文件的路径,从抽象的角度,目录也可视作一种文件,只是这种文件比较特殊,它的user data存储的是该路径下的普通文件的inode编号。

所以,如下图所示的这样一个路径结构,假设要在"/foo"目录下创建一个文件"bar.txt",那么需要从inode bitmap中分配一个空闲的inode,并在"/foo"这个目录中分配一个entry,以关联这个inode号。

接下来,我们要读取刚才创建的这个"/foo/bar.txt"文件,那么先得找到"/"这个目录文件的inode号(这必须是事先知道的,假设是2)。然后访问这个inode指向的data block,从中找到一个名为"foo"的entry,得到目录文件"foo"的inode号(假设是44)。重复此过程,按图索骥,直到找到文本文件"bar.txt"的inode号。

用户看到的文件

访问权限控制

每个文件有三种与之关联的权限,分别是读、写和执行。试图访问文件的用户也划分为三类,分别是文件的所有者(user)、与所有者在同一用户组的用户(group),以及其他用户(others)。

可通过"chmod"命令修改文件的权限,通过"chown"命令修改文件的UID和GID。

1
2
3
4
5
struct inode {
kuid_t i_uid; /* user id */
kgid_t i_gid; /* group id */
...
}

每个文件都有三种timestamp:文件上次被访问的时间(access time,简称atime)、文件上次被修改的时间(modification time,简称mtime)和文件属性上次被修改的时间(change time,简称ctime)

1
2
3
struct timespec64   i_atime;
struct timespec64 i_mtime;
struct timespec64 i_ctime;

mtime针对的是文件的内容(即user data),而ctime针对的是inode结构自身(即meta data)。

特殊文件

上面示例的这个文件是regular file,此外,设备在Linux中也被视作文件,一个设备可以是block device(即"i_bdev"),也可以是character device(即"i_cdev"),而且设备还具有主设备号和从设备号(即"i_rdev")。

1
2
3
4
5
6
7
8
dev_t i_rdev;
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
char *i_link;
...
};

如上文所说,目录(directory)也被视作一种特殊的文件,因而它没有独立的数据结构,且基于文件的大部分操作也可用于directory。

inode编号和superblock

一个文件必然处于一个文件系统中,因而一个inode也必然被一个superblock所管理(由"i_sb"指向)。同一superblock的所有inodes以双向链表的形式连接(即"i_sb_list"),每个inode在其所属的superblock中有唯一的编号(即"i_ino",对应上面stat命令输出的"Inode"项)。

1
2
3
struct super_block  *i_sb;
struct list_head i_sb_list;
unsigned long i_ino;

一个文件可以有两种link:hard link和symbolic/soft link,可分别通过"ln"和"ln -s"命令创建。

它们的inode编号却不相同(通过"ls -i"查看),hard link与原文件的inode号相同,soft link则有单独的inode号。

再来查看文件的详细信息:

现在用"rm"命令删除原文件,并通过strace工具追踪这期间发生的系统调用。

可以看到,它调用了"unlink",为什么不是叫"remove"或者"delete"呢?先来试试删除原文件之后,还能否继续使用hard link和soft link。

hard link还可以正常访问原来的内容,而soft link的访问则会失败。这一切的原因还得从hard link和soft link的属性说起。

当创建一个文件时,我们需要选择一个路径(pathname),并为文件设置一个字符串形式的名称(symbol)。这其实做了两件事,一是生成一个inode结构体,用于记录这个文件的所有相关信息,包括大小、在磁盘上占据的blocks数目等,二是将生成的inode关联(link)了这个路径和名称。

一个文件的hard link增加的是对这个inode结构体的关联/指向,并不是一个新的文件。而soft link本身就是一个文件,就像directory这种特殊文件里存放的是该目录下包含哪些文件,soft link这种文件里存放的则是指向原inode的路径,路径越长,soft link的大小就越大。这就是为什么hard link和原文件的inode号相同,而soft link不同。

因此,当我们用"rm"命令“删除”原文件时,删除的只是原文件的路径和inode之间的关联,而不是这个inode本身,文件的内容依然存在于磁盘中,因而只能算是"unlink"。所以直接关联inode的hard link不受影响,而关联原文件路径的soft link此时相当于是一个dangling reference。

一个inode被link的数目由"i_nlink"表示(这就是前面"ls -l"命令输出中第二列数值的含义):

1
unsigned int i_nlink;

相比起soft link,hard link在使用的时候有个限制,就是必须和原文件位于相同的文件系统,原因还是和inode编号有关。因为一个inode编号只在文件所属的superblock中是唯一的,而hard link使用和原文件相同的inode编号,如果hard link跑到其他文件系统,就可能和这些文件系统中既有的文件inode编号冲突。

参考文献

https://zhuanlan.zhihu.com/p/106459445
https://zhuanlan.zhihu.com/p/100329177