Linux系统中虚拟文件系统原理与方法

简介: Linux系统中虚拟文件系统原理与方法

在 Unix 的世界里,有句很经典的话:一切对象皆是文件。这句话的意思是说,可以将 Unix 操作系统中所有的对象都当成文件,然后使用操作文件的接口来操作它们。Linux 作为一个类 Unix 操作系统,也努力实现这个目标。

虚拟文件系统简介

为了实现 一切对象皆是文件 这个目标,Linux 内核提供了一个中间层:虚拟文件系统(Virtual File System)

如果大家使用过面向对象编程语言(如C++/Java等)的话,应该对 接口 这个概念并不陌生。而虚拟文件系统类似于面向对象中的接口,定义了一套标准的接口。开发者只需要实现这套接口,即可以使用操作文件的接口来操作对象。如下图所示:

上图中的蓝色部分就是虚拟文件系统所在位置。

从上图可以看出,虚拟文件系统为上层应用提供了统一的接口。如果某个文件系统实现了虚拟文件系统的接口,那么上层应用就能够使用诸如 open()read()write() 等函数来操作它们。

今天,我们就来介绍虚拟文件系统的原理与实现。

虚拟文件系统原理

在阐述虚拟文件系统的原理前,我们先来介绍一个 Java 例子。通过这个 Java 例子,我们能够更容易理解虚拟文件系统的原理。

一个Java例子

如果大家使用过 Java 编写程序的话,那么就很容易理解虚拟文件系统了。我们使用 Java 的接口来模拟虚拟文件系统的定义:

public interface VFSFile {
  int open(String file, int mode);
  int read(int fd, byte[] buffer, int size);
  int write(int fd, byte[] buffer, int size);
  ...
}

上面定义了一个名为 VFSFile 的接口,接口中定义了一些方法,如 open()read()write() 等。现在我们来定义一个名为 Ext3File 的对象来实现这个接口:

public class Ext3File implements VFSFile {
  @Override
  public int open(String file, int mode) {
    ...
  }
  
  @Override
  public int read(int fd, byte[] buffer, int size) {
    ...
  }
  
  @Override
  public int write(int fd, byte[] buffer, int size) {
    ...
  }
  
  ...
}

现在我们就能使用 VFSFile 接口来操作 Ext3File 对象了,如下代码:

public class Main() {
  public static void main(String[] args) {
    VFSFile file = new Ext3File();
    
    int fd = file.open("/tmp/file.txt", 0);
    ...
  }
}

从上面的例子可以看出,底层对象只需要实现 VFSFile 接口,就可以使用 VFSFile 接口相关的方法来操作对象,用户完全不需要了解底层对象的实现过程。

虚拟文件系统原理

上面的 Java 例子已经大概说明虚拟文件系统的原理,但由于 Linux 是使用 C 语言来编写的,而 C 语言并没有接口这个概念。所以,Linux 内核使用了一些技巧来模拟接口这个概念。

下面来介绍一下 Linux 内核是如何实现的。

1. file结构

为了模拟接口,Linux 内核定义了一个名为 file 的结构体,其定义如下:

struct file {
    ...
    const struct file_operations *f_op;
    ...
};

在 file 结构中,最为重要的一个字段就是 f_op,其类型为 file_operations 结构。而 file_operations 结构是由一组函数指针组成,其定义如下:

struct file_operations {
    ...
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ...
    int (*open) (struct inode *, struct file *);
    ...
};

file_operations 结构的定义可以隐约看到接口的影子,所以可以猜想出,如果实现了 file_operations 结构中的方法,应该就能接入到虚拟文件系统中。

在 Linux 内核中,file 结构代表着一个被打开的文件。所以,只需要将 file 结构的 f_op 字段设置成不同文件系统实现好的方法集,那么就能够使用不同文件系统的功能。

这个过程在 __dentry_open() 函数中实现,如下所示:

