Linux跨文件系统的文件夹和文件映射关系

VFS(Virtual File System)

Everything is a file 是Unix的设计理念,由其派生的Linux也如此。

Unix系统I/O模型最为显著的特征之一就是I/O通用性,也就是同一套系统调用open(), read(),write(),close()所执行的I/O操作,可以在所有文件类型上被执行。

文件系统有很多种实现。 Linux系统中通过VFS(Virtual File System),定义一系列通用接口。所有与文件交互程序都会按照接口进行操作。 每种文件系统都会提供VFS接口实现

依赖倒置原则(DIP):高层的模块不依赖底层, 而依赖高层的抽象。

文件模型

主要由以下对象模型组成:

  • Superblock object:存放文件系统信息。
  • inode object:存放具体文件一般信息, 文件控制信息,唯一标识一个文件。
  • file object:存放文件与进程间交互信息。
  • dentry object:存放dentry与文件相关信息,便于文件检索。

图片来在《Understanding The Linux Kernel》

可以看到在inode object和superblock object和实际的文件系统有直接关系的, 而file object和dentry object与文件系统是没有映射的,其中,dentry object起到了中间纽带的作用。

 

文件使用

 

每一个进程将打开的文件的描述符(file descriptor)保存在一个数组中, 当调用open()时,系统调用返回文件描述符,供后续read(),write()等使用, 这些系统调用获取file的数据结构后,通过VFS函数的调用,实现相关功能。

下图展示了相关数据结构之间的关系。

图片来自 https://myaut.github.io/dtrace-stap-book/kernel/fs.html

从上图可以看到, 进程通过task_struct中的files可以获得file object,包括文件打开方式f_mode,如O_RDONLY、O_RDWR, 文件偏移位置f_pos,如seek函数使用到, 文件相关的系统调用都可以通过file object对文件进行操作。

每一个文件可以通过inode object和dentry object进行标识, inode object包含一些文件本身的一些信息, 如权限信息,比如i_uid,i_gid, dentry object包含文件的目录结构信息,方便通过文件名进行查找。

虽然dentry和inode标识文件系统的文件, 但是系统在不同位置可以安装多个文件系统, 这个位置就是mountpoint, linux中通过vfsmount 结构的mnt_sb指向标识文件系统super_block, 通过mnt_root指向安装点, 就可以方便跨文件系统的文件管理。

文件映射

下面用调试工具看下跨文件系统映射关系

环境准备:

Windows10 上安装Vagrant & Vbox6.1 &CentOS8

虚拟机启动前在增加一块虚拟硬盘。 启动虚拟机后, 执行mount可以看到, 原有文件系统

/dev/sda1 on / type xfs (rw,relatime,...)

使用fdisk对新增加的硬盘,/dev/sdb 新增一个分区,使用mkfs.ext4格式化为ext4格式

创建目录,/hello , /hello/world 将新硬盘的挂在到 world目录上, 生成helloworld.txt

# mkdir -p /hello/world
# mount /dev/sdb1 /hello/world
# echo "helloworld" > /hello/world/helloworld.txt
调试步骤

下面通过调试工具查看相关数据结构信息。

  •  systemtap
  •  crash

systemtap脚本如下:

probe syscall.read {
        file = @cast(task_current(), "task_struct", "kernel")->files->fdt->fd[fd]&~3;
        if(!file)
            next;
        dentry = @cast(file, "file", "kernel")->f_path->dentry;
        inode = @cast(dentry, "dentry", "kernel")->d_inode;
        filename = d_name(dentry)

        // get the directory's dentry of file
        dentry_p = @cast(file, "file", "kernel")->f_path->dentry->d_parent;
        ino_p = @cast(file, "file", "kernel")->f_path->dentry->d_parent->d_inode->i_ino;
        file_p = d_name(dentry_p)
        // get the vfsmount's mnt_dentry of file
        dentry_m = @cast(file, "file", "kernel")->f_path->mnt->mnt_root;
        ino_m = @cast(file, "file", "kernel")->f_path->mnt->mnt_root->d_inode->i_ino;
        file_m = d_name(dentry_m)

        printf("READ %d : file '%s'  dentry %x of size '%d' on device %s ino_p %d  dentry_p %x  file_p %s ino_m %d dentry_m %x file_m %s\n",
            fd, filename, dentry ,
            @cast(inode, "inode", "kernel")->i_size,
            kernel_string(@cast(inode, "inode", "kernel")->i_sb->s_id),
            ino_p, dentry_p, file_p,  ino_m, dentry_m, file_m
             );
}

 

