Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,221 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/filesystems/debugfs.rst
=======
Debugfs
=======
译者
::
中文版维护者: 罗楚成 Chucheng Luo <luochucheng@vivo.com>
中文版翻译者: 罗楚成 Chucheng Luo <luochucheng@vivo.com>
中文版校译者: 罗楚成 Chucheng Luo <luochucheng@vivo.com>
版权所有2020 罗楚成 <luochucheng@vivo.com>
Debugfs是内核开发人员在用户空间获取信息的简单方法。与/proc不同,proc只提供进程
信息。也不像sysfs,具有严格的“每个文件一个值“的规则。debugfs根本没有规则,开发
人员可以在这里放置他们想要的任何信息。debugfs文件系统也不能用作稳定的ABI接口。
从理论上讲,debugfs导出文件的时候没有任何约束。但是[1]实际情况并不总是那么
简单。即使是debugfs接口,也最好根据需要进行设计,并尽量保持接口不变。
Debugfs通常使用以下命令安装::
mount -t debugfs none /sys/kernel/debug
(或等效的/etc/fstab行)。
debugfs根目录默认仅可由root用户访问。要更改对文件树的访问,请使用“ uid”,“ gid”
和“ mode”挂载选项。请注意,debugfs API仅按照GPL协议导出到模块。
使用debugfs的代码应包含<linux/debugfs.h>。然后,首先是创建至少一个目录来保存
一组debugfs文件::
struct dentry *debugfs_create_dir(const char *name, struct dentry *parent);
如果成功,此调用将在指定的父目录下创建一个名为name的目录。如果parent参数为空,
则会在debugfs根目录中创建。创建目录成功时,返回值是一个指向dentry结构体的指针。
该dentry结构体的指针可用于在目录中创建文件(以及最后将其清理干净)。ERR_PTR
(-ERROR)返回值表明出错。如果返回ERR_PTR(-ENODEV),则表明内核是在没有debugfs
支持的情况下构建的,并且下述函数都不会起作用。
在debugfs目录中创建文件的最通用方法是::
struct dentry *debugfs_create_file(const char *name, umode_t mode,
struct dentry *parent, void *data,
const struct file_operations *fops);
在这里,name是要创建的文件的名称,mode描述了访问文件应具有的权限,parent指向
应该保存文件的目录,data将存储在产生的inode结构体的i_private字段中,而fops是
一组文件操作函数,这些函数中实现文件操作的具体行为。至少,read()和/或
write()操作应提供;其他可以根据需要包括在内。同样的,返回值将是指向创建文件
的dentry指针,错误时返回ERR_PTR(-ERROR),系统不支持debugfs时返回值为ERR_PTR
(-ENODEV)。创建一个初始大小的文件,可以使用以下函数代替::
struct dentry *debugfs_create_file_size(const char *name, umode_t mode,
struct dentry *parent, void *data,
const struct file_operations *fops,
loff_t file_size);
file_size是初始文件大小。其他参数跟函数debugfs_create_file的相同。
在许多情况下,没必要自己去创建一组文件操作;对于一些简单的情况,debugfs代码提供
了许多帮助函数。包含单个整数值的文件可以使用以下任何一项创建::
void debugfs_create_u8(const char *name, umode_t mode,
struct dentry *parent, u8 *value);
void debugfs_create_u16(const char *name, umode_t mode,
struct dentry *parent, u16 *value);
struct dentry *debugfs_create_u32(const char *name, umode_t mode,
struct dentry *parent, u32 *value);
void debugfs_create_u64(const char *name, umode_t mode,
struct dentry *parent, u64 *value);
这些文件支持读取和写入给定值。如果某个文件不支持写入,只需根据需要设置mode
参数位。这些文件中的值以十进制表示;如果需要使用十六进制,可以使用以下函数
替代::
void debugfs_create_x8(const char *name, umode_t mode,
struct dentry *parent, u8 *value);
void debugfs_create_x16(const char *name, umode_t mode,
struct dentry *parent, u16 *value);
void debugfs_create_x32(const char *name, umode_t mode,
struct dentry *parent, u32 *value);
void debugfs_create_x64(const char *name, umode_t mode,
struct dentry *parent, u64 *value);
这些功能只有在开发人员知道导出值的大小的时候才有用。某些数据类型在不同的架构上
有不同的宽度,这样会使情况变得有些复杂。在这种特殊情况下可以使用以下函数::
void debugfs_create_size_t(const char *name, umode_t mode,
struct dentry *parent, size_t *value);
不出所料,此函数将创建一个debugfs文件来表示类型为size_t的变量。
同样地,也有导出无符号长整型变量的函数,分别以十进制和十六进制表示如下::
struct dentry *debugfs_create_ulong(const char *name, umode_t mode,
struct dentry *parent,
unsigned long *value);
void debugfs_create_xul(const char *name, umode_t mode,
struct dentry *parent, unsigned long *value);
布尔值可以通过以下方式放置在debugfs中::
struct dentry *debugfs_create_bool(const char *name, umode_t mode,
struct dentry *parent, bool *value);
读取结果文件将产生Y(对于非零值)或N,后跟换行符写入的时候,它只接受大写或小写
值或1或0。任何其他输入将被忽略。
同样,atomic_t类型的值也可以放置在debugfs中::
void debugfs_create_atomic_t(const char *name, umode_t mode,
struct dentry *parent, atomic_t *value)
读取此文件将获得atomic_t值,写入此文件将设置atomic_t值。
另一个选择是通过以下结构体和函数导出一个任意二进制数据块::
struct debugfs_blob_wrapper {
void *data;
unsigned long size;
};
struct dentry *debugfs_create_blob(const char *name, umode_t mode,
struct dentry *parent,
struct debugfs_blob_wrapper *blob);
读取此文件将返回由指针指向debugfs_blob_wrapper结构体的数据。一些驱动使用“blobs”
作为一种返回几行(静态)格式化文本的简单方法。这个函数可用于导出二进制信息,但
似乎在主线中没有任何代码这样做。请注意,使用debugfs_create_blob()命令创建的
所有文件是只读的。
如果您要转储一个寄存器块(在开发过程中经常会这么做,但是这样的调试代码很少上传
到主线中。Debugfs提供两个函数:一个用于创建仅寄存器文件,另一个把一个寄存器块
插入一个顺序文件中::
struct debugfs_reg32 {
char *name;
unsigned long offset;
};
struct debugfs_regset32 {
struct debugfs_reg32 *regs;
int nregs;
void __iomem *base;
};
struct dentry *debugfs_create_regset32(const char *name, umode_t mode,
struct dentry *parent,
struct debugfs_regset32 *regset);
void debugfs_print_regs32(struct seq_file *s, struct debugfs_reg32 *regs,
int nregs, void __iomem *base, char *prefix);
“base”参数可能为0,但您可能需要使用__stringify构建reg32数组,实际上有许多寄存器
名称(宏)是寄存器块在基址上的字节偏移量。
如果要在debugfs中转储u32数组,可以使用以下函数创建文件::
void debugfs_create_u32_array(const char *name, umode_t mode,
struct dentry *parent,
u32 *array, u32 elements);
“array”参数提供数据,而“elements”参数为数组中元素的数量。注意:数组创建后,数组
大小无法更改。
有一个函数来创建与设备相关的seq_file::
struct dentry *debugfs_create_devm_seqfile(struct device *dev,
const char *name,
struct dentry *parent,
int (*read_fn)(struct seq_file *s,
void *data));
“dev”参数是与此debugfs文件相关的设备,并且“read_fn”是一个函数指针,这个函数在
打印seq_file内容的时候被回调。
还有一些其他的面向目录的函数::
struct dentry *debugfs_rename(struct dentry *old_dir,
struct dentry *old_dentry,
struct dentry *new_dir,
const char *new_name);
struct dentry *debugfs_create_symlink(const char *name,
struct dentry *parent,
const char *target);
调用debugfs_rename()将为现有的debugfs文件重命名,可能同时切换目录。 new_name
函数调用之前不能存在;返回值为old_dentry,其中包含更新的信息。可以使用
debugfs_create_symlink()创建符号链接。
所有debugfs用户必须考虑的一件事是:
debugfs不会自动清除在其中创建的任何目录。如果一个模块在不显式删除debugfs目录的
情况下卸载模块,结果将会遗留很多野指针,从而导致系统不稳定。因此,所有debugfs
用户-至少是那些可以作为模块构建的用户-必须做模块卸载的时候准备删除在此创建的
所有文件和目录。一份文件可以通过以下方式删除::
void debugfs_remove(struct dentry *dentry);
dentry值可以为NULL或错误值,在这种情况下,不会有任何文件被删除。
很久以前,内核开发者使用debugfs时需要记录他们创建的每个dentry指针,以便最后所有
文件都可以被清理掉。但是,现在debugfs用户能调用以下函数递归清除之前创建的文件::
void debugfs_remove_recursive(struct dentry *dentry);
如果将对应顶层目录的dentry传递给以上函数,则该目录下的整个层次结构将会被删除。
注释:
[1] http://lwn.net/Articles/309298/
@@ -0,0 +1,67 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/filesystems/dnotify.rst
:翻译:
王龙杰 Wang Longjie <wang.longjie1@zte.com.cn>
==============
Linux 目录通知
==============
Stephen Rothwell <sfr@canb.auug.org.au>
目录通知的目的是使用户应用程序能够在目录或目录中的任何文件发生变更时收到通知。基本机制包括应用程序
通过 fcntl(2) 调用在目录上注册通知,通知本身则通过信号传递。
应用程序可以决定希望收到哪些 “事件” 的通知。当前已定义的事件如下:
========= =====================================
DN_ACCESS 目录中的文件被访问(read)
DN_MODIFY 目录中的文件被修改(write,truncate)
DN_CREATE 目录中创建了文件
DN_DELETE 目录中的文件被取消链接
DN_RENAME 目录中的文件被重命名
DN_ATTRIB 目录中的文件属性被更改(chmod,chown)
========= =====================================
通常,应用程序必须在每次通知后重新注册,但如果将 DN_MULTISHOT 与事件掩码进行或运算,则注册
将一直保持有效,直到被显式移除(通过注册为不接收任何事件)。
默认情况下,SIGIO 信号将被传递给进程,且不附带其他有用的信息。但是,如果使用 F_SETSIG fcntl(2)
调用让内核知道要传递哪个信号,一个 siginfo 结构体将被传递给信号处理程序,该结构体的 si_fd 成员将
包含与发生事件的目录相关联的文件描述符。
应用程序最好选择一个实时信号(SIGRTMIN + <n>),以便通知可以被排队。如果指定了 DN_MULTISHOT,
这一点尤为重要。注意,SIGRTMIN 通常是被阻塞的,因此最好使用(至少)SIGRTMIN + 1。
实现预期(特性与缺陷 :-))
--------------------------
对于文件的任何本地访问,通知都应能正常工作,即使实际文件系统位于远程服务器上。这意味着,对本地用户
模式服务器提供的文件的远程访问应能触发通知。同样的,对本地内核 NFS 服务器提供的文件的远程访问
也应能触发通知。
为了尽可能减小对文件系统代码的影响,文件硬链接的问题已被忽略。因此,如果一个文件(x)存在于两个
目录(a 和 b)中,通过名称”a/x”对该文件进行的更改应通知给期望接收目录“a”通知的程序,但不会
通知给期望接收目录“b”通知的程序。
此外,取消链接的文件仍会在它们链接到的最后一个目录中触发通知。
配置
----
Dnotify 由 CONFIG_DNOTIFY 配置选项控制。禁用该选项时,fcntl(fd, F_NOTIFY, ...) 将返
回 -EINVAL。
示例
----
具体示例可参见 tools/testing/selftests/filesystems/dnotify_test.c。
注意
----
从 Linux 2.6.13 开始,dnotify 已被 inotify 取代。有关 inotify 的更多信息,请参见
Documentation/filesystems/inotify.rst。
@@ -0,0 +1,211 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/filesystems/gfs2-glocks.rst
:翻译:
邵明寅 Shao Mingyin <shao.mingyin@zte.com.cn>
:校译:
杨涛 yang tao <yang.tao172@zte.com.cn>
==================
Glock 内部加锁规则
==================
本文档阐述 glock 状态机内部运作的基本原理。每个 glock(即
fs/gfs2/incore.h 中的 struct gfs2_glock)包含两把主要的内部锁:
1. 自旋锁(gl_lockref.lock):用于保护内部状态(如
gl_state、gl_target)和持有者列表(gl_holders)
2. 非阻塞的位锁(GLF_LOCK):用于防止其他线程同时调用
DLM 等操作。若某线程获取此锁,则在释放时必须调用
run_queue(通常通过工作队列),以确保所有待处理任务
得以完成。
gl_holders 列表包含与该 glock 关联的所有排队锁请求(不
仅是持有者)。若存在已持有的锁,它们将位于列表开头的连
续条目中。锁的授予严格遵循排队顺序。
glock 层用户可请求三种锁状态:共享(SH)、延迟(DF)和
排他(EX)。它们对应以下 DLM 锁模式:
========== ====== =====================================================
Glock 模式 DLM 锁模式
========== ====== =====================================================
UN IV/NL 未加锁(无关联的 DLM 锁)或 NL
SH PR 受保护读(Protected read)
DF CW 并发写(Concurrent write)
EX EX 排他(Exclusive)
========== ====== =====================================================
因此,DF 本质上是一种与“常规”共享锁模式(SH)互斥的共
享模式。在 GFS2 中,DF 模式专用于直接 I/O 操作。Glock
本质上是锁加缓存管理例程的组合,其缓存规则如下:
========== ============== ========== ========== ==============
Glock 模式 缓存元数据 缓存数据 脏数据 脏元数据
========== ============== ========== ========== ==============
UN 否 否 否 否
DF 是 否 否 否
SH 是 是 否 否
EX 是 是 是 是
========== ============== ========== ========== ==============
这些规则通过为每种 glock 定义的操作函数实现。并非所有
glock 类型都使用全部的模式,例如仅 inode glock 使用 DF 模
式。
glock 操作函数及类型常量说明表:
============== ========================================================
字段 用途
============== ========================================================
go_sync 远程状态变更前调用(如同步脏数据)
go_xmote_bh 远程状态变更后调用(如刷新缓存)
go_inval 远程状态变更需使缓存失效时调用
go_instantiate 获取 glock 时调用
go_held 每次获取 glock 持有者时调用
go_dump 为 debugfs 文件打印对象内容,或出错时将 glock 转储至日志
go_callback 若 DLM 发送回调以释放此锁时调用
go_unlocked 当 glock 解锁时调用(dlm_unlock())
go_type glock 类型,``LM_TYPE_*``
go_flags 若 glock 关联地址空间,则设置GLOF_ASPACE 标志
============== ========================================================
每种锁的最短持有时间是指在远程锁授予后忽略远程降级请求
的时间段。此举旨在防止锁在集群节点间持续弹跳而无实质进
展的情况,此现象常见于多节点写入的共享内存映射文件。通
过延迟响应远程回调的降级操作,为用户空间程序争取页面取
消映射前的处理时间。
未来计划将 glock 的 "EX" 模式设为本地共享,使本地锁通
过 i_mutex 实现而非 glock。
glock 操作函数的加锁规则:
============== ====================== =============================
操作 GLF_LOCK 位锁持有 gl_lockref.lock 自旋锁持有
============== ====================== =============================
go_sync 是 否
go_xmote_bh 是 否
go_inval 是 否
go_instantiate 否 否
go_held 否 否
go_dump 有时 是
go_callback 有时(N/A) 是
go_unlocked 是 否
============== ====================== =============================
.. Note::
若入口处持有锁则操作期间不得释放位锁或自旋锁。
go_dump 和 do_demote_ok 严禁阻塞。
仅当 glock 状态指示其缓存最新数据时才会调用 go_dump。
GFS2 内部的 glock 加锁顺序:
1. i_rwsem(如需要)
2. 重命名 glock(仅用于重命名)
3. Inode glock
(父级优先于子级,同级 inode 按锁编号排序)
4. Rgrp glock(用于(反)分配操作)
5. 事务 glock(通过 gfs2_trans_begin,非读操作)
6. i_rw_mutex(如需要)
7. 页锁(始终最后,至关重要!)
每个 inode 对应两把 glock:一把管理 inode 本身(加锁顺
序如上),另一把(称为 iopen glock)结合 inode 的
i_nlink 字段决定 inode 生命周期。inode 加锁基于单个
inode,rgrp 加锁基于单个 rgrp。通常优先获取本地锁再获
取集群锁。
Glock 统计
----------
统计分为两类:超级块相关统计和单个 glock 相关统计。超级
块统计按每 CPU 执行以减少收集开销,并进一步按 glock 类
型细分。所有时间单位为纳秒。
超级块和 glock 统计收集相同信息。超级块时序统计为 glock
时序统计提供默认值,使新建 glock 具有合理的初始值。每个
glock 的计数器在创建时初始化为零,当 glock 从内存移除时
统计丢失。
统计包含三组均值/方差对及两个计数器。均值/方差对为平滑
指数估计,算法与网络代码中的往返时间计算类似(参见《
TCP/IP详解 卷1》第21.3节及《卷2》第25.10节)。与 TCP/IP
案例不同,此处均值/方差未缩放且单位为整数纳秒。
三组均值/方差对测量以下内容:
1. DLM 锁时间(非阻塞请求)
2. DLM 锁时间(阻塞请求)
3. 请求间隔时间(指向 DLM)
非阻塞请求指无论目标 DLM 锁处于何种状态均能立即完成的请求。
当前满足条件的请求包括:(a)锁当前状态为互斥(如锁降级)、
(b)请求状态为空置或解锁(同样如锁降级)、或(c)设置"try lock"
标志的请求。其余锁请求均属阻塞请求。
两个计数器分别统计:
1. 锁请求总数(决定均值/方差计算的数据量)
2. glock 代码顶层的持有者排队数(通常远大于 DLM 锁请求数)
为什么收集这些统计数据?我们需深入分析时序参数的动因如下:
1. 更精准设置 glock "最短持有时间"
2. 快速识别性能问题
3. 改进资源组分配算法(基于锁等待时间而非盲目 "try lock")
因平滑更新的特性,采样量的阶跃变化需经 8 次采样(方差需
4 次)才能完全体现,解析结果时需审慎考虑。
通过锁请求完成时间和 glock 平均锁请求间隔时间,可计算节
点使用 glock 时长与集群共享时长的占比,对设置锁最短持有
时间至关重要。
我们已采取严谨措施,力求精准测量目标量值。任何测量系统均
存在误差,但我期望当前方案已达到合理精度极限。
超级块状态统计路径::
/sys/kernel/debug/gfs2/<fsname>/sbstats
Glock 状态统计路径::
/sys/kernel/debug/gfs2/<fsname>/glstats
(假设 debugfs 挂载于 /sys/kernel/debug,且 <fsname> 替
换为对应 GFS2 文件系统名)
输出缩写说明:
========= ============================================
srtt 非阻塞 DLM 请求的平滑往返时间
srttvar srtt 的方差估计
srttb (潜在)阻塞 DLM 请求的平滑往返时间
srttvarb srttb 的方差估计
sirt DLM 请求的平滑请求间隔时间
sirtvar sirt 的方差估计
dlm DLM 请求数(glstats 文件中的 dcnt)
queue 排队的 glock 请求数(glstats 文件中的 qcnt)
========= ============================================
sbstats文件按glock类型(每种类型8行)和CPU核心(每CPU一列)
记录统计数据集。glstats文件则为每个glock提供统计集,其格式
与glocks文件类似,但所有时序统计量均采用均值/方差格式存储。
gfs2_glock_lock_time 跟踪点实时输出目标 glock 的当前统计
值,并附带每次接收到的dlm响应附加信息:
====== ============
status DLM 请求状态
flags DLM 请求标志
tdiff 该请求的耗时
====== ============
(其余字段同上表)
@@ -0,0 +1,97 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/filesystems/gfs2-uevents.rst
:翻译:
邵明寅 Shao Mingyin <shao.mingyin@zte.com.cn>
:校译:
杨涛 yang tao <yang.tao172@zte.com.cn>
===============
uevents 与 GFS2
===============
在 GFS2 文件系统的挂载生命周期内,会生成多个 uevent。
本文档解释了这些事件的含义及其用途(被 gfs2-utils 中的 gfs_controld 使用)。
GFS2 uevents 列表
=================
1. ADD
------
ADD 事件发生在挂载时。它始终是新建文件系统生成的第一个 uevent。如果挂载成
功,随后会生成 ONLINE uevent。如果挂载失败,则随后会生成 REMOVE uevent。
ADD uevent 包含两个环境变量:SPECTATOR=[0|1] 和 RDONLY=[0|1],分别用
于指定文件系统的观察者状态(一种未分配日志的只读挂载)和只读状态(已分配日志)。
2. ONLINE
---------
ONLINE uevent 在成功挂载或重新挂载后生成。它具有与 ADD uevent 相同的环
境变量。ONLINE uevent 及其用于标识观察者和 RDONLY 状态的两个环境变量是较
新版本内核引入的功能(2.6.32-rc+ 及以上),旧版本内核不会生成此事件。
3. CHANGE
---------
CHANGE uevent 在两种场景下使用。一是报告第一个节点成功挂载文件系统时
(FIRSTMOUNT=Done)。这作为信号告知 gfs_controld,此时集群中其他节点可以
安全挂载该文件系统。
另一个 CHANGE uevent 用于通知文件系统某个日志的日志恢复已完成。它包含两个
环境变量:JID= 指定刚恢复的日志 ID,RECOVERY=[Done|Failed] 表示操作成
功与否。这些 uevent 会在每次日志恢复时生成,无论是在初始挂载过程中,还是
gfs_controld 通过 /sys/fs/gfs2/<fsname>/lock_module/recovery 文件
请求特定日志恢复的结果。
由于早期版本的 gfs_controld 使用 CHANGE uevent 时未检查环境变量以确定状
态,若为其添加新功能,存在用户工具版本过旧导致集群故障的风险。因此,在新增用
于标识成功挂载或重新挂载的 uevent 时,选择了使用 ONLINE uevent。
4. OFFLINE
----------
OFFLINE uevent 仅在文件系统发生错误时生成,是 "withdraw" 机制的一部分。
当前该事件未提供具体错误信息,此问题有待修复。
5. REMOVE
---------
REMOVE uevent 在挂载失败结束或卸载文件系统时生成。所有 REMOVE uevent
之前都至少存在同一文件系统的 ADD uevent。与其他 uevent 不同,它由内核的
kobject 子系统自动生成。
所有 GFS2 uevents 的通用信息(uevent 环境变量)
===============================================
1. LOCKTABLE=
--------------
LOCKTABLE 是一个字符串,其值来源于挂载命令行(locktable=)或 fstab 文件。
它用作文件系统标签,并为 lock_dlm 类型的挂载提供加入集群所需的信息。
2. LOCKPROTO=
-------------
LOCKPROTO 是一个字符串,其值取决于挂载命令行或 fstab 中的设置。其值将是
lock_nolock 或 lock_dlm。未来可能支持其他锁管理器。
3. JOURNALID=
-------------
如果文件系统正在使用日志(观察者挂载不分配日志),则所有 GFS2 uevent 中都
会包含此变量,其值为数字形式的日志 ID。
4. UUID=
--------
在较新版本的 gfs2-utils 中,mkfs.gfs2 会向文件系统超级块写入 UUID。若存
在 UUID,所有与该文件系统相关的 uevent 中均会包含此信息。
@@ -0,0 +1,57 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/filesystems/gfs2.rst
:翻译:
邵明寅 Shao Mingyin <shao.mingyin@zte.com.cn>
:校译:
杨涛 yang tao <yang.tao172@zte.com.cn>
=====================================
全局文件系统 2 (Global File System 2)
=====================================
GFS2 是一个集群文件系统。它允许一组计算机同时使用在它们之间共享的块设备(通
过 FC、iSCSI、NBD 等)。GFS2 像本地文件系统一样读写块设备,但也使用一个锁
模块来让计算机协调它们的 I/O 操作,从而维护文件系统的一致性。GFS2 的出色特
性之一是完美一致性——在一台机器上对文件系统所做的更改会立即显示在集群中的所
有其他机器上。
GFS2 使用可互换的节点间锁定机制,当前支持的机制有:
lock_nolock
- 允许将 GFS2 用作本地文件系统
lock_dlm
- 使用分布式锁管理器 (dlm) 进行节点间锁定。
该 dlm 位于 linux/fs/dlm/
lock_dlm 依赖于在上述 URL 中找到的用户空间集群管理系统。
若要将 GFS2 用作本地文件系统,则不需要外部集群系统,只需::
$ mkfs -t gfs2 -p lock_nolock -j 1 /dev/block_device
$ mount -t gfs2 /dev/block_device /dir
在所有集群节点上都需要安装 gfs2-utils 软件包;对于 lock_dlm,您还需要按
照文档配置 dlm 和 corosync 用户空间工具。
gfs2-utils 可在 https://pagure.io/gfs2-utils 找到。
GFS2 在磁盘格式上与早期版本的 GFS 不兼容,但它已相当接近。
以下手册页 (man pages) 可在 gfs2-utils 中找到:
============ =============================================
fsck.gfs2 用于修复文件系统
gfs2_grow 用于在线扩展文件系统
gfs2_jadd 用于在线向文件系统添加日志
tunegfs2 用于操作、检查和调优文件系统
gfs2_convert 用于将 gfs 文件系统原地转换为 GFS2
mkfs.gfs2 用于创建文件系统
============ =============================================
@@ -0,0 +1,44 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: :ref:`Documentation/filesystems/index.rst <filesystems_index>`
:Translator: Wang Wenhu <wenhu.wang@vivo.com>
.. _cn_filesystems_index:
========================
Linux Kernel中的文件系统
========================
这份正在开发的手册或许在未来某个辉煌的日子里以易懂的形式将Linux虚拟\
文件系统(VFS)层以及基于其上的各种文件系统如何工作呈现给大家。当前\
可以看到下面的内容。
核心 VFS 文档
=============
有关 VFS 层本身以及其算法工作方式的文档,请参阅这些手册。
.. toctree::
:maxdepth: 1
dnotify
文件系统
========
文件系统实现文档。
.. toctree::
:maxdepth: 2
virtiofs
debugfs
tmpfs
ubifs
ubifs-authentication
gfs2
gfs2-uevents
gfs2-glocks
inotify
@@ -0,0 +1,80 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/filesystems/inotify.rst
:翻译:
王龙杰 Wang Longjie <wang.longjie1@zte.com.cn>
==========================================
Inotify - 一个强大且简单的文件变更通知系统
==========================================
文档由 Robert Love <rml@novell.com> 于 2005 年 3 月 15 日开始撰写
文档由 Zhang Zhen <zhenzhang.zhang@huawei.com> 于 2015 年 1 月 4 日更新
- 删除了已废弃的接口,关于用户接口请参考手册页。
(i) 基本原理
问:
不将监控项与被监控对象打开的文件描述符(fd)绑定,这背后的设计决策是什么?
答:
监控项会与打开的 inotify 设备相关联,而非与打开的文件相关联。这解决了 dnotify 的主要问题:
保持文件打开会锁定文件,更糟的是,还会锁定挂载点。因此,dnotify 在带有可移动介质的桌面系统
上难以使用,因为介质将无法被卸载。监控文件不应要求文件处于打开状态。
问:
与每个监控项一个文件描述符的方式相比,采用每个实例一个文件描述符的设计决策是出于什么
考虑?
答:
每个监控项一个文件描述符会很快的消耗掉超出允许数量的文件描述符,其数量会超出实际可管理的范
围,也会超出 select() 能高效处理的范围。诚然,root 用户可以提高每个进程的文件描述符限制,
用户也可以使用 epoll,但同时要求这两者是不合理且多余的。一个监控项所消耗的内存比一个打开的文
件要少,因此将这两个数量空间分开是合理的。当前的设计正是用户空间开发者所期望的:用户只需初始
化一次 inotify,然后添加 n 个监控项,而这只需要一个文件描述符,无需调整文件描述符限制。初
始化 inotify 实例初始化两千次是很荒谬的。如果我们能够简洁地实现用户空间的偏好——而且我们
确实可以,idr 层让这类事情变得轻而易举——那么我们就应该这么做。
还有其他合理的理由。如果只有一个文件描述符,那就只需要在该描述符上阻塞,它对应着一个事件队列。
这个单一文件描述符会返回所有的监控事件以及任何可能的带外数据。而如果每个文件描述符都是一个独
立的监控项,
- 将无法知晓事件的顺序。文件 foo 和文件 bar 上的事件会触发两个文件描述符上的 poll(),
但无法判断哪个事件先发生。而用单个队列就可以很容易的提供事件的顺序。这种顺序对现有的应用程
序(如 Beagle)至关重要。想象一下,如果“mv a b ; mv b a”这样的事件没有顺序会是什么
情况。
- 我们将不得不维护 n 个文件描述符和 n 个带有状态的内部队列,而不是仅仅一个。这在 kernel 中
会混乱得多。单个线性队列是合理的数据结构。
- 用户空间开发者更青睐当前的 API。例如,Beagle 的开发者们就很喜欢它。相信我,我问过他们。
这并不奇怪:谁会想通过 select 来管理以及阻塞在 1000 个文件描述符上呢?
- 无法获取带外数据。
- 1024 这个数量仍然太少。 ;-)
当要设计一个可扩展到数千个目录的文件变更通知系统时,处理数千个文件描述符似乎并不是合适的接口。
这太繁琐了。
此外,创建多个实例、处理多个队列以及相应的多个文件描述符是可行的。不必是每个进程对应一个文件描
述符;而是每个队列对应一个文件描述符,一个进程完全可能需要多个队列。
问:
为什么采用系统调用的方式?
答:
糟糕的用户空间接口是 dnotify 的第二大问题。信号对于文件通知来说是一种非常糟糕的接口。其实对
于其他任何事情,信号也都不是好的接口。从各个角度来看,理想的解决方案是基于文件描述符的,它允许
基本的文件 I/O 操作以及 poll/select 操作。获取文件描述符和管理监控项既可以通过设备文件来
实现,也可以通过一系列新的系统调用来实现。我们决定采用一系列系统调用,因为这是提供新的内核接口
的首选方法。两者之间唯一真正的区别在于,我们是想使用 open(2) 和 ioctl(2),还是想使用几
个新的系统调用。系统调用比 ioctl 更有优势。
@@ -0,0 +1,373 @@
Chinese translated version of Documentation/filesystems/sysfs.rst
If you have any comment or update to the content, please contact the
original document maintainer directly. However, if you have a problem
communicating in English you can also ask the Chinese maintainer for
help. Contact the Chinese maintainer if this translation is outdated
or if there is a problem with the translation.
Maintainer: Patrick Mochel <mochel@osdl.org>
Mike Murphy <mamurph@cs.clemson.edu>
Chinese maintainer: Fu Wei <tekkamanninja@gmail.com>
---------------------------------------------------------------------
Documentation/filesystems/sysfs.rst 的中文翻译
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
译存在问题,请联系中文版维护者。
英文版维护者: Patrick Mochel <mochel@osdl.org>
Mike Murphy <mamurph@cs.clemson.edu>
中文版维护者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
中文版翻译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
中文版校译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
以下为正文
---------------------------------------------------------------------
sysfs - 用于导出内核对象(kobject)的文件系统
Patrick Mochel <mochel@osdl.org>
Mike Murphy <mamurph@cs.clemson.edu>
修订: 16 August 2011
原始版本: 10 January 2003
sysfs 简介:
~~~~~~~~~~
sysfs 是一个最初基于 ramfs 且位于内存的文件系统。它提供导出内核
数据结构及其属性,以及它们之间的关联到用户空间的方法。
sysfs 始终与 kobject 的底层结构紧密相关。请阅读
Documentation/core-api/kobject.rst 文档以获得更多关于 kobject 接口的
信息。
使用 sysfs
~~~~~~~~~~~
只要内核配置中定义了 CONFIG_SYSFS ,sysfs 总是被编译进内核。你可
通过以下命令挂载它:
mount -t sysfs sysfs /sys
创建目录
~~~~~~~~
任何 kobject 在系统中注册,就会有一个目录在 sysfs 中被创建。这个
目录是作为该 kobject 的父对象所在目录的子目录创建的,以准确地传递
内核的对象层次到用户空间。sysfs 中的顶层目录代表着内核对象层次的
共同祖先;例如:某些对象属于某个子系统。
Sysfs 在与其目录关联的 kernfs_node 对象中内部保存一个指向实现
目录的 kobject 的指针。以前,这个 kobject 指针被 sysfs 直接用于
kobject 文件打开和关闭的引用计数。而现在的 sysfs 实现中,kobject
引用计数只能通过 sysfs_schedule_callback() 函数直接修改。
属性
~~~~
kobject 的属性可在文件系统中以普通文件的形式导出。Sysfs 为属性定义
了面向文件 I/O 操作的方法,以提供对内核属性的读写。
属性应为 ASCII 码文本文件。以一个文件只存储一个属性值为宜。但一个
文件只包含一个属性值可能影响效率,所以一个包含相同数据类型的属性值
数组也被广泛地接受。
混合类型、表达多行数据以及一些怪异的数据格式会遭到强烈反对。这样做是
很丢脸的,而且其代码会在未通知作者的情况下被重写。
一个简单的属性结构定义如下:
struct attribute {
char * name;
struct module *owner;
umode_t mode;
};
int sysfs_create_file(struct kobject * kobj, const struct attribute * attr);
void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr);
一个单独的属性结构并不包含读写其属性值的方法。子系统最好为增删特定
对象类型的属性定义自己的属性结构体和封装函数。
例如:驱动程序模型定义的 device_attribute 结构体如下:
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};
int device_create_file(struct device *, const struct device_attribute *);
void device_remove_file(struct device *, const struct device_attribute *);
为了定义设备属性,同时定义了一下辅助宏:
#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
例如:声明
static DEVICE_ATTR(foo, S_IWUSR | S_IRUGO, show_foo, store_foo);
等同于如下代码:
static struct device_attribute dev_attr_foo = {
.attr = {
.name = "foo",
.mode = S_IWUSR | S_IRUGO,
.show = show_foo,
.store = store_foo,
},
};
子系统特有的回调函数
~~~~~~~~~~~~~~~~~~~
当一个子系统定义一个新的属性类型时,必须实现一系列的 sysfs 操作,
以帮助读写调用实现属性所有者的显示和储存方法。
struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct attribute *, char *);
ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};
[子系统应已经定义了一个 struct kobj_type 结构体作为这个类型的
描述符,并在此保存 sysfs_ops 的指针。更多的信息参见 kobject 的
文档]
sysfs 会为这个类型调用适当的方法。当一个文件被读写时,这个方法会
将一般的kobject 和 attribute 结构体指针转换为适当的指针类型后
调用相关联的函数。
示例:
#define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr)
static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct device_attribute *dev_attr = to_dev_attr(attr);
struct device *dev = kobj_to_dev(kobj);
ssize_t ret = -EIO;
if (dev_attr->show)
ret = dev_attr->show(dev, dev_attr, buf);
if (ret >= (ssize_t)PAGE_SIZE) {
printk("dev_attr_show: %pS returned bad count\n",
dev_attr->show);
}
return ret;
}
读写属性数据
~~~~~~~~~~~~
在声明属性时,必须指定 show() 或 store() 方法,以实现属性的
读或写。这些方法的类型应该和以下的设备属性定义一样简单。
ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
也就是说,他们应只以一个处理对象、一个属性和一个缓冲指针作为参数。
sysfs 会分配一个大小为 (PAGE_SIZE) 的缓冲区并传递给这个方法。
Sysfs 将会为每次读写操作调用一次这个方法。这使得这些方法在执行时
会出现以下的行为:
- 在读方面(read(2)),show() 方法应该填充整个缓冲区。回想属性
应只导出了一个属性值或是一个同类型属性值的数组,所以这个代价将
不会不太高。
这使得用户空间可以局部地读和任意的向前搜索整个文件。如果用户空间
向后搜索到零或使用‘0’偏移执行一个pread(2)操作,show()方法将
再次被调用,以重新填充缓存。
- 在写方面(write(2)),sysfs 希望在第一次写操作时得到整个缓冲区。
之后 Sysfs 传递整个缓冲区给 store() 方法。
当要写 sysfs 文件时,用户空间进程应首先读取整个文件,修该想要
改变的值,然后回写整个缓冲区。
在读写属性值时,属性方法的执行应操作相同的缓冲区。
注记:
- 写操作导致的 show() 方法重载,会忽略当前文件位置。
- 缓冲区应总是 PAGE_SIZE 大小。对于i386,这个值为4096。
- show() 方法应该返回写入缓冲区的字节数,也就是 scnprintf()的
返回值。
- show() 方法在将格式化返回值返回用户空间的时候,禁止使用snprintf()。
如果可以保证不会发生缓冲区溢出,可以使用sprintf(),否则必须使用
scnprintf()。
- store() 应返回缓冲区的已用字节数。如果整个缓存都已填满,只需返回
count 参数。
- show() 或 store() 可以返回错误值。当得到一个非法值,必须返回一个
错误值。
- 一个传递给方法的对象将会通过 sysfs 调用对象内嵌的引用计数固定在
内存中。尽管如此,对象代表的物理实体(如设备)可能已不存在。如有必要,
应该实现一个检测机制。
一个简单的(未经实验证实的)设备属性实现如下:
static ssize_t show_name(struct device *dev, struct device_attribute *attr,
char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%s\n", dev->name);
}
static ssize_t store_name(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
snprintf(dev->name, sizeof(dev->name), "%.*s",
(int)min(count, sizeof(dev->name) - 1), buf);
return count;
}
static DEVICE_ATTR(name, S_IRUGO, show_name, store_name);
(注意:真正的实现不允许用户空间设置设备名。)
顶层目录布局
~~~~~~~~~~~~
sysfs 目录的安排显示了内核数据结构之间的关系。
顶层 sysfs 目录如下:
block/
bus/
class/
dev/
devices/
firmware/
net/
fs/
devices/ 包含了一个设备树的文件系统表示。他直接映射了内部的内核
设备树,反映了设备的层次结构。
bus/ 包含了内核中各种总线类型的平面目录布局。每个总线目录包含两个
子目录:
devices/
drivers/
devices/ 包含了系统中出现的每个设备的符号链接,他们指向 root/ 下的
设备目录。
drivers/ 包含了每个已为特定总线上的设备而挂载的驱动程序的目录(这里
假定驱动没有跨越多个总线类型)。
fs/ 包含了一个为文件系统设立的目录。现在每个想要导出属性的文件系统必须
在 fs/ 下创建自己的层次结构(参见Documentation/filesystems/fuse/fuse.rst)。
dev/ 包含两个子目录: char/ 和 block/。在这两个子目录中,有以
<major>:<minor> 格式命名的符号链接。这些符号链接指向 sysfs 目录
中相应的设备。/sys/dev 提供一个通过一个 stat(2) 操作结果,查找
设备 sysfs 接口快捷的方法。
更多有关 driver-model 的特性信息可以在 Documentation/driver-api/driver-model/
中找到。
TODO: 完成这一节。
当前接口
~~~~~~~~
以下的接口层普遍存在于当前的sysfs中:
- 设备 (include/linux/device.h)
----------------------------------
结构体:
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};
声明:
DEVICE_ATTR(_name, _mode, _show, _store);
增/删属性:
int device_create_file(struct device *dev, const struct device_attribute * attr);
void device_remove_file(struct device *dev, const struct device_attribute * attr);
- 总线驱动程序 (include/linux/device.h)
--------------------------------------
结构体:
struct bus_attribute {
struct attribute attr;
ssize_t (*show)(const struct bus_type *, char * buf);
ssize_t (*store)(const struct bus_type *, const char * buf, size_t count);
};
声明:
BUS_ATTR(_name, _mode, _show, _store)
增/删属性:
int bus_create_file(struct bus_type *, struct bus_attribute *);
void bus_remove_file(struct bus_type *, struct bus_attribute *);
- 设备驱动程序 (include/linux/device.h)
-----------------------------------------
结构体:
struct driver_attribute {
struct attribute attr;
ssize_t (*show)(struct device_driver *, char * buf);
ssize_t (*store)(struct device_driver *, const char * buf,
size_t count);
};
声明:
DRIVER_ATTR(_name, _mode, _show, _store)
增/删属性:
int driver_create_file(struct device_driver *, const struct driver_attribute *);
void driver_remove_file(struct device_driver *, const struct driver_attribute *);
文档
~~~~
sysfs 目录结构以及其中包含的属性定义了一个内核与用户空间之间的 ABI。
对于任何 ABI,其自身的稳定和适当的文档是非常重要的。所有新的 sysfs
属性必须在 Documentation/ABI 中有文档。详见 Documentation/ABI/README。
@@ -0,0 +1,146 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/filesystems/tmpfs.rst
translated by Wang Qing<wangqing@vivo.com>
=====
Tmpfs
=====
Tmpfs是一个将所有文件都保存在虚拟内存中的文件系统。
tmpfs中的所有内容都是临时的,也就是说没有任何文件会在硬盘上创建。
如果卸载tmpfs实例,所有保存在其中的文件都会丢失。
tmpfs将所有文件保存在内核缓存中,随着文件内容增长或缩小可以将不需要的
页面swap出去。它具有最大限制,可以通过“mount -o remount ...”调整。
和ramfs(创建tmpfs的模板)相比,tmpfs包含交换和限制检查。和tmpfs相似的另
一个东西是RAM磁盘(/dev/ram*),可以在物理RAM中模拟固定大小的硬盘,并在
此之上创建一个普通的文件系统。Ramdisks无法swap,因此无法调整它们的大小。
由于tmpfs完全保存于页面缓存和swap中,因此所有tmpfs页面将在/proc/meminfo
中显示为“Shmem”,而在free(1)中显示为“Shared”。请注意,这些计数还包括
共享内存(shmem,请参阅ipcs(1))。获得计数的最可靠方法是使用df(1)和du(1)。
tmpfs具有以下用途:
1) 内核总有一个无法看到的内部挂载,用于共享匿名映射和SYSV共享内存。
挂载不依赖于CONFIG_TMPFS。如果CONFIG_TMPFS未设置,tmpfs对用户不可见。
但是内部机制始终存在。
2) glibc 2.2及更高版本期望将tmpfs挂载在/dev/shm上以用于POSIX共享内存
(shm_open,shm_unlink)。添加内容到/etc/fstab应注意如下:
tmpfs /dev/shm tmpfs defaults 0 0
使用时需要记住创建挂载tmpfs的目录。
SYSV共享内存无需挂载,内部已默认支持。(在2.3内核版本中,必须挂载
tmpfs的前身(shm fs)才能使用SYSV共享内存)
3) 很多人(包括我)都觉的在/tmp和/var/tmp上挂载非常方便,并具有较大的
swap分区。目前循环挂载tmpfs可以正常工作,所以大多数发布都应当可以
使用mkinitrd通过/tmp访问/tmp。
4) 也许还有更多我不知道的地方:-)
tmpfs有三个用于调整大小的挂载选项:
========= ===========================================================
size tmpfs实例分配的字节数限制。默认值是不swap时物理RAM的一半。
如果tmpfs实例过大,机器将死锁,因为OOM处理将无法释放该内存。
nr_blocks 与size相同,但以PAGE_SIZE为单位。
nr_inodes tmpfs实例的最大inode个数。默认值是物理内存页数的一半,或者
(有高端内存的机器)低端内存RAM的页数,二者以较低者为准。
========= ===========================================================
这些参数接受后缀k,m或g表示千,兆和千兆字节,可以在remount时更改。
size参数也接受后缀%用来限制tmpfs实例占用物理RAM的百分比:
未指定size或nr_blocks时,默认值为size=50%
如果nr_blocks=0(或size=0),block个数将不受限制;如果nr_inodes=0,
inode个数将不受限制。这样挂载通常是不明智的,因为它允许任何具有写权限的
用户通过访问tmpfs耗尽机器上的所有内存;但同时这样做也会增强在多个CPU的
场景下的访问。
tmpfs具有为所有文件设置NUMA内存分配策略挂载选项(如果启用了CONFIG_NUMA),
可以通过“mount -o remount ...”调整
======================== =========================
mpol=default 采用进程分配策略
(请参阅 set_mempolicy(2))
mpol=prefer:Node 倾向从给定的节点分配
mpol=bind:NodeList 只允许从指定的链表分配
mpol=interleave 倾向于依次从每个节点分配
mpol=interleave:NodeList 依次从每个节点分配
mpol=local 优先本地节点分配内存
======================== =========================
NodeList格式是以逗号分隔的十进制数字表示大小和范围,最大和最小范围是用-
分隔符的十进制数来表示。例如,mpol=bind0-3,5,7,9-15
带有有效NodeList的内存策略将按指定格式保存,在创建文件时使用。当任务在该
文件系统上创建文件时,会使用到挂载时的内存策略NodeList选项,如果设置的话,
由调用任务的cpuset[请参见Documentation/admin-guide/cgroup-v1/cpusets.rst]
以及下面列出的可选标志约束。如果NodeLists为设置为空集,则文件的内存策略将
恢复为“默认”策略。
NUMA内存分配策略有可选标志,可以用于模式结合。在挂载tmpfs时指定这些可选
标志可以在NodeList之前生效。
Documentation/admin-guide/mm/numa_memory_policy.rst列出所有可用的内存
分配策略模式标志及其对内存策略。
::
=static 相当于 MPOL_F_STATIC_NODES
=relative 相当于 MPOL_F_RELATIVE_NODES
例如,mpol=bind=staticNodeList相当于MPOL_BIND|MPOL_F_STATIC_NODES的分配策略
请注意,如果内核不支持NUMA,那么使用mpol选项挂载tmpfs将会失败;nodelist指定不
在线的节点也会失败。如果您的系统依赖于此,但内核会运行不带NUMA功能(也许是安全
revocery内核),或者具有较少的节点在线,建议从自动模式中省略mpol选项挂载选项。
可以在以后通过“mount -o remount,mpol=Policy:NodeList MountPoint”添加到挂载点。
要指定初始根目录,可以使用如下挂载选项:
==== ====================
模式 权限用八进制数字表示
uid 用户ID
gid 组ID
==== ====================
这些选项对remount没有任何影响。您可以通过chmod(1),chown(1)和chgrp(1)的更改
已经挂载的参数。
tmpfs具有选择32位还是64位inode的挂载选项:
======= =============
inode64 使用64位inode
inode32 使用32位inode
======= =============
在32位内核上,默认是inode32,挂载时指定inode64会被拒绝。
在64位内核上,默认配置是CONFIG_TMPFS_INODE64。inode64避免了单个设备上可能有多个
具有相同inode编号的文件;比如32位应用程序使用glibc如果长期访问tmpfs,一旦达到33
位inode编号,就有EOVERFLOW失败的危险,无法打开大于2GiB的文件,并返回EINVAL。
所以'mount -t tmpfs -o size=10G,nr_inodes=10k,mode=700 tmpfs /mytmpfs'将在
/mytmpfs上挂载tmpfs实例,分配只能由root用户访问的10GB RAM/SWAP,可以有10240个
inode的实例。
:作者:
Christoph Rohland <cr@sap.com>, 1.12.01
:更新:
Hugh Dickins, 4 June 2007
:更新:
KOSAKI Motohiro, 16 Mar 2010
:更新:
Chris Down, 13 July 2020
@@ -0,0 +1,354 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/filesystems/ubifs-authentication.rst
:翻译:
邵明寅 Shao Mingyin <shao.mingyin@zte.com.cn>
:校译:
杨涛 yang tao <yang.tao172@zte.com.cn>
=============
UBIFS认证支持
=============
引言
====
UBIFS 利用 fscrypt 框架为文件内容及文件名提供保密性。这能防止攻击者在单一
时间点读取文件系统内容的攻击行为。典型案例是智能手机丢失时,攻击者若没有文件
系统解密密钥则无法读取设备上的个人数据。
在现阶段,UBIFS 加密尚不能防止攻击者篡改文件系统内容后用户继续使用设备的攻
击场景。这种情况下,攻击者可任意修改文件系统内容而不被用户察觉。例如修改二
进制文件使其执行时触发恶意行为 [DMC-CBC-ATTACK]。由于 UBIFS 大部分文件
系统元数据以明文存储,使得文件替换和内容篡改变得相当容易。
其他全盘加密系统(如 dm-crypt)可以覆盖所有文件系统元数据,这类系统虽然能
增加这种攻击的难度,但特别是当攻击者能多次访问设备时,也有可能实现攻击。对于
基于 Linux 块 IO 层的 dm-crypt 等文件系统,可通过 dm-integrity 或
dm-verity 子系统[DM-INTEGRITY, DM-VERITY]在块层实现完整数据认证,这些
功能也可与 dm-crypt 结合使用[CRYPTSETUP2]。
本文描述一种为 UBIFS 实现文件内容认证和完整元数据认证的方法。由于 UBIFS
使用 fscrypt 进行文件内容和文件名加密,认证系统可与 fscrypt 集成以利用密
钥派生等现有功能。但系统同时也应支持在不启用加密的情况下使用 UBIFS 认证。
MTD, UBI & UBIFS
----------------
在 Linux 中,MTD(内存技术设备)子系统提供访问裸闪存设备的统一接口。运行于
MTD 之上的重要子系统是 UBI(无序块映像),它为闪存设备提供卷管理功能,类似
于块设备的 LVM。此外,UBI 还处理闪存特有的磨损均衡和透明 I/O 错误处理。
UBI 向上层提供逻辑擦除块(LEB),并透明地映射到闪存的物理擦除块(PEB)。
UBIFS 是运行于 UBI 之上的裸闪存文件系统。因此 UBI 处理磨损均衡和部分闪存
特性,而 UBIFS专注于可扩展性、性能和可恢复性。
::
+------------+ +*******+ +-----------+ +-----+
| | * UBIFS * | UBI-BLOCK | | ... |
| JFFS/JFFS2 | +*******+ +-----------+ +-----+
| | +-----------------------------+ +-----------+ +-----+
| | | UBI | | MTD-BLOCK | | ... |
+------------+ +-----------------------------+ +-----------+ +-----+
+------------------------------------------------------------------+
| MEMORY TECHNOLOGY DEVICES (MTD) |
+------------------------------------------------------------------+
+-----------------------------+ +--------------------------+ +-----+
| NAND DRIVERS | | NOR DRIVERS | | ... |
+-----------------------------+ +--------------------------+ +-----+
图1:处理裸闪存的 Linux 内核子系统
UBIFS 内部维护多个持久化在闪存上的数据结构:
- *索引*:存储在闪存上的 B+ 树,叶节点包含文件系统数据
- *日志*:在更新闪存索引前收集文件系统变更的辅助数据结构,可减少闪存磨损
- *树节点缓存(TNC)*:反映当前文件系统状态的内存 B+ 树,避免频繁读取闪存。
本质上是索引的内存表示,但包含额外属性
- *LEB属性树(LPT)*:用于统计每个 UBI LEB 空闲空间的闪存B+树
本节后续将详细讨论UBIFS的闪存数据结构。因为 TNC 不直接持久化到闪存,其在此
处的重要性较低。更多 UBIFS 细节详见[UBIFS-WP]。
UBIFS 索引与树节点缓存
~~~~~~~~~~~~~~~~~~~~~~
UBIFS 在闪存上的基础实体称为 *节点* ,包含多种类型。如存储文件内容块的数据
节点
( ``struct ubifs_data_node`` ),或表示 VFS 索引节点的 inode 节点
( ``struct ubifs_ino_node`` )。几乎所有节点共享包含节点类型、长度、序列
号等基础信息的通用头
( ``ubifs_ch`` )(见内核源码 ``fs/ubifs/ubifs-media.h`` )。LPT条目
和填充节点(用于填充 LEB
尾部不可用空间)等次要节点类型除外。
为避免每次变更重写整个 B+ 树,UBIFS 采用 *wandering tree* 实现:仅重写
变更节点,旧版本被标记废弃而非立即擦除。因此索引不固定存储于闪存某处,而是在
闪存上 *wanders* ,在 LEB 被 UBIFS 重用前,闪存上会存在废弃部分。为定位
最新索引,UBIFS 在 UBI LEB 1 存储称为 *主节点* 的特殊节点,始终指向最新
UBIFS 索引根节点。为增强可恢复性,主节点还备份到 LEB 2。因此挂载 UBIFS 只
需读取 LEB 1 和 2 获取当前主节点,进而定位最新闪存索引。
TNC 是闪存索引的内存表示,包含未持久化的运行时属性(如脏标记)。TNC 作为回
写式缓存,所有闪存索引修改都通过 TNC 完成。与其他缓存类似,TNC 无需将完整
索引全部加载到内存中,需要时从闪存读取部分内容。 *提交* 是更新闪存文件系统
结构(如索引)的 UBIFS 操作。每次提交时,标记为脏的 TNC 节点被写入闪存以更
新持久化索引。
日志
~~~~
为避免闪存磨损,索引仅在满足特定条件(如 ``fsync(2)`` )时才持久化(提交)。
日志用于记录索引提交之间的所有变更(以 inode 节点、数据节点等形式)。挂载时
从闪存读取日志并重放到 TNC(此时 TNC 按需从闪存索引创建)。
UBIFS 保留一组专用于日志的 LEB(称为 *日志区* )。日志区 LEB 数量在文件系
统创建时配置(使用 ``mkfs.ubifs`` )并存储于超级块节点。日志区仅含两类节
点: *引用节点* 和 *提交起始节点* 。执行索引提交时写入提交起始节点,每次日
志更新时写入引用节点。每个引用节点指向构成日志条目的其他节点( inode 节点、
数据节点等)在闪存上的位置,这些节点称为 *bud* ,描述包含数据的实际文件系
统变更。
日志区以环形缓冲区维护。当日志将满时触发提交操作,同时写入提交起始节点。因此
挂载时 UBIFS 查找最新提交起始节点,仅重放其后的引用节点。提交起始节点前的引
用节点将被忽略(因其已属于闪存索引)。
写入日志条目时,UBIFS 首先确保有足够空间写入引用节点和该条目的 bud。然后先
写引用节点,再写描述文件变更的 bud。在日志重放阶段,UBIFS 会记录每个参考节
点,并检查其引用的 LEB位置以定位 buds。若这些数据损坏或丢失,UBIFS 会尝试
通过重新读取 LEB 来恢复,但仅针对日志中最后引用的 LEB,因为只有它可能因断
电而损坏。若恢复失败,UBIFS 将拒绝挂载。对于其他 LEB 的错误,UBIFS 会直接
终止挂载操作。
::
| ---- LOG AREA ---- | ---------- MAIN AREA ------------ |
-----+------+-----+--------+---- ------+-----+-----+---------------
\ | | | | / / | | | \
/ CS | REF | REF | | \ \ DENT | INO | INO | /
\ | | | | / / | | | \
----+------+-----+--------+--- -------+-----+-----+----------------
| | ^ ^
| | | |
+------------------------+ |
| |
+-------------------------------+
图2:包含提交起始节点(CS)和引用节点(REF)的日志区闪存布局,引用节点指向含
bud 的主区
LEB属性树/表
~~~~~~~~~~~~
LEB 属性树用于存储每个 LEB 的信息,包括 LEB 类型、LEB 上的空闲空间和
*脏空间* (旧空间,废弃内容) [1]_ 的数量。因为 UBIFS 从不在单个 LEB 混
合存储索引节点和数据节点,所以 LEB 的类型至关重要,每个 LEB 都有特定用途,
这对空闲空间计算非常有帮助。详见[UBIFS-WP]。
LEB 属性树也是 B+ 树,但远小于索引。因为其体积小,所以每次提交时都整块写入,
保存 LPT 是原子操作。
.. [1] 由于LEB只能追加写入不能覆盖,空闲空间(即 LEB 剩余可写空间)与废弃
内容(先前写入但未擦除前不能覆盖)存在区别。
UBIFS认证
=========
本章介绍UBIFS认证,使UBIFS能验证闪存上元数据和文件内容的真实性与完整性。
威胁模型
--------
UBIFS 认证可检测离线数据篡改。虽然不能防止篡改,但是能让(可信)代码检查闪
存文件内容和文件系统元数据的完整性与真实性,也能检查文件内容被替换的攻击。
UBIFS 认证不防护全闪存内容回滚(攻击者可转储闪存内容并在后期还原)。也不防护
单个索引提交的部分回滚(攻击者能部分撤销变更)。这是因为 UBIFS 不立即覆盖索
引树或日志的旧版本,而是标记为废弃,稍后由垃圾回收擦除。攻击者可擦除当前树部
分内容并还原闪存上尚未擦除的旧版本。因每次提交总会写入索引根节点和主节点的新
版本而不覆盖旧版本,UBI 的磨损均衡操作(将内容从物理擦除块复制到另一擦除块
且非原子擦除原块)进一步助长此问题。
UBIFS 认证不覆盖认证密钥提供后攻击者在设备执行代码的攻击,需结合安全启动和
可信启动等措施确保设备仅执行可信代码。
认证
----
为完全信任从闪存读取的数据,所有存储在闪存的 UBIFS 数据结构均需认证:
- 包含文件内容、扩展属性、文件长度等元数据的索引
- 通过记录文件系统变更来包含文件内容和元数据的日志
- 存储 UBIFS 用于空闲空间统计的 UBI LEB 元数据的 LPT
索引认证
~~~~~~~~
借助 *wandering tree* 概念,UBIFS 仅更新和持久化从叶节点到根节点的变更
部分。这允许用子节点哈希增强索引树节点。最终索引基本成为 Merkle 树:因索引
叶节点含实际文件系统数据,其父索引节点的哈希覆盖所有文件内容和元数据。文件
变更时,UBIFS 索引从叶节点到根节点(含主节点)相应更新,此过程可挂钩以同步
重新计算各变更节点的哈希。读取文件时,UBIFS 可从叶节点到根节点逐级验证哈希
确保节点完整性。
为确保整个索引真实性,UBIFS 主节点存储基于密钥的哈希(HMAC),覆盖自身内容及
索引树根节点哈希。如前所述,主节点在索引持久化时(即索引提交时)总会写入闪存。
此方法仅修改 UBIFS 索引节点和主节点以包含哈希,其他类型节点保持不变,减少了
对 UBIFS 用户(如嵌入式设备)宝贵的存储开销。
::
+---------------+
| Master Node |
| (hash) |
+---------------+
|
v
+-------------------+
| Index Node #1 |
| |
| branch0 branchn |
| (hash) (hash) |
+-------------------+
| ... | (fanout: 8)
| |
+-------+ +------+
| |
v v
+-------------------+ +-------------------+
| Index Node #2 | | Index Node #3 |
| | | |
| branch0 branchn | | branch0 branchn |
| (hash) (hash) | | (hash) (hash) |
+-------------------+ +-------------------+
| ... | ... |
v v v
+-----------+ +----------+ +-----------+
| Data Node | | INO Node | | DENT Node |
+-----------+ +----------+ +-----------+
图3:索引节点哈希与主节点 HMAC 的覆盖范围
健壮性性和断电安全性的关键在于以原子操作持久化哈希值与文件内容。UBIFS 现有
的变更节点持久化机制专为此设计,能够确保断电时安全恢复。为索引节点添加哈希值
不会改变该机制,因为每个哈希值都与其对应节点以原子操作同步持久化。
日志认证
~~~~~~~~
日志也需要认证。因为日志持续写入,必须频繁地添加认证信息以确保断电时未认证数
据量可控。方法是从提交起始节点开始,对先前引用节点、当前引用节点和 bud 节点
创建连续哈希链。适时地在bud节点间插入认证节点,这种新节点类型包含哈希链当前
状态的 HMAC。因此日志可认证至最后一个认证节点。日志尾部无认证节点的部分无法
认证,在日志重放时跳过。
日志认证示意图如下::
,,,,,,,,
,......,...........................................
,. CS , hash1.----. hash2.----.
,. | , . |hmac . |hmac
,. v , . v . v
,.REF#0,-> bud -> bud -> bud.-> auth -> bud -> bud.-> auth ...
,..|...,...........................................
, | ,
, | ,,,,,,,,,,,,,,,
. | hash3,----.
, | , |hmac
, v , v
, REF#1 -> bud -> bud,-> auth ...
,,,|,,,,,,,,,,,,,,,,,,
v
REF#2 -> ...
|
V
...
因为哈希值包含引用节点,攻击者无法重排或跳过日志头重放,仅能移除日志尾部的
bud 节点或引用节点,最大限度将文件系统回退至上次提交。
日志区位置存储于主节点。因为主节点通过 HMAC 认证,所以未经检测无法篡改。日
志区大小在文件系统创建时由 `mkfs.ubifs` 指定并存储于超级块节点。为避免篡
改此值及其他参数,超级块结构添加 HMAC。超级块节点存储在 LEB 0,仅在功能标
志等变更时修改,文件变更时不修改。
LPT认证
~~~~~~~
LPT 根节点在闪存上的位置存储于 UBIFS 主节点。因为 LPT 每次提交时都以原子
操作写入和读取,无需单独认证树节点。通过主节点存储的简单哈希保护完整 LPT
即可。因为主节点自身已认证,通过验证主节点真实性并比对存储的 LTP 哈希与读
取的闪存 LPT 计算哈希值,即可验证 LPT 真实性。
密钥管理
--------
为了简化实现,UBIFS 认证使用单一密钥计算超级块、主节点、提交起始节点和引用
节点的 HMAC。创建文件系统(`mkfs.ubifs`) 时需提供此密钥以认证超级块节点。
挂载文件系统时也需此密钥验证认证节点并为变更生成新 HMAC。
UBIFS 认证旨在与 UBIFS 加密(fscrypt)协同工作以提供保密性和真实性。因为
UBIFS 加密采用基于目录的差异化加密策略,可能存在多个 fscrypt 主密钥甚至未
加密目录。而 UBIFS 认证采用全有或全无方式,要么认证整个文件系统要么完全不
认证。基于此特性,且为确保认证机制可独立于加密功能使用,UBIFS 认证不与
fscrypt 共享主密钥,而是维护独立的认证专用密钥。
提供认证密钥的API尚未定义,但可通过类似 fscrypt 的用户空间密钥环提供。需注
意当前 fscrypt 方案存在缺陷,用户空间 API 终将变更[FSCRYPT-POLICY2]。
用户仍可通过用户空间提供单一口令或密钥覆盖 UBIFS 认证与加密。相应用户空间工
具可解决此问题:除派生的 fscrypt 加密主密钥外,额外派生认证密钥。
为检查挂载时密钥可用性,UBIFS 超级块节点将额外存储认证密钥的哈希。此方法类
似 fscrypt 加密策略 v2 提出的方法[FSCRYPT-POLICY2]。
未来扩展
========
特定场景下,若供应商需要向客户提供认证文件系统镜像,应该能在不共享 UBIFS 认
证密钥的前提下实现。方法是在每个 HMAC 外额外存储数字签名,供应商随文件系统
镜像分发公钥。若该文件系统后续需要修改,若后续需修改该文件系统,UBIFS 可在
首次挂载时将全部数字签名替换为 HMAC,其处理逻辑与 IMA/EVM 子系统应对此类情
况的方式类似。此时,HMAC 密钥需按常规方式预先提供。
参考
====
[CRYPTSETUP2] https://www.saout.de/pipermail/dm-crypt/2017-November/005745.html
[DMC-CBC-ATTACK] https://www.jakoblell.com/blog/2013/12/22/practical-malleability-attack-against-cbc-en
crypted-luks-partitions/
[DM-INTEGRITY] https://www.kernel.org/doc/Documentation/device-mapper/dm-integrity.rst
[DM-VERITY] https://www.kernel.org/doc/Documentation/device-mapper/verity.rst
[FSCRYPT-POLICY2] https://www.spinics.net/lists/linux-ext4/msg58710.html
[UBIFS-WP] http://www.linux-mtd.infradead.org/doc/ubifs_whitepaper.pdf
@@ -0,0 +1,114 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/filesystems/ubifs.rst
:翻译:
邵明寅 Shao Mingyin <shao.mingyin@zte.com.cn>
:校译:
杨涛 yang tao <yang.tao172@zte.com.cn>
============
UBI 文件系统
============
简介
====
UBIFS 文件系统全称为 UBI 文件系统(UBI File System)。UBI 代表无序块镜
像(Unsorted Block Images)。UBIFS 是一种闪存文件系统,这意味着它专为闪
存设备设计。需要理解的是,UBIFS与 Linux 中任何传统文件系统(如 Ext2、
XFS、JFS 等)完全不同。UBIFS 代表一类特殊的文件系统,它们工作在 MTD 设备
而非块设备上。该类别的另一个 Linux 文件系统是 JFFS2。
为更清晰说明,以下是 MTD 设备与块设备的简要比较:
1. MTD 设备代表闪存设备,由较大尺寸的擦除块组成,通常约 128KiB。块设备由
小块组成,通常 512 字节。
2. MTD 设备支持 3 种主要操作:在擦除块内偏移位置读取、在擦除块内偏移位置写
入、以及擦除整个擦除块。块设备支持 2 种主要操作:读取整个块和写入整个块。
3. 整个擦除块必须先擦除才能重写内容。块可直接重写。
4. 擦除块在经历一定次数的擦写周期后会磨损,通常 SLC NAND 和 NOR 闪存为
100K-1G 次,MLC NAND 闪存为 1K-10K 次。块设备不具备磨损特性。
5. 擦除块可能损坏(仅限 NAND 闪存),软件需处理此问题。硬盘上的块通常不会损
坏,因为硬件有坏块替换机制(至少现代 LBA 硬盘如此)。
这充分说明了 UBIFS 与传统文件系统的本质差异。
UBIFS 工作在 UBI 层之上。UBI 是一个独立的软件层(位于 drivers/mtd/ubi),
本质上是卷管理和磨损均衡层。它提供称为 UBI 卷的高级抽象,比 MTD 设备更上层。
UBI 设备的编程模型与 MTD 设备非常相似,仍由大容量擦除块组成,支持读/写/擦
除操作,但 UBI 设备消除了磨损和坏块限制(上述列表的第 4 和第 5 项)。
某种意义上,UBIFS 是 JFFS2 文件系统的下一代产品,但它与 JFFS2 差异巨大且
不兼容。主要区别如下:
* JFFS2 工作在 MTD 设备之上,UBIFS 依赖于 UBI 并工作在 UBI 卷之上。
* JFFS2 没有介质索引,需在挂载时构建索引,这要求全介质扫描。UBIFS 在闪存
介质上维护文件系统索引信息,无需全介质扫描,因此挂载速度远快于 JFFS2。
* JFFS2 是直写(write-through)文件系统,而 UBIFS 支持回写
(write-back),这使得 UBIFS 写入速度快得多。
与 JFFS2 类似,UBIFS 支持实时压缩,可将大量数据存入闪存。
与 JFFS2 类似,UBIFS 能容忍异常重启和断电。它不需要类似 fsck.ext2 的工
具。UBIFS 会自动重放日志并从崩溃中恢复,确保闪存数据结构的一致性。
UBIFS 具有对数级扩展性(其使用的数据结构多为树形),因此挂载时间和内存消耗不
像 JFFS2 那样线性依赖于闪存容量。这是因为 UBIFS 在闪存介质上维护文件系统
索引。但 UBIFS 依赖于线性扩展的 UBI 层,因此整体 UBI/UBIFS 栈仍是线性扩
展。尽管如此,UBIFS/UBI 的扩展性仍显著优于 JFFS2。
UBIFS 开发者认为,未来可开发同样具备对数级扩展性的 UBI2。UBI2 将支持与
UBI 相同的 API,但二进制不兼容。因此 UBIFS 无需修改即可使用 UBI2。
挂载选项
========
(*) 表示默认选项。
==================== =======================================================
bulk_read 批量读取以利用闪存介质的顺序读取加速特性
no_bulk_read (*) 禁用批量读取
no_chk_data_crc (*) 跳过数据节点的 CRC 校验以提高读取性能。 仅在闪存
介质高度可靠时使用此选项。 此选项可能导致文件内容损坏无法被
察觉。
chk_data_crc 强制校验数据节点的 CRC
compr=none 覆盖默认压缩器,设置为"none"
compr=lzo 覆盖默认压缩器,设置为"LZO"
compr=zlib 覆盖默认压缩器,设置为"zlib"
auth_key= 指定用于文件系统身份验证的密钥。
使用此选项将强制启用身份验证。
传入的密钥必须存在于内核密钥环中, 且类型必须是'logon'
auth_hash_name= 用于身份验证的哈希算法。同时用于哈希计算和 HMAC
生成。典型值包括"sha256"或"sha512"
==================== =======================================================
快速使用指南
============
挂载的 UBI 卷通过 "ubiX_Y" 或 "ubiX:NAME" 语法指定,其中 "X" 是 UBI
设备编号,"Y" 是 UBI 卷编号,"NAME" 是 UBI 卷名称。
将 UBI 设备 0 的卷 0 挂载到 /mnt/ubifs::
$ mount -t ubifs ubi0_0 /mnt/ubifs
将 UBI 设备 0 的 "rootfs" 卷挂载到 /mnt/ubifs("rootfs" 是卷名)::
$ mount -t ubifs ubi0:rootfs /mnt/ubifs
以下是内核启动参数的示例,用于将 mtd0 附加到 UBI 并挂载 "rootfs" 卷:
ubi.mtd=0 root=ubi0:rootfs rootfstype=ubifs
参考资料
========
UBIFS 文档及常见问题解答/操作指南请访问 MTD 官网:
- http://www.linux-mtd.infradead.org/doc/ubifs.html
- http://www.linux-mtd.infradead.org/faq/ubifs.html
@@ -0,0 +1,58 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: :ref:`Documentation/filesystems/virtiofs.rst <virtiofs_index>`
译者
::
中文版维护者: 王文虎 Wang Wenhu <wenhu.wang@vivo.com>
中文版翻译者: 王文虎 Wang Wenhu <wenhu.wang@vivo.com>
中文版校译者: 王文虎 Wang Wenhu <wenhu.wang@vivo.com>
===========================================
virtiofs: virtio-fs 主机<->客机共享文件系统
===========================================
- Copyright (C) 2020 Vivo Communication Technology Co. Ltd.
介绍
====
Linux的virtiofs文件系统实现了一个半虚拟化VIRTIO类型“virtio-fs”设备的驱动,通过该\
类型设备实现客机<->主机文件系统共享。它允许客机挂载一个已经导出到主机的目录。
客机通常需要访问主机或者远程系统上的文件。使用场景包括:在新客机安装时让文件对其\
可见;从主机上的根文件系统启动;对无状态或临时客机提供持久存储和在客机之间共享目录。
尽管在某些任务可能通过使用已有的网络文件系统完成,但是却需要非常难以自动化的配置\
步骤,且将存储网络暴露给客机。而virtio-fs设备通过提供不经过网络的文件系统访问文件\
的设计方式解决了这些问题。
另外,virto-fs设备发挥了主客机共存的优点提高了性能,并且提供了网络文件系统所不具备
的一些语义功能。
用法
====
以``myfs``标签将文件系统挂载到``/mnt``:
.. code-block:: sh
guest# mount -t virtiofs myfs /mnt
请查阅 https://virtio-fs.gitlab.io/ 了解配置QEMU和virtiofsd守护程序的详细信息。
内幕
====
由于virtio-fs设备将FUSE协议用于文件系统请求,因此Linux的virtiofs文件系统与FUSE文\
件系统客户端紧密集成在一起。客机充当FUSE客户端而主机充当FUSE服务器,内核与用户空\
间之间的/dev/fuse接口由virtio-fs设备接口代替。
FUSE请求被置于虚拟队列中由主机处理。主机填充缓冲区中的响应部分,而客机处理请求的完成部分。
将/dev/fuse映射到虚拟队列需要解决/dev/fuse和虚拟队列之间语义上的差异。每次读取\
/dev/fuse设备时,FUSE客户端都可以选择要传输的请求,从而可以使某些请求优先于其他\
请求。虚拟队列有其队列语义,无法更改已入队请求的顺序。在虚拟队列已满的情况下尤
其关键,因为此时不可能加入高优先级的请求。为了解决此差异,virtio-fs设备采用“hiprio”\
(高优先级)虚拟队列,专门用于有别于普通请求的高优先级请求。