static struct file *
__dentry_open(struct dentry *dentry, 
              struct vfsmount *mnt, 
              truct file *f, 
              int (*open)(struct inode *, struct file *), 
              const struct cred *cred)
{
    ...
    inode = dentry->d_inode;
    ...
    // 设置file结构的f_op字段为底层文件系统实现的方法集
    f->f_op = fops_get(inode->i_fop);
    ...
    return f;
}

设置好 file 结构的 f_op 字段后,虚拟文件系统就能够使用通用的接口来操作此文件了。调用过程如下:

2. file_operations结构

底层文件系统需要实现虚拟文件系统的接口,才能被虚拟文件系统使用。也就是说,底层文件系统需要实现 file_operations 结构中的方法集。

一般底层文件系统会在其内部定义好 file_operations 结构,并且填充好其方法集中的函数指针。如 minix文件系统 就定义了一个名为 minix_file_operationsfile_operations 结构。其定义如下:

// 文件:fs/minix/file.c
const struct file_operations minix_file_operations = {
    .llseek         = generic_file_llseek,
    .read           = do_sync_read,
    .aio_read       = generic_file_aio_read,
    .write          = do_sync_write,
    .aio_write      = generic_file_aio_write,
    .mmap           = generic_file_mmap,
    .fsync          = generic_file_fsync,
    .splice_read    = generic_file_splice_read,
};

也就是说,如果当前使用的是 minix 文件系统,当使用 read() 函数读取其文件的内容时,那么最终将会调用 do_sync_read() 函数来读取文件的内容。

3. dentry结构

到这里,虚拟文件系统的原理基本分析完毕,但还有两个非常重要的结构要介绍一下的:dentryinode

dentry 结构表示一个打开的目录项,当我们打开文件 /usr/local/lib/libc.so 文件时,内核会为文件路径中的每个目录创建一个 dentry 结构。如下图所示:

可以看到,file 结构有个指向 dentry 结构的指针,如下所示:

struct file {
    ...
    struct path f_path;
    ...
    const struct file_operations *f_op;
    ...
};
struct path {
    ...
    struct dentry *dentry;
};

与文件类似,目录也有相关的操作接口,所以在 dentry 结构中也有操作方法集,如下所示:

struct dentry {
    ...
    struct dentry *d_parent;              // 父目录指针
    struct qstr d_name;                   // 目录名字
    struct inode *d_inode;                // 指向inode结构
    ...
    const struct dentry_operations *d_op; // 操作方法集
    ...
};

其中的 d_op 字段就是目录的操作方法集。

内核在打开文件时,会为路径中的每个目录创建一个 dentry 结构,并且使用 d_parent 字段来指向其父目录项,这样就能通过 d_parent 字段来追索到根目录。

4. inode结构

在 Linux 内核中,inode 结构表示一个真实的文件。为什么有了 dentry 结构还需要 inode 结构呢?这是因为 Linux 存在硬链接的概念。

例如使用以下命令为 /usr/local/lib/libc.so 文件创建一个硬链接:

ln /usr/local/lib/libc.so /tmp/libc.so

现在 /usr/local/lib/libc.so/tmp/libc.so 指向同一个文件,但它们的路径是不一样的。所以,就需要引入 inode 结构了。如下图所示:

由于 /usr/local/lib/libc.so/tmp/libc.so 指向同一个文件,所以它们都使用同一个 inode 对象。

inode 结构保存了文件的所有属性值,如文件的创建时间、文件所属用户和文件的大小等。其定义如下所示:

struct inode {
    ...
    uid_t           i_uid;               // 文件所属用户
    gid_t           i_gid;               // 文件所属组
    ...
    struct timespec i_atime;             // 最后访问时间
    struct timespec i_mtime;             // 最后修改时间
    struct timespec i_ctime;             // 文件创建时间
    ...
    unsigned short  i_bytes;             // 文件大小
    ...
    const struct file_operations *i_fop; // 文件操作方法集(用于设置file结构)
    ...
};

