首发于术道经纬
Linux中的VFS实现 [一]

Linux中的VFS实现 [一]

简约而不简单

在Linux中,不管是访问什么文件,也不管使用的是什么文件系统,基本都可以统一地使用诸如open(), read()和write()这样的接口。表面上看这些接口都很简单,但要基于不同的存储介质,适应不同的文件系统,并不是件容易的事。

这得归功于VFS(Virtual Filesystem),它提供的抽象让不同的文件系统表现出一致的行为。对用户空间和内核的其他部分,这些文件系统看起来都是一样的(比如都有文件和目录,都支持添加和删除操作),不用关注不同文件系统的具体细节。

如何实现抽象

VFS基于Unix文件系统模型,其提供的抽象与上文的描述基本一致。除了表示一个文件的"inode",以及管理一个文件系统的"superblock"外,还有一个关于路径的抽象"dentry"。

关于一个文件的inode信息,可通过"stat"命令获取:

我们可以对照着上面这张图,来看下Linux的inode数据结构中与之有关的部分。

访问控制

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

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

struct inode {
    kuid_t    i_uid;    /* user id */
    kgid_t    i_gid;    /* group id */
    ...
}

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

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")。

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"项)。

struct super_block  *i_sb;
struct list_head     i_sb_list;
unsigned long        i_ino;
两种link

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

用"cat"命令查看文件包含的内容,得到的输出都是一样的:

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

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

输出的第一列代表user, group和others的访问权限,第三列是文件的大小(至于第二列的含义,将在后面给出。)。原文件和hard link的大小都是6个字节,也就是"hello"字符串的大小,soft link的大小则是4个字节。

现在用"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"命令输出中第二列数值的含义):

unsigned int i_nlink;

假设现在有这样的一个目录结构:

来看一下其中的每个directory和每个regular file的link数目:

每个目录下,除了包含文件和子目录,还包含一个对当前目录的指向(用"."表示)和一个对上级目录/父目录的指向(用".."表示)。

一个普通文本文件(比如file和sub-file)的link数目是1,一个没有子目录的directory(比如sub-test)的link数目是2,包含了自身的引用和父目录对它的引用,一个含有子目录的directory(比如test)的link还包含了子目录对它的指向,所以link数目大于2。

那什么时候,文件的内容才会被真正的从磁盘上消失,对应的inode管理结构也不再存在呢?这将在后续的文章中给出答案。

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

下文将围绕一个文件的打开、读写和关闭等基础操作,进一步讲解VFS中各个抽象元素之间的交互。


参考:

《Red Hat Linux 用户基础》第5章


原创文章,转载请注明出处。



编辑于 10-03

文章被以下专栏收录