执行如下命令:

# stap script4.stp -c "cat /hello/world/helloworld.txt"

 

输出结果:

root@localhost trace]# stap script4.stp -c "cat /hello/world/helloworld.txt"
helloworld
READ 4 : file '.cmd' dentry ffff9938d97ca3c0 of size '0' on device debugfs ino_p 43648 dentry_p ffff9938d97cab40 file_p stap_3c0744183555cd482fd82f7186f56a23_11542 ino_m 1 dentry_m ffff9938cc806cc0 file_m /
。。。
READ 3 : file 'locale.alias' dentry ffff9938d979b9c0 of size '2997' on device sda1 ino_p 16798337 dentry_p ffff9938c18e6180 file_p locale ino_m 128 dentry_m ffff9938de4976c0 file_m /
。。。
READ 3 : file 'helloworld.txt' dentry ffff9938d84ecf00 of size '11' on device sdb1 ino_p 2 dentry_p ffff9938c180a6c0 file_p / ino_m 2 dentry_m ffff9938c180a6c0 file_m /
READ 3 : file 'helloworld.txt' dentry ffff9938d84ecf00 of size '11' on device sdb1 ino_p 2 dentry_p ffff9938c180a6c0 file_p / ino_m 2 dentry_m ffff9938c180a6c0 file_m /

 

对于 helloworld.txt的上一级的dentry,可以看到不是想象中的“world”而是“/”。 可以通过crash看下

crash> dentry ffff9938d84ecf00|grep parent
  d_parent = 0xffff9938c180a6c0,
crash> dentry 0xffff9938c180a6c0 |grep name
  d_name = {
    name = 0xffff9938c180a6f8 "/"
  d_iname = "/\000\311}\302\177\000\000\220f\311}\302\177\000\000\000\000\000\000\000\000\000\000)\255\003\000\000\000\000",

可以看到和日志中打印应的dentry parent的dentry ffff9938c180a6c0, 一样

 

crash> dentry ffff9938c180a6c0|grep super
crash> super_block 0xffff9938d8d66800|grep s_id
s_id = "sdb1\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",

 

也就是helloworld.txt 文件上级的dentry是文件系统 /dev/sdb1的根目录。他的挂载点是 /hello/world, 通过crash看下mount信息

 

crash> mount
     MOUNT           SUPERBLK     TYPE   DEVNAME   DIRNAME
ffff9938cce15600 ffff9938ccc0e000 rootfs rootfs    /
。。。
ffff9938db416300 ffff9938d8d66800 ext4   /dev/sdb1 /hello/world
crash> struct mount ffff9938db416300|grep mnt_mountpoint
mnt_mountpoint = 0xffff9938c18089c0,
crash> dentry 0xffff9938c18089c0|grep name
d_name = {
name = 0xffff9938c18089f8 "world"
d_iname = "world\000\000\000\324\016\002\200\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",

可以看到, 跨文件系统,dentry维护的目录结构不是我们看到的目录结构, 如果查找是dentry不在内存中的话, 就需要通过磁盘进行查找, 这时候就需要通过用到mount中相关信息了。 磁盘查找的过程,更类似和我们平时查找文件的逻辑。

另外 dentrynegativity状态问题,通常由于关联文件被删除, 或者dentry解析一个不存在的路径,会出现无效的dentry, LWN 上这篇文章 Dentry negativity 描述了针对这种情况还除了一个补丁, 不过目前尚未被采纳。

 

参考及引用

图片来自 twitter Ines B @moraimauy

Virtual File System

 

 

Comments are closed.