我们注意到 inode 结构有个类型为 file_operations 结构的字段 i_fop,这个字段保存了文件的操作方法集。当用户调用 open() 系统调用打开文件时,内核将会使用 inode 结构的 i_fop 字段赋值给 file 结构的 f_op 字段。我们再来重温下赋值过程:

static struct file *
__dentry_open(struct dentry *dentry, 
              struct vfsmount *mnt, 
              truct file *f, 
              int (*open)(struct inode *, struct file *), 
              const struct cred *cred)
{
    ...
    // 文件对应的inode对象
    inode = dentry->d_inode;
    ...
    // 使用inode结构的i_fop字段赋值给file结构的f_op字段
    f->f_op = fops_get(inode->i_fop);
    ...
    return f;
}

总结

本文主要介绍了 虚拟文件系统 的基本原理,从分析中可以发现,虚拟文件系统使用了类似于面向对象编程语言中的接口概念。正是有了 虚拟文件系统,Linux 才能支持各种各样的文件系统。

目录
相关文章
|
3天前
|
存储 缓存 监控
Linux缓存管理:如何安全地清理系统缓存
在Linux系统中,内存管理至关重要。本文详细介绍了如何安全地清理系统缓存,特别是通过使用`/proc/sys/vm/drop_caches`接口。内容包括清理缓存的原因、步骤、注意事项和最佳实践,帮助你在必要时优化系统性能。
108 78
|
6天前
|
Linux Shell 网络安全
Kali Linux系统Metasploit框架利用 HTA 文件进行渗透测试实验
本指南介绍如何利用 HTA 文件和 Metasploit 框架进行渗透测试。通过创建反向 shell、生成 HTA 文件、设置 HTTP 服务器和发送文件,最终实现对目标系统的控制。适用于教育目的,需合法授权。
33 9
Kali Linux系统Metasploit框架利用 HTA 文件进行渗透测试实验
|
2天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
35 13
|
3天前
|
Ubuntu Linux C++
Win10系统上直接使用linux子系统教程(仅需五步!超简单,快速上手)
本文介绍了如何在Windows 10上安装并使用Linux子系统。首先,通过应用商店安装Windows Terminal和Linux系统(如Ubuntu)。接着,在控制面板中启用“适用于Linux的Windows子系统”并重启电脑。最后,在Windows Terminal中选择安装的Linux系统即可开始使用。文中还提供了注意事项和进一步配置的链接。
13 0
|
7月前
|
Linux
百度搜索:蓝易云【Linux中如何对文件进行压缩和解压缩?】
这些是在Linux中进行文件压缩和解压缩的常见方法。根据您的需求和具体情况,可能会使用其他压缩工具和选项。您可以通过查阅相应命令的帮助文档来获取更多详细信息。
91 1
|
7月前
|
NoSQL Java Linux
Linux常用命令(文件目录操作、拷贝移动、打包压缩、文本编辑、查找)
Linux常用命令(文件目录操作、拷贝移动、打包压缩、文本编辑、查找)
|
7月前
|
算法 Java Linux
Linux下文件增删改查定位压缩操作与权限所属用户
Linux下文件增删改查定位压缩操作与权限所属用户
77 0
26Linux - 文件管理(文件压缩解压:bzip2)
26Linux - 文件管理(文件压缩解压:bzip2)
64 0
|
7月前
|
Java Shell Linux
Linux【脚本 01】简单Shell脚本实现定时备份文件、压缩、删除超时文件操作(showDoc文件备份脚本举例)
Linux【脚本 01】简单Shell脚本实现定时备份文件、压缩、删除超时文件操作(showDoc文件备份脚本举例)
387 0
|
6月前
|
Linux
14. 【Linux教程】文件压缩与解压
14. 【Linux教程】文件压缩与解压
61 0