Merge tag 'Chinese-docs-6.18' of gitolite.kernel.org:pub/scm/linux/kernel/git/alexs/linux into alex

Chinese translation docs for 6.18

This is the Chinese translation subtree for 6.18. It includes
the following changes:
        - docs/zh_CN: Add rust Chinese translations
        - docs/zh_CN: Add scsi Chinese translations
        - docs/zh_CN: Add gfs2 Chinese translations
        - Add some other Chinese translations and fixes

Above patches are tested by 'make htmldocs'
This commit is contained in:
Jonathan Corbet
2025-11-03 16:12:37 -07:00
29 changed files with 5056 additions and 15 deletions

View File

@@ -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。

View File

@@ -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 加锁基于单个
inodergrp 加锁基于单个 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 该请求的耗时
====== ============
(其余字段同上表)

View File

@@ -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= 指定刚恢复的日志 IDRECOVERY=[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 中均会包含此信息。

View File

@@ -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 用于创建文件系统
============ =============================================

View File

@@ -15,6 +15,16 @@ Linux Kernel中的文件系统
文件系统VFS层以及基于其上的各种文件系统如何工作呈现给大家。当前\
可以看到下面的内容。
核心 VFS 文档
=============
有关 VFS 层本身以及其算法工作方式的文档,请参阅这些手册。
.. toctree::
:maxdepth: 1
dnotify
文件系统
========
@@ -26,4 +36,9 @@ Linux Kernel中的文件系统
virtiofs
debugfs
tmpfs
ubifs
ubifs-authentication
gfs2
gfs2-uevents
gfs2-glocks
inotify

View File

@@ -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 更有优势。

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,176 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/networking/generic-hdlc.rst
:翻译:
孙渔喜 Sun yuxi <sun.yuxi@zte.com.cn>
==========
通用HDLC层
==========
Krzysztof Halasa <khc@pm.waw.pl>
通用HDLC层当前支持以下协议:
1. 帧中继支持ANSI、CCITT、Cisco及无LMI模式
- 常规(路由)接口和以太网桥接(以太网设备仿真)接口
可共享同一条PVC。
- 支持ARP内核暂不支持InARP但可通过实验性用户空间守护程序实现
下载地址http://www.kernel.org/pub/linux/utils/net/hdlc/)。
2. 原始HDLC —— 支持IPIPv4接口或以太网设备仿真
3. Cisco HDLC
4. PPP
5. X.25使用X.25协议栈)
通用HDLC仅作为协议驱动 - 必须配合具体硬件的底层驱动
才能运行。
以太网设备仿真使用HDLC或帧中继PVC兼容IEEE 802.1QVLAN
802.1D(以太网桥接)。
请确保已加载 hdlc.o 和硬件驱动程序。系统将为每个WAN端口创建一个
"hdlc"网络设备如hdlc0等。您需要使用"sethdlc"工具,可从以下
地址获取:
http://www.kernel.org/pub/linux/utils/net/hdlc/
编译 sethdlc.c 工具::
gcc -O2 -Wall -o sethdlc sethdlc.c
请确保使用与您内核版本匹配的 sethdlc 工具。
使用 sethdlc 工具设置物理接口、时钟频率、HDLC 模式,
若使用帧中继还需添加所需的 PVC。
通常您需要执行类似以下命令::
sethdlc hdlc0 clock int rate 128000
sethdlc hdlc0 cisco interval 10 timeout 25
::
sethdlc hdlc0 rs232 clock ext
sethdlc hdlc0 fr lmi ansi
sethdlc hdlc0 create 99
ifconfig hdlc0 up
ifconfig pvc0 localIP pointopoint remoteIP
在帧中继模式下请先启用主hdlc设备不分配IP地址
使用pvc设备。
接口设置选项:
* v35 | rs232 | x21 | t1 | e1
- 当网卡支持软件可选接口时,可为指定端口设置物理接口
loopback
- 启用硬件环回(仅用于测试)
* clock ext
- RX与TX时钟均使用外部时钟源
* clock int
- RX与TX时钟均使用内部时钟源
* clock txint
- RX时钟使用外部时钟源TX时钟使用内部时钟源
* clock txfromrx
- RX时钟使用外部时钟源TX时钟从RX时钟派生
* rate
- 设置时钟速率(仅适用于"int"或"txint"时钟模式)
设置协议选项:
* hdlc - 设置原始HDLC模式仅支持IP协议
nrz / nrzi / fm-mark / fm-space / manchester - 传输编码选项
no-parity / crc16 / crc16-pr0 (预设零值的CRC16) / crc32-itu
crc16-itu (使用ITU-T多项式的CRC16) / crc16-itu-pr0 - 校验方式选项
* hdlc-eth - 使用HDLC进行以太网设备仿真. 校验和编码方式同上
as above.
* cisco - 设置Cisco HDLC模式支持IP、IPv6和IPX协议
interval - 保活数据包发送间隔(秒)
timeout - 未收到保活数据包的超时时间(秒),超过此时长将判定
链路断开
* ppp - 设置同步PPP模式
* x25 - 设置X.25模式
* fr - 帧中继模式
lmi ansi / ccitt / cisco / none - LMI(链路管理)类型
dce - 将帧中继设置为DCE网络侧LMI模式默认为DTE用户侧
此设置与时钟无关!
- t391 - 链路完整性验证轮询定时器(秒)- 用户侧
- t392 - 轮询验证定时器(秒)- 网络侧
- n391 - 全状态轮询计数器 - 用户侧
- n392 - 错误阈值 - 用户侧和网络侧共用
- n393 - 监控事件计数 - 用户侧和网络侧共用
帧中继专用命令:
* create n | delete n - 添加/删除DLCI编号为n的PVC接口。
新创建的接口将命名为pvc0、pvc1等。
* create ether n | delete ether n - 添加/删除用于以太网
桥接帧的设备设备将命名为pvceth0、pvceth1等。
板卡特定问题
------------
n2.o 和 c101.o 驱动模块需要参数才能工作::
insmod n2 hw=io,irq,ram,ports[:io,irq,...]
示例::
insmod n2 hw=0x300,10,0xD0000,01
::
insmod c101 hw=irq,ram[:irq,...]
示例::
insmod c101 hw=9,0xdc000
若直接编译进内核,这些驱动需要通过内核(命令行)参数配置::
n2.hw=io,irq,ram,ports:...
::
c101.hw=irq,ram:...
若您的N2、C101或PLX200SYN板卡出现问题可通过"private"
命令查看端口数据包描述符环(显示在内核日志中)
sethdlc hdlc0 private
硬件驱动需使用#define DEBUG_RINGS编译选项构建。
在提交错误报告时附上这些信息将很有帮助。如在使用过程中遇
到任何问题,请随时告知。
获取补丁和其他信息,请访问:
<http://www.kernel.org/pub/linux/utils/net/hdlc/>.

View File

@@ -27,6 +27,9 @@
xfrm_proc
netmem
alias
mptcp-sysctl
generic-hdlc
timestamping
Todolist:
@@ -76,7 +79,6 @@ Todolist:
* eql
* fib_trie
* filter
* generic-hdlc
* generic_netlink
* netlink_spec/index
* gen_stats
@@ -96,7 +98,6 @@ Todolist:
* mctp
* mpls-sysctl
* mptcp
* mptcp-sysctl
* multiqueue
* multi-pf-netdev
* net_cachelines/index
@@ -126,7 +127,6 @@ Todolist:
* sctp
* secid
* seg6-sysctl
* skbuff
* smc-sysctl
* sriov
* statistics
@@ -138,7 +138,6 @@ Todolist:
* tcp_ao
* tcp-thin
* team
* timestamping
* tipc
* tproxy
* tuntap

View File

@@ -0,0 +1,139 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/networking/mptcp-sysctl.rst
:翻译:
孙渔喜 Sun yuxi <sun.yuxi@zte.com.cn>
================
MPTCP Sysfs 变量
================
/proc/sys/net/mptcp/* Variables
===============================
add_addr_timeout - INTEGER (秒)
设置ADD_ADDR控制消息的重传超时时间。当MPTCP对端未确认
先前的ADD_ADDR消息时将在该超时时间后重新发送。
默认值与TCP_RTO_MAX相同。此为每个命名空间的sysctl参数。
默认值120
allow_join_initial_addr_port - BOOLEAN
控制是否允许对端向初始子流使用的IP地址和端口号发送加入
请求1表示允许。此参数会设置连接时发送给对端的标志位
并决定是否接受此类加入请求。
通过ADD_ADDR通告的地址不受此参数影响。
此为每个命名空间的sysctl参数。
默认值1
available_path_managers - STRING
显示已注册的可用路径管理器选项。可能有更多路径管理器可用
但尚未加载。
available_schedulers - STRING
显示已注册的可用调度器选项。可能有更多数据包调度器可用
但尚未加载。
blackhole_timeout - INTEGER (秒)
当发生MPTCP防火墙黑洞问题时初始禁用活跃MPTCP套接字上MPTCP
功能的时间。如果在重新启用MPTCP后立即检测到更多黑洞问题
此时间段将呈指数增长;当黑洞问题消失时,将重置为初始值。
设置为0可禁用黑洞检测功能。此为每个命名空间的sysctl参数。
默认值3600
checksum_enabled - BOOLEAN
控制是否启用DSS校验和功能。
当值为非零时可启用DSS校验和。此为每个命名空间的sysctl参数。
默认值0
close_timeout - INTEGER (seconds)
设置"先断后连"超时时间在未调用close或shutdown系统调用时
MPTCP套接字将在最后一个子流移除后保持当前状态达到该时长
会转为TCP_CLOSE状态。
默认值与TCP_TIMEWAIT_LEN相同。此为每个命名空间的sysctl参数。
默认值60
enabled - BOOLEAN
控制是否允许创建MPTCP套接字。
当值为1时允许创建MPTCP套接字。此为每个命名空间的sysctl参数。
默认值1启用
path_manager - STRING
设置用于每个新MPTCP套接字的默认路径管理器名称。内核路径管理将
根据通过MPTCP netlink API配置的每个命名空间值来控制子流连接
和地址通告。用户空间路径管理将每个MPTCP连接的子流连接决策和地
址通告交由特权用户空间程序控制代价是需要更多netlink流量来
传播所有相关事件和命令。
此为每个命名空间的sysctl参数。
* "kernel" - 内核路径管理器
* "userspace" - 用户空间路径管理器
默认值:"kernel"
pm_type - INTEGER
设置用于每个新MPTCP套接字的默认路径管理器类型。内核路径管理将
根据通过MPTCP netlink API配置的每个命名空间值来控制子流连接
和地址通告。用户空间路径管理将每个MPTCP连接的子流连接决策和地
址通告交由特权用户空间程序控制代价是需要更多netlink流量来
传播所有相关事件和命令。
此为每个命名空间的sysctl参数。
自v6.15起已弃用请改用path_manager参数。
* 0 - 内核路径管理器
* 1 - 用户空间路径管理器
默认值0
scheduler - STRING
选择所需的调度器类型。
支持选择不同的数据包调度器。此为每个命名空间的sysctl参数。
默认值:"default"
stale_loss_cnt - INTEGER
用于判定子流失效stale的MPTCP层重传间隔次数阈值。当指定
子流在连续多个重传间隔内既无数据传输又有待处理数据时,将被标
记为失效状态。失效子流将被数据包调度器忽略。
设置较低的stale_loss_cnt值可实现快速主备切换较高的值则能
最大化边缘场景(如高误码率链路或对端暂停数据处理等异常情况)
的链路利用率。
此为每个命名空间的sysctl参数。
默认值4
syn_retrans_before_tcp_fallback - INTEGER
在回退到 TCP即丢弃 MPTCP 选项之前SYN + MP_CAPABLE
报文的重传次数。换句话说,如果所有报文在传输过程中都被丢弃,
那么将会:
* 首次SYN携带MPTCP支持选项
* 按本参数值重传携带MPTCP选项的SYN包
* 后续重传将不再携带MPTCP支持选项
0 表示首次重传即丢弃MPTCP选项。
>=128 表示所有SYN重传均保留MPTCP选项设置过低的值可能增加
MPTCP黑洞误判几率。此为每个命名空间的sysctl参数。
默认值2

View File

@@ -0,0 +1,674 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/networking/timestamping.rst
:翻译:
王亚鑫 Wang Yaxin <wang.yaxin@zte.com.cn>
======
时间戳
======
1. 控制接口
===========
接收网络数据包时间戳的接口包括:
SO_TIMESTAMP
为每个传入数据包生成(不一定是单调的)系统时间时间戳。通过 recvmsg()
在控制消息中以微秒分辨率报告时间戳。
SO_TIMESTAMP 根据架构类型和 libc 的 lib 中的 time_t 表示方式定义为
SO_TIMESTAMP_NEW 或 SO_TIMESTAMP_OLD。
SO_TIMESTAMP_OLD 和 SO_TIMESTAMP_NEW 的控制消息格式分别为
struct __kernel_old_timeval 和 struct __kernel_sock_timeval。
SO_TIMESTAMPNS
与 SO_TIMESTAMP 相同的时间戳机制,但以 struct timespec 格式报告时间戳,
纳秒分辨率。
SO_TIMESTAMPNS 根据架构类型和 libc 的 time_t 表示方式定义为
SO_TIMESTAMPNS_NEW 或 SO_TIMESTAMPNS_OLD。
控制消息格式对于 SO_TIMESTAMPNS_OLD 为 struct timespec
对于 SO_TIMESTAMPNS_NEW 为 struct __kernel_timespec。
IP_MULTICAST_LOOP + SO_TIMESTAMP[NS]
仅用于多播:通过读取回环数据包接收时间戳,获得近似的传输时间戳。
SO_TIMESTAMPING
在接收、传输或两者时生成时间戳。支持多个时间戳源,包括硬件。
支持为流套接字生成时间戳。
1.1 SO_TIMESTAMP也包括 SO_TIMESTAMP_OLD 和 SO_TIMESTAMP_NEW
---------------------------------------------------------------
此套接字选项在接收路径上启用数据报的时间戳。由于目标套接字(如果有)
在网络栈早期未知,因此必须为所有数据包启用此功能。所有早期接收的时间
戳选项也是如此。
有关接口详细信息,请参阅 `man 7 socket`
始终使用 SO_TIMESTAMP_NEW 时间戳以获得 struct __kernel_sock_timeval
格式的时间戳。
如果时间在 2038 年后SO_TIMESTAMP_OLD 在 32 位机器上将返回错误的时间戳。
1.2 SO_TIMESTAMPNS也包括 SO_TIMESTAMPNS_OLD 和 SO_TIMESTAMPNS_NEW
---------------------------------------------------------------------
此选项与 SO_TIMESTAMP 相同,但返回数据类型有所不同。其 struct timespec
能达到比 SO_TIMESTAMP 的 timeval毫秒更高的分辨率纳秒时间戳。
始终使用 SO_TIMESTAMPNS_NEW 时间戳获得 struct __kernel_timespec 格式
的时间戳。
如果时间在 2038 年后SO_TIMESTAMPNS_OLD 在 32 位机器上将返回错误的时间戳。
1.3 SO_TIMESTAMPING也包括 SO_TIMESTAMPING_OLD 和 SO_TIMESTAMPING_NEW
------------------------------------------------------------------------
支持多种类型的时间戳请求。因此,此套接字选项接受标志位图,而不是布尔值。在::
err = setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &val, sizeof(val));
val 是一个整数,设置了以下任何位。设置其他位将返回 EINVAL 且不更改当前状态。
这个套接字选项配置以下几个方面的时间戳生成:
为单个 sk_buff 结构体生成时间戳1.3.1
将时间戳报告到套接字的错误队列1.3.2
配置相关选项1.3.3
也可以通过 cmsg 为单个 sendmsg 调用启用时间戳生成1.3.4)。
1.3.1 时间戳生成
^^^^^^^^^^^^^^^^
某些位是向协议栈请求尝试生成时间戳。它们的任何组合都是有效的。对这些位的更改适
用于新创建的数据包,而不是已经在协议栈中的数据包。因此,可以通过在两个 setsockopt
调用之间嵌入 send() 调用来选择性地为数据包子集请求时间戳(例如,用于采样),
一个用于启用时间戳生成,一个用于禁用它。时间戳也可能由于特定套接字请求之外的原
因而生成,例如在当系统范围内启用接收时间戳时,如前所述。
SOF_TIMESTAMPING_RX_HARDWARE:
请求由网络适配器生成的接收时间戳。
SOF_TIMESTAMPING_RX_SOFTWARE:
当数据进入内核时请求接收时间戳。这些时间戳在设备驱动程序将数据包交给内核接收
协议栈后生成。
SOF_TIMESTAMPING_TX_HARDWARE:
请求由网络适配器生成的传输时间戳。此标志可以通过套接字选项和控制消息启用。
SOF_TIMESTAMPING_TX_SOFTWARE:
当数据离开内核时请求传输TX时间戳。这些时间戳由设备驱动程序生成并且尽
可能贴近网络接口发送点,但始终在内核将数据包传递给网络接口之前生成。因此,
它们需要驱动程序支持,且可能并非所有设备都可用。此标志可通过套接字选项和
控制消息两种方式启用。
SOF_TIMESTAMPING_TX_SCHED:
在进入数据包调度器之前请求传输时间戳。内核传输延迟(如果很长)通常由排队
延迟主导。此时间戳与在 SOF_TIMESTAMPING_TX_SOFTWARE 处获取的时间戳之
间的差异将暴露此延迟,并且与协议处理无关。协议处理中产生的延迟(如果有)
可以通过从 send() 之前立即获取的用户空间时间戳中减去此时间戳来计算。在
具有虚拟设备的机器上,传输的数据包通过多个设备和多个数据包调度器,在每层
生成时间戳。这允许对排队延迟进行细粒度测量。此标志可以通过套接字选项和控
制消息启用。
SOF_TIMESTAMPING_TX_ACK:
请求在发送缓冲区中的所有数据都已得到确认时生成传输TX时间戳。此选项
仅适用于可靠协议目前仅在TCP协议中实现。对于该协议它可能会过度报告
测量结果因为时间戳是在send()调用时缓冲区中的所有数据(包括该缓冲区)
都被确认时生成的即累积确认。该机制会忽略选择确认SACK和前向确认
FACK。此标志可通过套接字选项和控制消息两种方式启用。
SOF_TIMESTAMPING_TX_COMPLETION:
在数据包传输完成时请求传输时间戳。完成时间戳由内核在从硬件接收数据包完成
报告时生成。硬件可能一次报告多个数据包,完成时间戳反映报告的时序而不是实
际传输时间。此标志可以通过套接字选项和控制消息启用。
1.3.2 时间戳报告
^^^^^^^^^^^^^^^^
其他三个位控制将在生成的控制消息中报告哪些时间戳。对这些位的更改在协议栈中
的时间戳报告位置立即生效。仅当数据包设置了相关的时间戳生成请求时,才会报告
其时间戳。
SOF_TIMESTAMPING_SOFTWARE:
在可用时报告任何软件时间戳。
SOF_TIMESTAMPING_SYS_HARDWARE:
此选项已被弃用和忽略。
SOF_TIMESTAMPING_RAW_HARDWARE:
在可用时报告由 SOF_TIMESTAMPING_TX_HARDWARE 或 SOF_TIMESTAMPING_RX_HARDWARE
生成的硬件时间戳。
1.3.3 时间戳选项
^^^^^^^^^^^^^^^^
接口支持以下选项
SOF_TIMESTAMPING_OPT_ID:
每个数据包生成一个唯一标识符。一个进程可以同时存在多个未完成的时间戳请求。
数据包在传输路径中可能会发生重排序(例如在数据包调度器中)。在这种情况下,
时间戳会以与原始send()调用不同的顺序排队到错误队列中。如此一来,仅根据
时间戳顺序或 payload有效载荷检查并不总能将时间戳与原始send()调用
唯一匹配。
此选项在 send() 时将每个数据包与唯一标识符关联,并与时间戳一起返回。
标识符源自每个套接字的 u32 计数器(会回绕)。对于数据报套接字,计数器
随每个发送的数据包递增。对于流套接字,它随每个字节递增。对于流套接字,
还要设置 SOF_TIMESTAMPING_OPT_ID_TCP请参阅下面的部分。
计数器从零开始。在首次启用套接字选项时初始化。在禁用后再重新启用选项时
重置。重置计数器不会更改系统中现有数据包的标识符。
此选项仅针对传输时间戳实现。在这种情况下时间戳总是与sock_extended_err
结构体一起回环。该选项会修改ee_data字段以传递一个在该套接字所有同时
存在的未完成时间戳请求中唯一的 ID。
进程可以通过控制消息SCM_TS_OPT_IDTCP 套接字不支持)传递特定 ID
从而选择性地覆盖默认生成的 ID示例如下::
struct msghdr *msg;
...
cmsg = CMSG_FIRSTHDR(msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_TS_OPT_ID;
cmsg->cmsg_len = CMSG_LEN(sizeof(__u32));
*((__u32 *) CMSG_DATA(cmsg)) = opt_id;
err = sendmsg(fd, msg, 0);
SOF_TIMESTAMPING_OPT_ID_TCP:
与 SOF_TIMESTAMPING_OPT_ID 一起传递给新的 TCP 时间戳应用程序。
SOF_TIMESTAMPING_OPT_ID 定义了流套接字计数器的增量,但其起始点
并不完全显而易见。此选项修复了这一点。
对于流套接字,如果设置了 SOF_TIMESTAMPING_OPT_ID则此选项应始终
设置。在数据报套接字上,选项没有效果。
一个合理的期望是系统调用后计数器重置为零,因此后续写入 N 字节将生成
计数器为 N-1 的时间戳。SOF_TIMESTAMPING_OPT_ID_TCP 在所有条件下
都实现了此行为。
SOF_TIMESTAMPING_OPT_ID 不带修饰符时通常报告相同,特别是在套接字选项
在无数据传输时设置时。如果正在传输数据它可能与输出队列的长度SIOCOUTQ
偏差。
差异是由于基于 snd_una 与 write_seq 的。snd_una 是 peer 确认的 stream
的偏移量。这取决于外部因素,例如网络 RTT。write_seq 是进程写入的最后一个
字节。此偏移量不受外部输入影响。
差异细微,在套接字选项初始化时配置时不易察觉,但 SOF_TIMESTAMPING_OPT_ID_TCP
行为在任何时候都更稳健。
SOF_TIMESTAMPING_OPT_CMSG:
支持所有时间戳数据包的 recv() cmsg。控制消息已无条件地在所有接收时间戳数据包
和 IPv6 数据包上支持,以及在发送时间戳数据包的 IPv4 数据包上支持。此选项扩展
了它们以在发送时间戳数据包的 IPv4 数据包上支持。一个用例是启用 socket 选项
IP_PKTINFO 以关联数据包与其出口设备,通过启用 socket 选项 IP_PKTINFO 同时。
SOF_TIMESTAMPING_OPT_TSONLY:
仅适用于传输时间戳。使内核返回一个 cmsg 与一个空数据包一起,而不是与原
始数据包一起。这减少了套接字接收预算SO_RCVBUF中收取的内存量并即使
在 sysctl net.core.tstamp_allow_data 为 0 时也提供时间戳。此选项禁用
SOF_TIMESTAMPING_OPT_CMSG。
SOF_TIMESTAMPING_OPT_STATS:
与传输时间戳一起获取的选项性统计信息。它必须与 SOF_TIMESTAMPING_OPT_TSONLY
一起使用。当传输时间戳可用时,统计信息可在类型为 SCM_TIMESTAMPING_OPT_STATS
的单独控制消息中获取,作为 TLVstruct nlattr类型的列表。这些统计信息允许应
用程序将各种传输层统计信息与传输时间戳关联,例如某个数据块被 peer 的接收窗口限
制了多长时间。
SOF_TIMESTAMPING_OPT_PKTINFO:
启用 SCM_TIMESTAMPING_PKTINFO 控制消息以接收带有硬件时间戳的数据包。
消息包含 struct scm_ts_pktinfo它提供接收数据包的实际接口索引和层 2 长度。
只有在 CONFIG_NET_RX_BUSY_POLL 启用且驱动程序使用 NAPI 时,才会返回非零的
有效接口索引。该结构还包含另外两个字段,但它们是保留字段且未定义。
SOF_TIMESTAMPING_OPT_TX_SWHW:
请求在 SOF_TIMESTAMPING_TX_HARDWARE 和 SOF_TIMESTAMPING_TX_SOFTWARE
同时启用时,为传出数据包生成硬件和软件时间戳。如果同时生成两个时间戳,两个单
独的消息将回环到套接字的错误队列,每个消息仅包含一个时间戳。
SOF_TIMESTAMPING_OPT_RX_FILTER:
过滤掉虚假接收时间戳:仅当匹配的时间戳生成标志已启用时才报告接收时间戳。
接收时间戳在入口路径中生成较早,在数据包的目的套接字确定之前。如果任何套接
字启用接收时间戳,所有套接字的数据包将接收时间戳数据包。包括那些请求时间戳
报告与 SOF_TIMESTAMPING_SOFTWARE 和/或 SOF_TIMESTAMPING_RAW_HARDWARE
但未请求接收时间戳生成。这可能发生在仅请求发送时间戳时。
接收虚假时间戳通常是无害的。进程可以忽略意外的非零值。但它使行为在其他套接
字上微妙地依赖。此标志隔离套接字以获得更确定的行为。
新应用程序鼓励传递 SOF_TIMESTAMPING_OPT_ID 以区分时间戳并传递
SOF_TIMESTAMPING_OPT_TSONLY 以操作,而不管 sysctl net.core.tstamp_allow_data
的设置。
例外情况是当进程需要额外的 cmsg 数据时,例如 SOL_IP/IP_PKTINFO 以检测出
口网络接口。然后传递选项 SOF_TIMESTAMPING_OPT_CMSG。此选项依赖于访问原
始数据包的内容,因此不能与 SOF_TIMESTAMPING_OPT_TSONLY 组合。
1.3.4. 通过控制消息启用时间戳
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
除了套接字选项外,时间戳生成还可以通过 cmsg 按写入请求,仅适用于
SOF_TIMESTAMPING_TX_*(见第 1.3.1 节)。使用此功能,应用程序可以无需启用和
禁用时间戳即可采样每个 sendmsg() 的时间戳::
struct msghdr *msg;
...
cmsg = CMSG_FIRSTHDR(msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SO_TIMESTAMPING;
cmsg->cmsg_len = CMSG_LEN(sizeof(__u32));
*((__u32 *) CMSG_DATA(cmsg)) = SOF_TIMESTAMPING_TX_SCHED |
SOF_TIMESTAMPING_TX_SOFTWARE |
SOF_TIMESTAMPING_TX_ACK;
err = sendmsg(fd, msg, 0);
通过 cmsg 设置的 SOF_TIMESTAMPING_TX_* 标志将覆盖通过 setsockopt 设置的
SOF_TIMESTAMPING_TX_* 标志。
此外,应用程序仍然需要通过 setsockopt 启用时间戳报告以接收时间戳::
__u32 val = SOF_TIMESTAMPING_SOFTWARE |
SOF_TIMESTAMPING_OPT_ID /* 或任何其他标志 */;
err = setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &val, sizeof(val));
1.4 字节流时间戳
----------------
SO_TIMESTAMPING 接口支持字节流的时间戳。每个请求解释为请求当整个缓冲区内容
通过时间戳点时。也就是说,对于流选项 SOF_TIMESTAMPING_TX_SOFTWARE 将记录
当所有字节都到达设备驱动程序时,无论数据被转换成多少个数据包。
一般来说,字节流没有自然分隔符,因此将时间戳与数据相关联是非平凡的。字节范围
可能跨段,任何段可能合并(可能合并先前分段缓冲区关联的独立 send() 调用)。段
可以重新排序,同一字节范围可以在多个段中并存,对于实现重传的协议。
所有时间戳必须实现相同的语义,否则它们是不可比较的。以不同于简单情况(缓冲区
到 skb 的 1:1 映射)的方式处理“罕见”角落情况是不够的,因为性能调试通常需要
关注这些异常。
在实践中,时间戳可以与字节流段一致地关联,如果时间戳语义和测量时序的选择正确。
此挑战与决定 IP 分片策略没有不同。在那里,定义是仅对第一个分片进行时间戳。对
于字节流我们选择仅在所有字节通过某个点时生成时间戳。SOF_TIMESTAMPING_TX_ACK
定义的实现和推理是容易的。一个需要考虑 SACK 的实现会更复杂,因为可能存在传输
空洞和乱序到达。
在主机上TCP 也可以通过 Nagle、cork、autocork、分段和 GSO 打破简单的 1:1
缓冲区到 skbuff 映射。实现确保在所有情况下都正确,通过跟踪每个 send() 传递
给send() 的最后一个字节,即使它在 skbuff 扩展或合并操作后不再是最后一个字
节。它存储相关的序列号在 skb_shinfo(skb)->tskey。因为一个 skbuff 只有一
个这样的字段,所以只能生成一个时间戳。
在罕见情况下,如果两个请求折叠到同一个 skb则时间戳请求可能会被错过。进程可
以通过始终在请求之间刷新 TCP 栈来检测此情况,例如启用 TCP_NODELAY 和禁用
TCP_CORK和 autocork。在 linux-4.7 之后,更好的预防合并方法是使用 MSG_EOR
标志在sendmsg()时。
这些预防措施确保时间戳仅在所有字节通过时间戳点时生成,假设网络栈本身不会重新
排序段。栈确实试图避免重新排序。唯一的例外是管理员控制:可以构造一个数据包调
度器配置,将来自同一流的不同段延迟不同。这种设置通常不常见。
2 数据接口
==========
时间戳通过 recvmsg() 的辅助数据功能读取。请参阅 `man 3 cmsg` 了解此接口的
详细信息。套接字手册页面 (`man 7 socket`) 描述了如何检索SO_TIMESTAMP 和
SO_TIMESTAMPNS 生成的数据包时间戳。
2.1 SCM_TIMESTAMPING 记录
-------------------------
这些时间戳在 cmsg_level SOL_SOCKET、cmsg_type SCM_TIMESTAMPING 和类型为
对于 SO_TIMESTAMPING_OLD::
struct scm_timestamping {
struct timespec ts[3];
};
对于 SO_TIMESTAMPING_NEW::
struct scm_timestamping64 {
struct __kernel_timespec ts[3];
始终使用 SO_TIMESTAMPING_NEW 时间戳以始终获得 struct scm_timestamping64
格式的时间戳。
SO_TIMESTAMPING_OLD 在 32 位机器上 2038 年后返回错误的时间戳。
该结构可以返回最多三个时间戳。这是一个遗留功能。任何时候至少有一个字
段不为零。大多数时间戳都通过 ts[0] 传递。硬件时间戳通过 ts[2] 传递。
ts[1] 以前用于存储硬件时间戳转换为系统时间。相反,将硬件时钟设备直接
暴露为HW PTP时钟源以允许用户空间进行时间转换并可选地与用户空间
PTP 堆栈如linuxptp同步系统时间。对于 PTP 时钟 API请参阅
Documentation/driver-api/ptp.rst。
注意,如果同时启用了 SO_TIMESTAMP 或 SO_TIMESTAMPNS 与
SO_TIMESTAMPING 使用 SOF_TIMESTAMPING_SOFTWARE在 recvmsg()
调用时会生成一个虚假的软件时间戳,并传递给 ts[0] 当真实软件时间戳缺
失时。这也发生在硬件传输时间戳上。
2.1.1 传输时间戳与 MSG_ERRQUEUE
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
对于传输时间戳传出数据包回环到套接字的错误队列并附加发送时间戳s
进程通过调用带有 MSG_ERRQUEUE 标志的 recvmsg() 接收时间戳,并传递
一个足够大的 msg_control缓冲区以接收相关的元数据结构。recvmsg 调用
返回原始传出数据包,并附加两个辅助消息。
一个 cm_level SOL_IP(V6) 和 cm_type IP(V6)_RECVERR 嵌入一个
struct sock_extended_err这定义了错误类型。对于时间戳ee_errno
字段是 ENOMSG。另一个辅助消息将具有 cm_level SOL_SOCKET 和 cm_type
SCM_TIMESTAMPING。这嵌入了 struct scm_timestamping。
2.1.1.2 时间戳类型
~~~~~~~~~~~~~~~~~~
三个 struct timespec 的语义由 struct sock_extended_err 中的
ee_info 字段定义。它包含一个类型 SCM_TSTAMP_* 来定义实际传递给
scm_timestamping 的时间戳。
SCM_TSTAMP_* 类型与之前讨论的 SOF_TIMESTAMPING_* 控制字段完全
匹配只有一个例外对于遗留原因SCM_TSTAMP_SND 等于零,可以设置为
SOF_TIMESTAMPING_TX_HARDWARE 和 SOF_TIMESTAMPING_TX_SOFTWARE。
它是第一个,如果 ts[2] 不为零,否则是第二个,在这种情况下,时间戳存
储在ts[0] 中。
2.1.1.3 分片
~~~~~~~~~~~~
传出数据报分片很少见,但可能发生,例如通过显式禁用 PMTU 发现。如果
传出数据包被分片,则仅对第一个分片进行时间戳,并返回给发送套接字。
2.1.1.4 数据包负载
~~~~~~~~~~~~~~~~~~
调用应用程序通常不关心接收它传递给堆栈的整个数据包负载:套接字错误队
列机制仅是一种将时间戳附加到其上的方法。在这种情况下,应用程序可以选
择读取较小的数据报,甚至长度为 0。负载相应地被截断。直到进程调用
recvmsg() 到错误队列,然而,整个数据包仍在队列中,占用 SO_RCVBUF 预算。
2.1.1.5 阻塞读取
~~~~~~~~~~~~~~~~
从错误队列读取始终是非阻塞操作。要阻塞等待时间戳,请使用 poll 或
select。poll() 将在 pollfd.revents 中返回 POLLERR如果错误队列
中有数据。没有必要在 pollfd.events中传递此标志。此标志在请求时被忽
略。另请参阅 `man 2 poll`
2.1.2 接收时间戳
^^^^^^^^^^^^^^^^
在接收时没有理由从套接字错误队列读取。SCM_TIMESTAMPING 辅助数据与
数据包数据一起通过正常 recvmsg() 发送。由于这不是套接字错误,它不伴
随消息 SOL_IP(V6)/IP(V6)_RECVERROR。在这种情况下struct
scm_timestamping 中的三个字段含义隐式定义。ts[0] 在设置时包含软件
时间戳ts[1] 再次被弃用ts[2] 在设置时包含硬件时间戳。
3. 硬件时间戳配置ETHTOOL_MSG_TSCONFIG_SET/GET
===============================================
硬件时间戳也必须为每个设备驱动程序初始化,该驱动程序预期执行硬件时间戳。
参数在 include/uapi/linux/net_tstamp.h 中定义为::
struct hwtstamp_config {
int flags; /* 目前没有定义的标志,必须为零 */
int tx_type; /* HWTSTAMP_TX_* */
int rx_filter; /* HWTSTAMP_FILTER_* */
};
期望的行为通过 tsconfig netlink 套接字 ``ETHTOOL_MSG_TSCONFIG_SET``
传递到内核,并通过 ``ETHTOOL_A_TSCONFIG_TX_TYPES``
``ETHTOOL_A_TSCONFIG_RX_FILTERS````ETHTOOL_A_TSCONFIG_HWTSTAMP_FLAGS``
netlink 属性设置 struct hwtstamp_config 相应地。
``ETHTOOL_A_TSCONFIG_HWTSTAMP_PROVIDER`` netlink 嵌套属性用于选择
硬件时间戳的来源。它由设备源的索引和时间戳类型限定符组成。
驱动程序可以自由使用比请求更宽松的配置。预期驱动程序应仅实现可以直接支持的
最通用模式。例如,如果硬件可以支持 HWTSTAMP_FILTER_PTP_V2_EVENT则它
通常应始终升级HWTSTAMP_FILTER_PTP_V2_L2_SYNC依此类推因为
HWTSTAMP_FILTER_PTP_V2_EVENT 更通用(更实用)。
支持硬件时间戳的驱动程序应更新 struct并可能返回更宽松的实际配置。如果
请求的数据包无法进行时间戳,则不应更改任何内容,并返回 ERANGE与 EINVAL
相反,这表明 SIOCSHWTSTAMP 根本不支持)。
只有具有管理权限的进程才能更改配置。用户空间负责确保多个进程不会相互干扰,
并确保设置被重置。
任何进程都可以通过请求 tsconfig netlink 套接字 ``ETHTOOL_MSG_TSCONFIG_GET``
读取实际配置。
遗留配置是使用 ioctl(SIOCSHWTSTAMP) 与指向 struct ifreq 的指针,其
ifr_data指向 struct hwtstamp_config。tx_type 和 rx_filter 是驱动
程序期望执行的提示。如果请求的细粒度过滤对传入数据包不支持,驱动程序可能
会对请求的数据包进行时间戳。ioctl(SIOCGHWTSTAMP) 以与
ioctl(SIOCSHWTSTAMP) 相同的方式使用。然而,并非所有驱动程序都实现了这一点。
::
/* 可能的 hwtstamp_config->tx_type 值 */
enum {
/*
* 不会需要硬件时间戳的传出数据包;
* 如果数据包到达并请求它,则不会进行硬件时间戳
*/
HWTSTAMP_TX_OFF,
/*
* 启用传出数据包的硬件时间戳;
* 数据包的发送者决定哪些数据包需要时间戳,
* 在发送数据包之前设置 SOF_TIMESTAMPING_TX_SOFTWARE
*/
HWTSTAMP_TX_ON,
};
/* 可能的 hwtstamp_config->rx_filter 值 */
enum {
/* 时间戳不传入任何数据包 */
HWTSTAMP_FILTER_NONE,
/* 时间戳任何传入数据包 */
HWTSTAMP_FILTER_ALL,
/* 返回值:时间戳所有请求的数据包加上一些其他数据包 */
HWTSTAMP_FILTER_SOME,
/* PTP v1UDP任何事件数据包 */
HWTSTAMP_FILTER_PTP_V1_L4_EVENT,
/* 有关完整值列表,请检查
* 文件 include/uapi/linux/net_tstamp.h
*/
};
3.1 硬件时间戳实现:设备驱动程序
--------------------------------
支持硬件时间戳的驱动程序必须支持 ndo_hwtstamp_set NDO 或遗留 SIOCSHWTSTAMP
ioctl 并更新提供的 struct hwtstamp_config 与实际值,如 SIOCSHWTSTAMP 部分
所述。它还应支持 ndo_hwtstamp_get 或遗留 SIOCGHWTSTAMP。
接收数据包的时间戳必须存储在 skb 中。要获取 skb 的共享时间戳结构,请调用
skb_hwtstamps()。然后设置结构中的时间戳::
struct skb_shared_hwtstamps {
/* 硬件时间戳转换为自任意时间点的持续时间
* 自定义点
*/
ktime_t hwtstamp;
};
传出数据包的时间戳应按如下方式生成:
- 在 hard_start_xmit() 中,检查 (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)
是否不为零。如果是,则驱动程序期望执行硬件时间戳。
- 如果此 skb 和请求都可能,则声明驱动程序正在执行时间戳,通过设置 skb_shinfo(skb)->tx_flags
中的标志SKBTX_IN_PROGRESS例如::
skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
您可能希望保留与 skb 关联的指针,而不是释放 skb。不支持硬件时间戳的驱
动程序不会这样做。驱动程序绝不能触及 sk_buff::tstamp它用于存储网络
子系统生成的软件时间戳。
- 驱动程序应在尽可能接近将 sk_buff 传递给硬件时调用 skb_tx_timestamp()。
skb_tx_timestamp()提供软件时间戳(如果请求),并且硬件时间戳不可用
SKBTX_IN_PROGRESS 未设置)。
- 一旦驱动程序发送数据包并/或获取硬件时间戳,它就会通过 skb_tstamp_tx()
传递时间戳,原始 skb原始硬件时间戳。skb_tstamp_tx() 克隆原始 skb 并
添加时间戳,因此原始 skb 现在必须释放。如果获取硬件时间戳失败,则驱动程序
不应回退到软件时间戳。理由是,这会在处理管道中的稍后时间发生,而不是其他软
件时间戳,因此可能导致时间戳之间的差异。
3.2 堆叠 PTP 硬件时钟的特殊考虑
-------------------------------
在数据包的路径中可能存在多个 PHCPTP 硬件时钟)。内核没有明确的机制允许用
户选择用于时间戳以太网帧的 PHC。相反假设最外层的 PHC 始终是最优的,并且
内核驱动程序协作以实现这一目标。目前有 3 种堆叠 PHC 的情况,如下所示:
3.2.1 DSA分布式交换架构交换机
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
这些是具有一个端口连接到(完全不知情的)主机以太网接口的以太网交换机,并且
执行端口多路复用或可选转发加速功能。每个 DSA 交换机端口在用户看来都是独立的
(虚拟)网络接口,其网络 I/O 在底层通过主机接口(在 TX 上重定向到主机端口,
在 RX 上拦截帧)执行。
当 DSA 交换机连接到主机端口时PTP 同步必须受到限制,因为交换机的可变排队
延迟引入了主机端口与其 PTP 伙伴之间的路径延迟抖动。因此,一些 DSA 交换机
包含自己的时间戳时钟,并具有在自身 MAC上执行网络时间戳的能力因此路径延迟
仅测量线缆和 PHY 传播延迟。支持 Linux 的 DSA 交换机暴露了与任何其他网络
接口相同的 ABI除了 DSA 接口在网络 I/O 方面实际上是虚拟的,它们确实有自
己的PHC。典型地但不是强制性地所有DSA 交换机接口共享相同的 PHC。
通过设计DSA 交换机对连接到其主机端口的 PTP 时间戳不需要任何特殊的驱动程
序处理。然而,当主机端口也支持 PTP 时间戳时DSA 将负责拦截
``.ndo_eth_ioctl`` 调用,并阻止尝试在主机端口上启用硬件时间戳。这是因为
SO_TIMESTAMPING API 不允许为同一数据包传递多个硬件时间戳,因此除了 DSA
交换机端口之外的任何人都不应阻止这样做。
在通用层DSA 提供了以下基础设施用于 PTP 时间戳:
- ``.port_txtstamp()``:在用户空间从用户空间请求带有硬件 TX 时间戳请求
的数据包之前调用的钩子。这是必需的,因为硬件时间戳在实际 MAC 传输后才可
用,因此驱动程序必须准备将时间戳与原始数据包相关联,以便它可以重新入队数
据包到套接字的错误队列。为了保存可能在时间戳可用时需要的数据包,驱动程序
可以调用 ``skb_clone_sk``,在 skb->cb 中保存克隆指针,并入队一个 tx
skb 队列。通常交换机会有一个PTP TX 时间戳寄存器(或有时是一个 FIFO
其中时间戳可用。在 FIFO 的情况下硬件可能会存储PTP 序列 ID/消息类型/
域号和实际时间戳的键值对。为了在等待时间戳的数据包队列和实际时间戳之间正
确关联,驱动程序可以使用 BPF 分类器(``ptp_classify_raw``) 来识别 PTP
传输类型,并使用 ``ptp_parse_header`` 解释 PTP 头字段。可能存在一个 IRQ
当此时间戳可用时触发,或者驱动程序可能需要轮询,在调用 ``dev_queue_xmit()``
到主机接口之后。单步 TX 时间戳不需要数据包克隆,因为 PTP 协议不需要后续消
因为TX 时间戳已嵌入到数据包中),因此用户空间不期望数据包带有 TX 时间戳
被重新入队到其套接字的错误队列。
- ``.port_rxtstamp()``:在 RX 上DSA 运行 BPF 分类器以识别 PTP 事件消息
(任何其他数据包,包括 PTP 通用消息,不进行时间戳)。驱动程序提供原始(也是唯一)
时间戳数据包,以便它可以标记它,如果它是立即可用的,或者延迟。在接收时,时间
戳可能要么在频带内通过DSA 头中的元数据,或以其他方式附加到数据包),要么在频
带外(通过另一个 RX 时间戳FIFO。在 RX 上延迟通常是必要的,当检索时间戳需要
可睡眠上下文时。在这种情况下DSA驱动程序有责任调用 ``netif_rx()`` 在新鲜时
间戳的 skb 上。
3.2.2 以太网 PHYs
^^^^^^^^^^^^^^^^^
这些是通常在网络栈中履行第 1 层角色的设备,因此它们在 DSA 交换机中没有网络接
口的表示。然而PHY可能能够检测和时间戳 PTP 数据包,出于性能原因:在尽可能接
近导线的地方获取的时间戳具有更稳定的同步性和更精确的精度。
支持 PTP 时间戳的 PHY 驱动程序必须创建 ``struct mii_timestamper`` 并添加
指向它的指针在 ``phydev->mii_ts`` 中。 ``phydev->mii_ts`` 的存在将由网络
堆栈检查。
由于 PHY 没有网络接口表示PHY 的时间戳和 ethtool ioctl 操作需要通过其各自
的 MAC驱动程序进行中介。因此与 DSA 交换机不同,需要对每个单独的 MAC 驱动
程序进行 PHY时间戳支持的修改。这包括
-``.ndo_eth_ioctl`` 中检查,是否 ``phy_has_hwtstamp(netdev->phydev)``
为真或假。如果是,则 MAC 驱动程序不应处理此请求,而应将其传递给 PHY 使用
``phy_mii_ioctl()``
- 在 RX 上,特殊干预可能或可能不需要,具体取决于将 skb 传递到网络堆栈的函数。
在 plain ``netif_rx()`` 和类似情况下MAC 驱动程序必须检查是否
``skb_defer_rx_timestamp(skb)`` 是必要的,如果是,则不调用 ``netif_rx()``
如果 ``CONFIG_NETWORK_PHY_TIMESTAMPING`` 启用,并且
``skb->dev->phydev->mii_ts`` 存在,它的 ``.rxtstamp()`` 钩子现在将被调
用,以使用与 DSA 类似的逻辑确定 RX 时间戳延迟是否必要。同样像 DSA它成为
PHY 驱动程序的责任,在时间戳可用时发送数据包到堆栈。
对于其他 skb 接收函数,例如 ``napi_gro_receive````netif_receive_skb``
堆栈会自动检查是否 ``skb_defer_rx_timestamp()`` 是必要的,因此此检查不
需要在驱动程序内部。
- 在 TX 上,同样,特殊干预可能或可能不需要。调用 ``mii_ts->txtstamp()``
子的函数名为``skb_clone_tx_timestamp()``。此函数可以直接调用(在这种情
况下,确实需要显式 MAC 驱动程序支持),但函数也 piggybacks 从
``skb_tx_timestamp()`` 调用,许多 MAC 驱动程序已经为软件时间戳目的执行。
因此,如果 MAC 支持软件时间戳,则它不需要在此阶段执行任何其他操作。
3.2.3 MII 总线嗅探设备
^^^^^^^^^^^^^^^^^^^^^^
这些执行与时间戳以太网 PHY 相同的角色,除了它们是离散设备,因此可以与任何 PHY
组合,即使它不支持时间戳。在 Linux 中,它们是可发现的,可以通过 Device Tree
附加到 ``struct phy_device``,对于其余部分,它们使用与那些相同的 mii_ts 基
础设施。请参阅 Documentation/devicetree/bindings/ptp/timestamper.txt 了
解更多详细信息。
3.2.4 MAC 驱动程序的其他注意事项
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
堆叠 PHC 可能会暴露 MAC 驱动程序的错误,这些错误在未堆叠 PHC 时无法触发。一个
例子涉及此行代码,已经在前面的部分中介绍过::
skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
任何 TX 时间戳逻辑,无论是普通的 MAC 驱动程序、DSA 交换机驱动程序、PHY 驱动程
序还是 MII 总线嗅探设备驱动程序,都应该设置此标志。但一个未意识到 PHC 堆叠的
MAC 驱动程序可能会被其他不是它自己的实体设置此标志,并传递一个重复的时间戳。例
如,典型的 TX 时间戳逻辑可能是将传输部分分为 2 个部分:
1. "TX":检查是否通过 ``.ndo_eth_ioctl``"``priv->hwtstamp_tx_enabled
== true``")和当前 skb 是否需要 TX 时间戳("``skb_shinfo(skb)->tx_flags
& SKBTX_HW_TSTAMP``")。如果为真,则设置 "``skb_shinfo(skb)->tx_flags
|= SKBTX_IN_PROGRESS``" 标志。注意:如上所述,在堆叠 PHC 系统中,此条件
不应触发,因为此 MAC 肯定不是最外层的 PHC。但这是典型的错误所在。传输继续
使用此数据包。
2. "TX 确认":传输完成。驱动程序检查是否需要收集任何 TX 时间戳。这里通常是典
型的错误所在:驱动程序采取捷径,只检查 "``skb_shinfo(skb)->tx_flags &
SKBTX_IN_PROGRESS``" 是否设置。在堆叠 PHC 系统中,这是错误的,因为此 MAC
驱动程序不是唯一在 TX 数据路径中启用 SKBTX_IN_PROGRESS 的实体。
此问题的正确解决方案是 MAC 驱动程序在其 "TX 确认" 部分中有一个复合检查,不仅
针对 "``skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS``",还针对
"``priv->hwtstamp_tx_enabled == true``"。因为系统确保 PTP 时间戳仅对最
外层 PHC 启用,此增强检查将避免向用户空间传递重复的 TX 时间戳。

View File

@@ -13,6 +13,7 @@
本文档包含了在内核中使用Rust支持时需要了解的有用信息。
.. _rust_code_documentation_zh_cn:
代码文档
--------

View File

@@ -10,7 +10,35 @@
Rust
====
与内核中的Rust有关的文档。若要开始在内核中使用Rust请阅读quick-start.rst指南。
与内核中的Rust有关的文档。若要开始在内核中使用Rust请阅读 quick-start.rst 指南。
Rust 实验
---------
Rust 支持在 v6.1 版本中合并到主线,以帮助确定 Rust 作为一种语言是否适合内核,
即是否值得进行权衡。
目前Rust 支持主要面向对 Rust 支持感兴趣的内核开发人员和维护者,
以便他们可以开始处理抽象和驱动程序,并帮助开发基础设施和工具。
如果您是终端用户,请注意,目前没有适合或旨在生产使用的内置驱动程序或模块,
并且 Rust 支持仍处于开发/实验阶段,尤其是对于特定内核配置。
代码文档
--------
给定一个内核配置,内核可能会生成 Rust 代码文档,即由 ``rustdoc`` 工具呈现的 HTML。
.. only:: rustdoc and html
该内核文档使用 `Rust 代码文档 <rustdoc/kernel/index.html>`_ 构建。
.. only:: not rustdoc and html
该内核文档不使用 Rust 代码文档构建。
预生成版本提供在https://rust.docs.kernel.org。
请参阅 :ref:`代码文档 <rust_code_documentation_zh_cn>` 部分以获取更多详细信息。
.. toctree::
:maxdepth: 1
@@ -19,6 +47,9 @@ Rust
general-information
coding-guidelines
arch-support
testing
你还可以在 :doc:`../../../process/kernel-docs` 中找到 Rust 的学习材料。
.. only:: subproject and html

View File

@@ -0,0 +1,215 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/rust/testing.rst
:翻译:
郭杰 Ben Guo <benx.guo@gmail.com>
测试
====
本文介绍了如何在内核中测试 Rust 代码。
有三种测试类型:
- KUnit 测试
- ``#[test]`` 测试
- Kselftests
KUnit 测试
----------
这些测试来自 Rust 文档中的示例。它们会被转换为 KUnit 测试。
使用
****
这些测试可以通过 KUnit 运行。例如,在命令行中使用 ``kunit_tool`` ``kunit.py`` ::
./tools/testing/kunit/kunit.py run --make_options LLVM=1 --arch x86_64 --kconfig_add CONFIG_RUST=y
或者KUnit 也可以在内核启动时以内置方式运行。获取更多 KUnit 信息,请参阅
Documentation/dev-tools/kunit/index.rst。
关于内核内置与命令行测试的详细信息,请参阅 Documentation/dev-tools/kunit/architecture.rst。
要使用这些 KUnit 文档测试,需要在内核配置中启用以下选项::
CONFIG_KUNIT
Kernel hacking -> Kernel Testing and Coverage -> KUnit - Enable support for unit tests
CONFIG_RUST_KERNEL_DOCTESTS
Kernel hacking -> Rust hacking -> Doctests for the `kernel` crate
KUnit 测试即文档测试
********************
文档测试( *doctests* )一般用于展示函数、结构体或模块等的使用方法。
它们非常方便,因为它们就写在文档旁边。例如:
.. code-block:: rust
/// 求和两个数字。
///
/// ```
/// assert_eq!(mymod::f(10, 20), 30);
/// ```
pub fn f(a: i32, b: i32) -> i32 {
a + b
}
在用户空间中,这些测试由 ``rustdoc`` 负责收集并运行。单独使用这个工具已经很有价值,
因为它可以验证示例能否成功编译(确保和代码保持同步),
同时还可以运行那些不依赖内核 API 的示例。
然而,在内核中,这些测试会转换成 KUnit 测试套件。
这意味着文档测试会被编译成 Rust 内核对象,从而可以在构建的内核环境中运行。
通过与 KUnit 集成Rust 的文档测试可以复用内核现有的测试设施。
例如,内核日志会显示::
KTAP version 1
1..1
KTAP version 1
# Subtest: rust_doctests_kernel
1..59
# rust_doctest_kernel_build_assert_rs_0.location: rust/kernel/build_assert.rs:13
ok 1 rust_doctest_kernel_build_assert_rs_0
# rust_doctest_kernel_build_assert_rs_1.location: rust/kernel/build_assert.rs:56
ok 2 rust_doctest_kernel_build_assert_rs_1
# rust_doctest_kernel_init_rs_0.location: rust/kernel/init.rs:122
ok 3 rust_doctest_kernel_init_rs_0
...
# rust_doctest_kernel_types_rs_2.location: rust/kernel/types.rs:150
ok 59 rust_doctest_kernel_types_rs_2
# rust_doctests_kernel: pass:59 fail:0 skip:0 total:59
# Totals: pass:59 fail:0 skip:0 total:59
ok 1 rust_doctests_kernel
文档测试中,也可以正常使用 `? <https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator>`_ 运算符,例如:
.. code-block:: rust
/// ```
/// # use kernel::{spawn_work_item, workqueue};
/// spawn_work_item!(workqueue::system(), || pr_info!("x\n"))?;
/// # Ok::<(), Error>(())
/// ```
这些测试和普通代码一样,也可以在 ``CLIPPY=1`` 条件下通过 Clippy 进行编译,
因此可以从额外的 lint 检查中获益。
为了便于开发者定位文档测试出错的具体行号,日志会输出一条 KTAP 诊断信息。
其中标明了原始测试的文件和行号(不是 ``rustdoc`` 生成的临时 Rust 文件位置)::
# rust_doctest_kernel_types_rs_2.location: rust/kernel/types.rs:150
Rust 测试中常用的断言宏是来自 Rust 标准库( ``core`` )中的 ``assert!````assert_eq!`` 宏。
内核提供了一个定制版本,这些宏的调用会被转发到 KUnit。
和 KUnit 测试不同的是,这些宏不需要传递上下文参数( ``struct kunit *`` )。
这使得它们更易于使用,同时文档的读者无需关心底层用的是什么测试框架。
此外,这种方式未来也许可以让我们更容易测试第三方代码。
当前有一个限制KUnit 不支持在其他任务中执行断言。
因此,如果断言真的失败了,我们只是简单地把错误打印到内核日志里。
另外,文档测试不适用于非公开的函数。
作为文档中的测试示例,应当像 “实际代码” 一样编写。
例如:不要使用 ``unwrap()````expect()``,请使用 `? <https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator>`_ 运算符。
更多背景信息,请参阅:
https://rust.docs.kernel.org/kernel/error/type.Result.html#error-codes-in-c-and-rust
``#[test]`` 测试
----------------
此外,还有 ``#[test]`` 测试。与文档测试类似,这些测试与用户空间中的测试方式也非常相近,并且同样会映射到 KUnit。
这些测试通过 ``kunit_tests`` 过程宏引入,该宏将测试套件的名称作为参数。
例如,假设想要测试前面文档测试示例中的函数 ``f``,我们可以在定义该函数的同一文件中编写:
.. code-block:: rust
#[kunit_tests(rust_kernel_mymod)]
mod tests {
use super::*;
#[test]
fn test_f() {
assert_eq!(f(10, 20), 30);
}
}
如果我们执行这段代码,内核日志会显示::
KTAP version 1
# Subtest: rust_kernel_mymod
# speed: normal
1..1
# test_f.speed: normal
ok 1 test_f
ok 1 rust_kernel_mymod
与文档测试类似, ``assert!````assert_eq!`` 宏被映射回 KUnit 并且不会发生 panic。
同样,支持 `? <https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator>`_ 运算符,
测试函数可以什么都不返回(单元类型 ``()``)或 ``Result`` (任何 ``Result<T, E>``)。例如:
.. code-block:: rust
#[kunit_tests(rust_kernel_mymod)]
mod tests {
use super::*;
#[test]
fn test_g() -> Result {
let x = g()?;
assert_eq!(x, 30);
Ok(())
}
}
如果我们运行测试并且调用 ``g`` 失败,那么内核日志会显示::
KTAP version 1
# Subtest: rust_kernel_mymod
# speed: normal
1..1
# test_g: ASSERTION FAILED at rust/kernel/lib.rs:335
Expected is_test_result_ok(test_g()) to be true, but is false
# test_g.speed: normal
not ok 1 test_g
not ok 1 rust_kernel_mymod
如果 ``#[test]`` 测试可以对用户起到示例作用,那就应该改用文档测试。
即使是 API 的边界情况,例如错误或边界问题,放在示例中展示也同样有价值。
``rusttest`` 宿主机测试
-----------------------
这类测试运行在用户空间,可以通过 ``rusttest`` 目标在构建内核的宿主机中编译并运行::
make LLVM=1 rusttest
当前操作需要内核 ``.config``
目前,它们主要用于测试 ``macros`` crate 的示例。
Kselftests
----------
Kselftests 可以在 ``tools/testing/selftests/rust`` 文件夹中找到。
测试所需的内核配置选项列在 ``tools/testing/selftests/rust/config`` 文件中,
可以借助 ``merge_config.sh`` 脚本合并到现有配置中::
./scripts/kconfig/merge_config.sh .config tools/testing/selftests/rust/config
Kselftests 会在内核源码树中构建,以便在运行相同版本内核的系统上执行测试。
一旦安装并启动了与源码树匹配的内核,测试即可通过以下命令编译并执行::
make TARGETS="rust" kselftest
请参阅 Documentation/dev-tools/kselftest.rst 文档以获取更多信息。

View File

@@ -0,0 +1,92 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/scsi/index.rst
:翻译:
郝栋栋 doubled <doubled@leap-io-kernel.com>
:校译:
==========
SCSI子系统
==========
.. toctree::
:maxdepth: 1
简介
====
.. toctree::
:maxdepth: 1
scsi
SCSI驱动接口
============
.. toctree::
:maxdepth: 1
scsi_mid_low_api
scsi_eh
SCSI驱动参数
============
.. toctree::
:maxdepth: 1
scsi-parameters
link_power_management_policy
SCSI主机适配器驱动
==================
.. toctree::
:maxdepth: 1
sd-parameters
Todolist:
* 53c700
* aacraid
* advansys
* aha152x
* aic79xx
* aic7xxx
* arcmsr_spec
* bfa
* bnx2fc
* BusLogic
* cxgb3i
* dc395x
* dpti
* FlashPoint
* g_NCR5380
* hpsa
* hptiop
* libsas
* lpfc
* megaraid
* ncr53c8xx
* NinjaSCSI
* ppa
* qlogicfas
* scsi-changer
* scsi_fc_transport
* scsi-generic
* smartpqi
* st
* sym53c500_cs
* sym53c8xx_2
* tcm_qla2xxx
* ufs
* wd719x
* scsi_transport_srp/figures

View File

@@ -0,0 +1,32 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/scsi/link_power_management_policy.rst
:翻译:
郝栋栋 doubled <doubled@leap-io-kernel.com>
:校译:
================
链路电源管理策略
================
该参数允许用户设置链路(接口)的电源管理模式。
共计三类可选项:
===================== =====================================================
选项 作用
===================== =====================================================
min_power 指示控制器在可能的情况下尽量使链路处于最低功耗。
这可能会牺牲一定的性能,因为从低功耗状态恢复时会增加延迟。
max_performance 通常,这意味着不进行电源管理。指示
控制器优先考虑性能而非电源管理。
medium_power 指示控制器在可能的情况下进入较低功耗状态,
而非最低功耗状态从而改善min_power模式下的延迟。
===================== =====================================================

View File

@@ -0,0 +1,118 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/scsi/scsi-parameters.rst
:翻译:
郝栋栋 doubled <doubled@leap-io-kernel.com>
:校译:
============
SCSI内核参数
============
请查阅Documentation/admin-guide/kernel-parameters.rst以获取
指定模块参数相关的通用信息。
当前文档可能不完全是最新和全面的。命令 ``modinfo -p ${modulename}``
显示了可加载模块的参数列表。可加载模块被加载到内核中后,也会在
/sys/module/${modulename}/parameters/ 目录下显示其参数。其
中某些参数可以通过命令
``echo -n ${value} > /sys/module/${modulename}/parameters/${parm}``
在运行时修改。
::
advansys= [HW,SCSI]
请查阅 drivers/scsi/advansys.c 文件头部。
aha152x= [HW,SCSI]
请查阅 Documentation/translations/zh_CN/scsi/aha152x.rst。
aha1542= [HW,SCSI]
格式:<portbase>[,<buson>,<busoff>[,<dmaspeed>]]
aic7xxx= [HW,SCSI]
请查阅 Documentation/translations/zh_CN/scsi/aic7xxx.rst。
aic79xx= [HW,SCSI]
请查阅 Documentation/translations/zh_CN/scsi/aic79xx.rst。
atascsi= [HW,SCSI]
请查阅 drivers/scsi/atari_scsi.c。
BusLogic= [HW,SCSI]
请查阅 drivers/scsi/BusLogic.c 文件中
BusLogic_ParseDriverOptions()函数前的注释。
gvp11= [HW,SCSI]
ips= [HW,SCSI] Adaptec / IBM ServeRAID 控制器
请查阅 drivers/scsi/ips.c 文件头部。
mac5380= [HW,SCSI]
请查阅 drivers/scsi/mac_scsi.c。
scsi_mod.max_luns=
[SCSI] 最大可探测LUN数。
取值范围为 1 到 2^32-1。
scsi_mod.max_report_luns=
[SCSI] 接收到的最大LUN数。
取值范围为 1 到 16384。
NCR_D700= [HW,SCSI]
请查阅 drivers/scsi/NCR_D700.c 文件头部。
ncr5380= [HW,SCSI]
请查阅 Documentation/translations/zh_CN/scsi/g_NCR5380.rst。
ncr53c400= [HW,SCSI]
请查阅 Documentation/translations/zh_CN/scsi/g_NCR5380.rst。
ncr53c400a= [HW,SCSI]
请查阅 Documentation/translations/zh_CN/scsi/g_NCR5380.rst。
ncr53c8xx= [HW,SCSI]
osst= [HW,SCSI] SCSI磁带驱动
格式:<buffer_size>,<write_threshold>
另请查阅 Documentation/translations/zh_CN/scsi/st.rst。
scsi_debug_*= [SCSI]
请查阅 drivers/scsi/scsi_debug.c。
scsi_mod.default_dev_flags=
[SCSI] SCSI默认设备标志
格式:<integer>
scsi_mod.dev_flags=
[SCSI] 厂商和型号的黑/白名单条目
格式:<vendor>:<model>:<flags>
flags 为整数值)
scsi_mod.scsi_logging_level=
[SCSI] 日志级别的位掩码
位的定义请查阅 drivers/scsi/scsi_logging.h。
此参数也可以通过sysctl对dev.scsi.logging_level
进行设置(/proc/sys/dev/scsi/logging_level
此外S390-tools软件包提供了一个便捷的
scsi_logging_level 脚本,可以从以下地址下载:
https://github.com/ibm-s390-linux/s390-tools/blob/master/scripts/scsi_logging_level
scsi_mod.scan= [SCSI] sync默认在发现SCSI总线过程中
同步扫描。async在内核线程中异步扫描允许系统继续
启动流程。none忽略扫描预期由用户空间完成扫描。
sim710= [SCSI,HW]
请查阅 drivers/scsi/sim710.c 文件头部。
st= [HW,SCSI] SCSI磁带参数缓冲区大小等
请查阅 Documentation/translations/zh_CN/scsi/st.rst。
wd33c93= [HW,SCSI]
请查阅 drivers/scsi/wd33c93.c 文件头部。

View File

@@ -0,0 +1,48 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/scsi/scsi.rst
:翻译:
郝栋栋 doubled <doubled@leap-io-kernel.com>
:校译:
==============
SCSI子系统文档
==============
Linux文档项目LDP维护了一份描述Linux内核lk 2.4中SCSI
子系统的文档。请参考:
https://www.tldp.org/HOWTO/SCSI-2.4-HOWTO 。LDP提供单页和
多页的HTML版本以及PostScript与PDF格式的文档。
在SCSI子系统中使用模块的注意事项
================================
Linux内核中的SCSI支持可以根据终端用户的需求以不同的方式模块
化。为了理解你的选择,我们首先需要定义一些术语。
scsi-core也被称为“中间层”包含SCSI支持的核心。没有他你将
无法使用任何其他SCSI驱动程序。SCSI核心支持可以是一个模块
scsi_mod.o也可以编译进内核。如果SCSI核心是一个模块那么
他必须是第一个被加载的SCSI模块如果你将卸载该模块那么他必
须是最后一个被卸载的模块。实际上modprobe和rmmod命令将确保
SCSI子系统中模块加载与卸载的正确顺序。
一旦SCSI核心存在于内核中无论是编译进内核还是作为模块加载
独立的上层驱动和底层驱动可以按照任意顺序加载。磁盘驱动程序
sd_mod.o、光盘驱动程序sr_mod.o、磁带驱动程序 [1]_
st.o以及SCSI通用驱动程序sg.o代表了上层驱动用于控制
相应的各种设备。例如,你可以加载磁带驱动程序来使用磁带驱动器,
然后在不需要该驱动程序时卸载他(并释放相关内存)。
底层驱动程序用于支持您所运行硬件平台支持的不同主机卡。这些不同
的主机卡通常被称为主机总线适配器HBAs。例如aic7xxx.o驱动
程序被用于控制Adaptec所属的所有最新的SCSI控制器。几乎所有的底
层驱动都可以被编译为模块或直接编译进内核。
.. [1] 磁带驱动程序有一个变种用于控制OnStream磁带设备。其模块
名称为osst.o 。

View File

@@ -0,0 +1,482 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/scsi/scsi_eh.rst
:翻译:
郝栋栋 doubled <doubled@leap-io-kernel.com>
:校译:
===================
SCSI 中间层错误处理
===================
本文档描述了SCSI中间层mid layer的错误处理基础架构。
关于SCSI中间层的更多信息请参阅
Documentation/scsi/scsi_mid_low_api.rst。
.. 目录
[1] SCSI 命令如何通过中间层传递并进入错误处理EH
[1-1] scsi_cmndSCSI命令结构体
[1-2] scmdSCSI 命令)是如何完成的?
[1-2-1] 通过scsi_done完成scmd
[1-2-2] 通过超时机制完成scmd
[1-3] 错误处理模块如何接管流程
[2] SCSI错误处理机制工作原理
[2-1] 基于细粒度回调的错误处理
[2-1-1] 概览
[2-1-2] scmd在错误处理流程中的传递路径
[2-1-3] 控制流分析
[2-2] 通过transportt->eh_strategy_handler()实现的错误处理
[2-2-1] transportt->eh_strategy_handler()调用前的中间层状态
[2-2-2] transportt->eh_strategy_handler()调用后的中间层状态
[2-2-3] 注意事项
1. SCSI命令在中间层及错误处理中的传递流程
=========================================
1.1 scsi_cmnd结构体
-------------------
每个SCSI命令都由struct scsi_cmnd简称scmd结构体
表示。scmd包含两个list_head类型的链表节点scmd->list
与scmd->eh_entry。其中scmd->list是用于空闲链表或设备
专属的scmd分配链表与错误处理讨论关联不大。而
scmd->eh_entry则是专用于命令完成和错误处理链表除非
特别说明本文讨论中所有scmd的链表操作均通过
scmd->eh_entry实现。
1.2 scmd是如何完成的
----------------------
底层设备驱动LLDD在获取SCSI命令scmd存在两种
完成路径底层驱动可通过调用hostt->queuecommand()时从
中间层传递的scsi_done回调函数主动完成命令或者当命令未
及时完成时由块层block layer触发超时处理机制。
1.2.1 通过scsi_done回调完成SCSI命令
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
对于所有非错误处理EH命令scsi_done()是其完成回调
函数。它只调用blk_mq_complete_request()来删除块层的
定时器并触发块设备软中断BLOCK_SOFTIRQ
BLOCK_SOFTIRQ会间接调用scsi_complete(),进而调用
scsi_decide_disposition()来决定如何处理该命令。
scsi_decide_disposition()会查看scmd->result值和感
应码数据来决定如何处理命令。
- SUCCESS
调用scsi_finish_command()来处理该命令。该函数会
执行一些维护操作然后调用scsi_io_completion()来
完成I/O操作。scsi_io_completion()会通过调用
blk_end_request及其相关函数来通知块层该请求已完成
如果发生错误,还会判断如何处理剩余的数据。
- NEEDS_RETRY
- ADD_TO_MLQUEUE
scmd被重新加入到块设备队列中。
- otherwise
调用scsi_eh_scmd_add(scmd)来处理该命令。
关于此函数的详细信息,请参见 [1-3]。
1.2.2 scmd超时完成机制
^^^^^^^^^^^^^^^^^^^^^^
SCSI命令超时处理机制由scsi_timeout()函数实现。
当发生超时事件时,该函数
1. 首先调用可选的hostt->eh_timed_out()回调函数。
返回值可能是以下3种情况之一
- ``SCSI_EH_RESET_TIMER``
表示需要延长命令执行时间并重启计时器。
- ``SCSI_EH_NOT_HANDLED``
表示eh_timed_out()未处理该命令。
此时将执行第2步的处理流程。
- ``SCSI_EH_DONE``
表示eh_timed_out()已完成该命令。
2. 若未通过回调函数解决,系统将调用
scsi_abort_command()发起异步中止操作,该操作最多
可执行scmd->allowed + 1次。但存在三种例外情况会跳
过异步中止而直接进入第3步处理当检测到
SCSI_EH_ABORT_SCHEDULED标志位已置位表明该命令先
前已被中止过一次且当前重试仍失败)、当重试次数已达上
限、或当错误处理时限已到期时。在这些情况下,系统将跳
过异步中止流程而直接执行第3步处理方案。
3. 最终未解决的命令会通过scsi_eh_scmd_add(scmd)移交给
错误处理子系统,具体流程详见[1-4]章节说明。
1.3 异步命令中止机制
--------------------
当命令超时触发后系统会通过scsi_abort_command()调度异
步中止操作。若中止操作执行成功,则根据重试次数决定后续处
理:若未达最大重试限制,命令将重新下发执行;若重试次数已
耗尽则命令最终以DID_TIME_OUT状态终止。当中止操作失败
系统会调用scsi_eh_scmd_add()将该命令移交错误处理子
系统,具体处理流程详见[1-4]。
1.4 错误处理(EH)接管机制
------------------------
SCSI命令通过scsi_eh_scmd_add()函数进入错误处理流程,该函
数执行以下操作:
1. 将scmd->eh_entry链接到shost->eh_cmd_q
2. 在shost->shost_state中设置SHOST_RECOVERY状态位
3. 递增shost->host_failed失败计数器
4. 当检测到shost->host_busy == shost->host_failed
即所有进行中命令均已失败立即唤醒SCSI错误处理
线程。
如上所述当任一scmd被加入到shost->eh_cmd_q队列时系统
会立即置位shost_state中的SHOST_RECOVERY状态标志位该操
作将阻止块层向对应主机控制器下发任何新的SCSI命令。在此状
态下主机控制器上所有正在处理的scmd最终会进入以下三种状
态之一正常完成、失败后被移入到eh_cmd_q队列、或因超时被
添加到shost->eh_cmd_q队列。
如果所有的SCSI命令都已经完成或失败系统中正在执行的命令
数量与失败命令数量相等(
即shost->host_busy == shost->host_failed此时将唤
醒SCSI错误处理线程。SCSI错误处理线程一旦被唤醒就可以确
保所有未完成命令均已标记为失败状态,并且已经被链接到
shost->eh_cmd_q队列中。
需要特别说明的是,这并不意味着底层处理流程完全静止。当底层
驱动以错误状态完成某个scmd时底层驱动及其下层组件会立刻遗
忘该命令的所有关联状态。但对于超时命令,除非
hostt->eh_timed_out()回调函数已经明确通知底层驱动丢弃该
命令(当前所有底层驱动均未实现此功能),否则从底层驱动视角
看该命令仍处于活跃状态,理论上仍可能在某时刻完成。当然,由
于超时计时器早已触发,所有此类延迟完成都将被系统直接忽略。
我们将在后续章节详细讨论关于SCSI错误处理如何执行中止操作
即强制底层驱动丢弃已超时SCSI命令
2. SCSI错误处理机制详解
=======================
SCSI底层驱动可以通过以下两种方式之一来实现SCSI错误处理。
- 细粒度的错误处理回调机制
底层驱动可选择实现细粒度的错误处理回调函数由SCSI中间层
主导错误恢复流程并自动调用对应的回调函数。此实现模式的详
细设计规范在[2-1]节中展开讨论。
- eh_strategy_handler()回调函数
该回调函数作为统一的错误处理入口,需要完整实现所有的恢复
操作。具体而言它必须涵盖SCSI中间层在常规恢复过程中执行
的全部处理流程,相关实现将在[2-2]节中详细描述。
当错误恢复流程完成后SCSI错误处理系统通过调用
scsi_restart_operations()函数恢复正常运行,该函数按顺序执行
以下操作:
1. 验证是否需要执行驱动器安全门锁定机制
2. 清除shost_state中的SHOST_RECOVERY状态标志位
3. 唤醒所有在shost->host_wait上等待的任务。如果有人调用了
scsi_block_when_processing_errors()则会发生这种情况。
(疑问:由于错误处理期间块层队列已被阻塞,为何仍需显式
唤醒?)
4. 强制激活该主机控制器下所有设备的I/O队列
2.1 基于细粒度回调的错误处理机制
--------------------------------
2.1.1 概述
^^^^^^^^^^^
如果不存在eh_strategy_handler()SCSI中间层将负责驱动的
错误处理。错误处理EH的目标有两个一是让底层驱动程序、
主机和设备不再维护已超时的SCSI命令scmd二是使他们准备
好接收新命令。当一个SCSI命令scmd被底层遗忘且底层已准备
好再次处理或拒绝该命令时即可认为该scmd已恢复。
为实现这些目标错误处理EH会逐步执行严重性递增的恢复
操作。部分操作通过下发SCSI命令完成而其他操作则通过调用
以下细粒度的错误处理回调函数实现。这些回调函数可以省略,
若被省略则默认始终视为执行失败。
::
int (* eh_abort_handler)(struct scsi_cmnd *);
int (* eh_device_reset_handler)(struct scsi_cmnd *);
int (* eh_bus_reset_handler)(struct scsi_cmnd *);
int (* eh_host_reset_handler)(struct scsi_cmnd *);
只有在低级别的错误恢复操作无法恢复部分失败的SCSI命令
scmd才会采取更高级别的恢复操作。如果最高级别的错误
处理失败就意味着整个错误恢复EH过程失败所有未能恢复
的设备被强制下线。
在恢复过程中,需遵循以下规则:
- 错误恢复操作针对待处理列表eh_work_q中的失败的scmds执
行。如果某个恢复操作成功恢复了一个scmd那么该scmd会
从eh_work_q链表中移除。
需要注意的是对某个scmd执行的单个恢复操作可能会恢复
多个scmd。例如对某个设备执行复位操作可能会恢复该设
备上所有失败的scmd。
- 仅当低级别的恢复操作完成且eh_work_q仍然非空时才会
触发更高级别的操作
- SCSI错误恢复机制会重用失败的scmd来发送恢复命令。对于
超时的scmdSCSI错误处理机制会确保底层驱动在重用scmd
前已不再维护该命令。
当一个SCSI命令scmd被成功恢复后错误处理逻辑会通过
scsi_eh_finish_cmd()将其从待处理队列eh_work_q
至错误处理的本地完成队列eh_done_q。当所有scmd均恢
复完成即eh_work_q为空时错误处理逻辑会调用
scsi_eh_flush_done_q()对这些已恢复的scmd进行处理
重新尝试或错误总终止(向上层通知失败)。
SCSI命令仅在满足以下全部条件时才会被重试对应的SCSI设
备仍处于在线状态未设置REQ_FAILFAST标志或递增后的
scmd->retries值仍小于scmd->allowed。
2.1.2 SCSI命令在错误处理过程中的流转路径
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1. 错误完成/超时
:处理: 调用scsi_eh_scmd_add()处理scmd
- 将scmd添加到shost->eh_cmd_q
- 设置SHOST_RECOVERY标记位
- shost->host_failed++
:锁要求: shost->host_lock
2. 启动错误处理EH
:操作: 将所有scmd移动到EH本地eh_work_q队列
清空 shost->eh_cmd_q。
:锁要求: shost->host_lock非严格必需仅为保持一致性
3. scmd恢复
:操作: 调用scsi_eh_finish_cmd()完成scmd的EH
- 将scmd从本地eh_work_q队列移至本地eh_done_q队列
:锁要求:
:并发控制: 每个独立的eh_work_q至多一个线程确保无锁
队列的访问
4. EH完成
:操作: 调用scsi_eh_flush_done_q()重试scmd或通知上层处理
失败。此函数可以被并发调用但每个独立的eh_work_q队
列至多一个线程,以确保无锁队列的访问。
- 从eh_done_q队列中移除scmd清除scmd->eh_entry
- 如果需要重试调用scsi_queue_insert()重新入队scmd
- 否则调用scsi_finish_command()完成scmd
- 将shost->host_failed置为零
:锁要求: 队列或完成函数会执行适当的加锁操作
2.1.3 控制流
^^^^^^^^^^^^
通过细粒度回调机制执行的SCSI错误处理EH是从
scsi_unjam_host()函数开始的
``scsi_unjam_host``
1. 持有shost->host_lock锁将shost->eh_cmd_q中的命令移动
到本地的eh_work_q队里中并释放host_lock锁。注意这一步
会清空shost->eh_cmd_q。
2. 调用scsi_eh_get_sense函数。
``scsi_eh_get_sense``
该操作针对没有有效感知数据的错误完成命令。大部分SCSI传输协议
或底层驱动在命令失败时会自动获取感知数据(自动感知)。出于性
能原因,建议使用自动感知,推荐使用自动感知机制,因为它不仅有
助于提升性能还能避免从发生CHECK CONDITION到执行本操作之间
感知信息出现不同步的问题。
注意如果不支持自动感知那么在使用scsi_done()以错误状态完成
scmd 时scmd->sense_buffer将包含无效感知数据。在这种情况下
scsi_decide_disposition()总是返回FAILED从而触发SCSI错误处理
EH。当该scmd执行到这里时会重新获取感知数据并再次调用
scsi_decide_disposition()进行处理。
1. 调用scsi_request_sense()发送REQUEST_SENSE命令。如果失败
则不采取任何操作。请注意不采取任何操作会导致对该scmd执行
更高级别的恢复操作。
2. 调用scsi_decide_disposition()处理scmd
- SUCCESS
scmd->retries被设置为scmd->allowed以防止
scsi_eh_flush_done_q()重试该scmd并调用
scsi_eh_finish_cmd()。
- NEEDS_RETRY
调用scsi_eh_finish_cmd()
- 其他情况
无操作。
4. 如果!list_empty(&eh_work_q)则调用scsi_eh_ready_devs()。
``scsi_eh_ready_devs``
该函数采取四种逐步增强的措施,使失败的设备准备好处理新的命令。
1. 调用scsi_eh_stu()
``scsi_eh_stu``
对于每个具有有效感知数据且scsi_check_sense()判断为失败的
scmd发送START STOP UNITSTU命令且将start置1。注意
于我们明确选择错误完成的scmd可以确定底层驱动已不再维护该
scmd我们可以重用它进行STU。
如果STU操作成功且sdev处于离线或就绪状态所有在sdev上失败的
scmd都会通过scsi_eh_finish_cmd()完成。
*注意* 如果hostt->eh_abort_handler()未实现或返回失败,可能
此时仍有超时的scmd此时STU不会导致底层驱动不再维护scmd。但
如果STU执行成功该函数会通过scsi_eh_finish_cmd()来完成
sdev上的所有scmd这会导致底层驱动处于不一致的状态。看来STU
操作应仅在sdev不包含超时scmd时进行。
2. 如果!list_empty(&eh_work_q)调用scsi_eh_bus_device_reset()。
``scsi_eh_bus_device_reset``
此操作与scsi_eh_stu()非常相似,区别在于使用
hostt->eh_device_reset_handler()替代STU命令。此外由于我们
没有发送SCSI命令且重置会清空该sdev上所有的scmd所以无需筛选错
误完成的scmd。
3. 如果!list_empty(&eh_work_q)调用scsi_eh_bus_reset()。
``scsi_eh_bus_reset``
对于每个包含失败scmd的SCSI通道调用
hostt->eh_bus_reset_handler()。如果总线重置成功,那么该通道上
所有准备就绪或离线状态sdev上的失败scmd都会被处理处理完成。
4. 如果!list_empty(&eh_work_q)调用scsi_eh_host_reset()。
``scsi_eh_host_reset``
调用hostt->eh_host_reset_handler()是最终的手段。如果SCSI主机
重置成功主机上所有就绪或离线sdev上的失败scmd都会通过错误处理
完成。
5. 如果!list_empty(&eh_work_q)调用scsi_eh_offline_sdevs()。
``scsi_eh_offline_sdevs``
离线所有包含未恢复scmd的所有sdev并通过
scsi_eh_finish_cmd()完成这些scmd。
5. 调用scsi_eh_flush_done_q()。
``scsi_eh_flush_done_q``
此时所有的scmd都已经恢复或放弃并通过
scsi_eh_finish_cmd()函数加入eh_done_q队列。该函数通过
重试或显示通知上层scmd的失败来刷新eh_done_q。
2.2 基于transportt->eh_strategy_handler()的错误处理机制
-------------------------------------------------------------
在该机制中transportt->eh_strategy_handler()替代
scsi_unjam_host()的被调用,并负责整个错误恢复过程。该处理
函数完成后应该确保底层驱动不再维护任何失败的scmd并且将设备
设置为就绪(准备接收新命令)或离线状态。此外,该函数还应该
执行SCSI错误处理的维护任务以维护SCSI中间层的数据完整性。
换句话说eh_strategy_handler()必须实现[2-1-2]中除第1步
外的所有步骤。
2.2.1 transportt->eh_strategy_handler()调用前的SCSI中间层状态
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
进入该处理函数时,以下条件成立。
- 每个失败的scmd的eh_flags字段已正确设置。
- 每个失败的scmd通过scmd->eh_entry链接到scmd->eh_cmd_q队列。
- 已设置SHOST_RECOVERY标志。
- `shost->host_failed == shost->host_busy`
2.2.2 transportt->eh_strategy_handler()调用后的SCSI中间层状态
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
从该处理函数退出时,以下条件成立。
- shost->host_failed为零。
- shost->eh_cmd_q被清空。
- 每个scmd->eh_entry被清空。
- 对每个scmd必须调用scsi_queue_insert()或scsi_finish_command()。
注意该处理程序可以使用scmd->retries剩余重试次数
scmd->allowed允许重试次数限制重试次数。
2.2.3 注意事项
^^^^^^^^^^^^^^
- 需明确已超时的scmd在底层仍处于活跃状态因此在操作这些
scmd前必须确保底层已彻底不再维护。
- 访问或修改shost数据结构时必须持有shost->host_lock锁
以维持数据一致性。
- 错误处理完成后每个故障设备必须彻底清除所有活跃SCSI命
scmd的关联状态。
- 错误处理完成后,每个故障设备必须被设置为就绪(准备接收
新命令)或离线状态。
Tejun Heo
htejun@gmail.com
11th September 2005

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,38 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/scsi/sd-parameters.rst
:翻译:
郝栋栋 doubled <doubled@leap-io-kernel.com>
:校译:
============================
Linux SCSI磁盘驱动sd参数
============================
缓存类型(读/写)
------------------
启用/禁用驱动器读写缓存。
=========================== ===== ===== ======= =======
缓存类型字符串 WCE RCD 写缓存 读缓存
=========================== ===== ===== ======= =======
write through 0 0 关闭 开启
none 0 1 关闭 关闭
write back 1 0 开启 开启
write back, no read (daft) 1 1 开启 关闭
=========================== ===== ===== ======= =======
将缓存类型设置为“write back”并将该设置保存到驱动器::
# echo "write back" > cache_type
如果要修改缓存模式但不使更改持久化,可在缓存类型字符串前
添加“temporary ”。例如::
# echo "temporary write back" > cache_type

View File

@@ -0,0 +1,317 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/security/SCTP.rst
:翻译:
赵硕 Shuo Zhao <zhaoshuo@cqsoftware.com.cn>
====
SCTP
====
SCTP的LSM支持
=============
安全钩子
--------
对于安全模块支持已经实现了三个特定于SCTP的钩子::
security_sctp_assoc_request()
security_sctp_bind_connect()
security_sctp_sk_clone()
security_sctp_assoc_established()
这些钩子的用法在下面的 `SCTP的SELinux支持`_ 一章中描述SELinux的实现。
security_sctp_assoc_request()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
将关联INIT数据包的 ``@asoc````@chunk->skb`` 传递给安全模块。
成功时返回 0失败时返回错误。
::
@asoc - 指向sctp关联结构的指针。
@skb - 指向包含关联数据包skbuff的指针。
security_sctp_bind_connect()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
将一个或多个IPv4/IPv6地址传递给安全模块进行基于 ``@optname`` 的验证,
这将导致是绑定还是连接服务,如下面的权限检查表所示。成功时返回 0失败
时返回错误。
::
@sk - 指向sock结构的指针。
@optname - 需要验证的选项名称。
@address - 一个或多个IPv4 / IPv6地址。
@addrlen - 地址的总长度。使用sizeof(struct sockaddr_in)或
sizeof(struct sockaddr_in6)来计算每个ipv4或ipv6地址。
------------------------------------------------------------------
| BIND 类型检查 |
| @optname | @address contains |
|----------------------------|-----------------------------------|
| SCTP_SOCKOPT_BINDX_ADD | 一个或多个 ipv4 / ipv6 地址 |
| SCTP_PRIMARY_ADDR | 单个 ipv4 or ipv6 地址 |
| SCTP_SET_PEER_PRIMARY_ADDR | 单个 ipv4 or ipv6 地址 |
------------------------------------------------------------------
------------------------------------------------------------------
| CONNECT 类型检查 |
| @optname | @address contains |
|----------------------------|-----------------------------------|
| SCTP_SOCKOPT_CONNECTX | 一个或多个 ipv4 / ipv6 地址 |
| SCTP_PARAM_ADD_IP | 一个或多个 ipv4 / ipv6 地址 |
| SCTP_SENDMSG_CONNECT | 单个 ipv4 or ipv6 地址 |
| SCTP_PARAM_SET_PRIMARY | 单个 ipv4 or ipv6 地址 |
------------------------------------------------------------------
条目 ``@optname`` 的摘要如下::
SCTP_SOCKOPT_BINDX_ADD - 允许在(可选地)调用 bind(3) 后,关联额外
的绑定地址。
sctp_bindx(3) 用于在套接字上添加一组绑定地址。
SCTP_SOCKOPT_CONNECTX - 允许分配多个地址以连接到对端(多宿主)。
sctp_connectx(3) 使用多个目标地址在SCTP
套接字上发起连接。
SCTP_SENDMSG_CONNECT - 通过sendmsg(2)或sctp_sendmsg(3)在新关联上
发起连接。
SCTP_PRIMARY_ADDR - 设置本地主地址。
SCTP_SET_PEER_PRIMARY_ADDR - 请求远程对端将某个地址设置为其主地址。
SCTP_PARAM_ADD_IP - 在启用动态地址重配置时使用。
SCTP_PARAM_SET_PRIMARY - 如下所述,启用重新配置功能。
为了支持动态地址重新配置,必须在两个端点上启用以下
参数(或使用适当的 **setsockopt**\(2)::
/proc/sys/net/sctp/addip_enable
/proc/sys/net/sctp/addip_noauth_enable
当相应的 ``@optname`` 存在时,以下的 *_PARAM_* 参数会
通过ASCONF块发送到对端::
@optname ASCONF Parameter
---------- ------------------
SCTP_SOCKOPT_BINDX_ADD -> SCTP_PARAM_ADD_IP
SCTP_SET_PEER_PRIMARY_ADDR -> SCTP_PARAM_SET_PRIMARY
security_sctp_sk_clone()
~~~~~~~~~~~~~~~~~~~~~~~~
每当通过 **accept**\(2)创建一个新的套接字即TCP类型的套接字或者当
一个套接字被‘剥离’时如用户空间调用 **sctp_peeloff**\(3),会调用此函数。
::
@asoc - 指向当前sctp关联结构的指针。
@sk - 指向当前套接字结构的指针。
@newsk - 指向新的套接字结构的指针。
security_sctp_assoc_established()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
当收到COOKIE ACK时调用对于客户端对端的secid将被保存
``@asoc->peer_secid``::
@asoc - 指向sctp关联结构的指针。
@skb - 指向COOKIE ACK数据包的skbuff指针。
用于关联建立的安全钩子
----------------------
下图展示了在建立关联时 ``security_sctp_bind_connect()````security_sctp_assoc_request()``
``security_sctp_assoc_established()`` 的使用。
::
SCTP 端点 "A" SCTP 端点 "Z"
============= =============
sctp_sf_do_prm_asoc()
关联的设置可以通过connect(2),
sctp_connectx(3),sendmsg(2)
or sctp_sendmsg(3)来发起。
这将导致调用security_sctp_bind_connect()
发起与SCTP对端端点"Z"的关联。
INIT --------------------------------------------->
sctp_sf_do_5_1B_init()
响应一个INIT数据块。
SCTP对端端点"A"正在请求一个临时关联。
如果是首次关联调用security_sctp_assoc_request()
来设置对等方标签。
如果不是首次关联,检查是否被允许。
如果允许,则发送:
<----------------------------------------------- INIT ACK
|
| 否则,生成审计事件并默默丢弃该数据包。
|
COOKIE ECHO ------------------------------------------>
sctp_sf_do_5_1D_ce()
响应一个COOKIE ECHO数据块。
确认该cookie并创建一个永久关联。
调用security_sctp_assoc_request()
执行与INIT数据块响应相同的操作。
<------------------------------------------- COOKIE ACK
| |
sctp_sf_do_5_1E_ca |
调用security_sctp_assoc_established() |
来设置对方标签 |
| |
| 如果是SCTP_SOCKET_TCP或是剥离的套接
| 字,会调用 security_sctp_sk_clone()
| 来克隆新的套接字。
| |
建立 建立
| |
------------------------------------------------------------------
| 关联建立 |
------------------------------------------------------------------
SCTP的SELinux支持
=================
安全钩子
--------
上面的 `SCTP的LSM支持`_ 章节描述了以下SCTP安全钩子SELinux的细节
说明如下::
security_sctp_assoc_request()
security_sctp_bind_connect()
security_sctp_sk_clone()
security_sctp_assoc_established()
security_sctp_assoc_request()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
将关联INIT数据包的 ``@asoc````@chunk->skb`` 传递给安全模块。
成功时返回 0失败时返回错误。
::
@asoc - 指向sctp关联结构的指针。
@skb - 指向关联数据包skbuff的指针。
安全模块执行以下操作:
如果这是 ``@asoc->base.sk`` 上的首次关联则将对端的sid设置
``@skb`` 中的值。这将确保只有一个对端sid分配给可能支持多个
关联的 ``@asoc->base.sk``
否则验证 ``@asoc->base.sk peer sid`` 是否与 ``@skb peer sid``
匹配,以确定该关联是否应被允许或拒绝。
将sctp的 ``@asoc sid`` 设置为套接字的sid来自 ``asoc->base.sk``
并从 ``@skb peer sid`` 中提取MLS部分。这将在SCTP的TCP类型套接字及
剥离连接中使用,因为它们会导致生成一个新的套接字。
如果配置了IP安全选项CIPSO/CALIPSO则会在套接字上设置IP选项。
security_sctp_bind_connect()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
根据 ``@optname`` 检查ipv4/ipv6地址所需的权限具体如下::
------------------------------------------------------------------
| BIND 权限检查 |
| @optname | @address contains |
|----------------------------|-----------------------------------|
| SCTP_SOCKOPT_BINDX_ADD | 一个或多个 ipv4 / ipv6 地址 |
| SCTP_PRIMARY_ADDR | 单个 ipv4 or ipv6 地址 |
| SCTP_SET_PEER_PRIMARY_ADDR | 单个 ipv4 or ipv6 地址 |
------------------------------------------------------------------
------------------------------------------------------------------
| CONNECT 权限检查 |
| @optname | @address contains |
|----------------------------|-----------------------------------|
| SCTP_SOCKOPT_CONNECTX | 一个或多个 ipv4 / ipv6 地址 |
| SCTP_PARAM_ADD_IP | 一个或多个 ipv4 / ipv6 地址 |
| SCTP_SENDMSG_CONNECT | 单个 ipv4 or ipv6 地址 |
| SCTP_PARAM_SET_PRIMARY | 单个 ipv4 or ipv6 地址 |
------------------------------------------------------------------
`SCTP的LSM支持`_ 提供了 ``@optname`` 摘要,并且还描述了当启用动态地址重新
配置时ASCONF块的处理过程。
security_sctp_sk_clone()
~~~~~~~~~~~~~~~~~~~~~~~~
每当通过 **accept**\(2)即TCP类型的套接字创建一个新的套接字或者
当一个套接字被“剥离”如用户空间调用 **sctp_peeloff**\(3)时,
``security_sctp_sk_clone()`` 将会分别将新套接字的sid和对端sid设置为
``@asoc sid````@asoc peer sid`` 中包含的值。
::
@asoc - 指向当前sctp关联结构的指针。
@sk - 指向当前sock结构的指针。
@newsk - 指向新sock结构的指针。
security_sctp_assoc_established()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
当接收到COOKIE ACK时调用它将连接的对端sid设置为 ``@skb`` 中的值::
@asoc - 指向sctp关联结构的指针。
@skb - 指向COOKIE ACK包skbuff的指针。
策略声明
--------
以下支持SCTP的类和权限在内核中是可用的::
class sctp_socket inherits socket { node_bind }
当启用以下策略功能时::
policycap extended_socket_class;
SELinux对SCTP的支持添加了用于连接特定端口类型 ``name_connect`` 权限
以及在下面的章节中进行解释的 ``association`` 权限。
如果用户空间工具已更新SCTP将支持如下所示的 ``portcon`` 声明::
portcon sctp 1024-1036 system_u:object_r:sctp_ports_t:s0
SCTP对端标签
------------
每个SCTP套接字仅分配一个对端标签。这个标签将在建立第一个关联时分配。
任何后续在该套接字上的关联都会将它们的数据包对端标签与套接字的对端标
签进行比较,只有在它们不同的情况下 ``association`` 权限才会被验证。
这是通过检查套接字的对端sid与接收到的数据包中的对端sid来验证的以决
定是否允许或拒绝该关联。
注:
1) 如果对端标签未启用,则对端上下文将始终是 ``SECINITSID_UNLABELED``
(在策略声明中为 ``unlabeled_t`` )。
2) 由于SCTP可以在单个套接字上支持每个端点多宿主的多个传输地址因此
可以配置策略和NetLabel为每个端点提供不同的对端标签。由于套接字的对端
标签是由第一个关联的传输地址决定的,因此建议所有的对端标签保持一致。
3) 用户空间可以使用 **getpeercon**\(3) 来检索套接字的对端上下文。
4) 虽然这不是SCTP特有的但在使用NetLabel时要注意如果标签分配给特定的接
而该接口goes down则NetLabel服务会移除该条目。因此请确保网络启
动脚本调用 **netlabelctl**\(8) 来设置所需的标签(详细信息,
请参阅 **netlabel-config**\(8) 辅助脚本)。
5) NetLabel SCTP对端标签规则应用如下所述标签为“netlabel”的一组帖子
https://www.paul-moore.com/blog/t.
6) CIPSO仅支持IPv4地址 ``socket(AF_INET, ...)``
CALIPSO仅支持IPv6地址 ``socket(AF_INET6, ...)``
测试CIPSO/CALIPSO时请注意以下事项
a) 如果SCTP数据包由于无效标签无法送达CIPSO会发送一个ICMP包。
b) CALIPSO不会发送ICMP包只会默默丢弃数据包。
7) RFC 3554不支持IPSEC —— SCTP/IPSEC支持尚未在用户空间实现(**racoon**\(8)
**ipsec_pluto**\(8)),尽管内核支持 SCTP/IPSEC。

View File

@@ -18,7 +18,9 @@
credentials
snp-tdx-threat-model
lsm
lsm-development
sak
SCTP
self-protection
siphash
tpm/index
@@ -28,7 +30,5 @@
TODOLIST:
* IMA-templates
* keys/index
* lsm-development
* SCTP
* secrets/index
* ipe

View File

@@ -0,0 +1,398 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/security/sak.rst
:翻译:
赵硕 Shuo Zhao <zhaoshuo@cqsoftware.com.cn>
完整性策略执行IPE-内核文档
==============================
.. NOTE::
这是针对开发人员而不是管理员的文档。如果您正在
寻找有关IPE使用的文档请参阅 :doc:`IPE admin
guide </admin-guide/LSM/ipe>`。
历史背景
--------
最初促使IPE实施的原因是需要创建一个锁定式系统。该系统将
从一开始就具备安全性,并且在可执行代码和系统功能关键的特定
数据文件上,提供强有力的完整性保障。只有当这些特定数据文件
符合完整性策略时,它们才可以被读取。系统中还将存在强制访问
控制机制因此扩展属性xattrs也必须受到保护。这就引出了
需要选择能够提供完整性保证的机制。当时,有两种主要机制被考
虑,用以在满足这些要求的前提下保证系统完整性:
1. IMA + EVM Signatures
2. DM-Verity
这两个选项都经过了仔细考虑然而在原始的IPE使用场景
最终选择DM-Verity而非IMA+EVM作为完整性机制
要有三个原因:
1. 防护额外的攻击途径
* 使用IMA+EVM时如果没有加密解决方案系统很容易受到
离线攻击,特别是针对上述特定数据文件的攻击。
与可执行文件不同,读取操作(如对受保护数据文件的读
取操作)无法强制性进行全局完整性验证。这意味着必须
有一种选择机制来决定是否应对某个读取操作实施完整性
策略。
在当时这是通过强制访问控制标签来实现的IMA策略会
指定哪些标签需要进行完整性验证,这带来了一个问题:
EVM虽然可以保护标签但如果攻击者离线修改文件系统
那么攻击者就可以清除所有的扩展属性xattrs——包括
用于确定文件是否应受完整性策略约束的SELinux标签。
使用DM-Verity由于xattrs被保存为Merkel树的一部分
如果对由dm-verity保护的文件系统进行了离线挂载校验
和将不在匹配,文件将无法读取。
* 由于用户空间的二进制文件在Linux中是分页加载的dm-
verity同样提供了对抗恶意块设备的额外保护。在这样的
攻击中块设备最初报告适当的内容以供IMA哈希计算
过所需的完整性检查。然后,在访问真实数据时发生的页面
错误将报告攻击者的有效载荷。由于dm-verity会在页面错
误发生时检查数据(以及磁盘访问),因此这种攻击得到了
缓解。
2. 性能:
* dm-verity在块被读取时按需提供完整性验证而不需要将整
个文件读入内存进行验证。
3. 签名的简化性:
* 不需要两个签名IMA 然后是 EVM一个签名可以覆盖整个
块设备。
* 签名可以存储在文件系统元数据之外。
* 该签名支持基于 x.509 的签名基础设施。
下一步是选择一个策略来执行完整性验证机制,该策略的最低
要求是:
1. 策略本身必须经过完整性验证(防止针对它的简单攻击)。
2. 策略本身必须抵抗回滚攻击。
3. 策略执行必须具有类似宽松模式的功能。
4. 策略必须能够在不重启的情况下,完整地进行更新。
5. 策略更新必须是原子性的。
6. 策略必须支持撤销先前创建的组件。
7. 策略必须在任何时间点都能进行审计。
当时IMA作为唯一的完整性策略机制被用来与这些要求进行对比
但未能满足所有最低要求。尽管考虑过扩展IMA以涵盖这些要求
最终因两个原因被放弃:
1. 回归风险这其中许多变更将导致对已经存在于内核的IMA进行
重大代码更改,因此可能会影响用户。
2. IMA在该系统中用于测量和证明将测量策略与本地完整性策略
的执行分离被认为是有利的。
由于这些原因决定创建一个新的LSM其职责是仅限于本地完整性
策略的执行。
职责和范围
----------
IPE顾名思义本质上是一种完整性策略执行解决方案IPE并不强制规定
如何提供完整性保障,而是将这一决策权留给系统管理员,管理员根据自身
需求,选择符合的机制来设定安全标准。存在几种不同的完整性解决方案,
它们提供了不同程度的安全保障而IPE允许系统管理员理论上为所有这些
解决方案制定策略。
IPE自身没有内置确保完整性的固有机制。相反在构建具备完整性保障能力
的系统时,存在更高效的分层方案可供使用。需要重点注意的是,用于证明完
整性的机制,与用于执行完整性声明的策略是相互独立的。
因此IPE依据以下方面进行设计
1. 便于与完整性提供机制集成。
2. 便于平台管理员/系统管理员使用。
设计理由:
---------
IPE是在评估其他操作系统和环境中的现有完整性策略解决方案后设计的。
在对其他实现的调查中,发现了一些缺陷:
1. 策略不易为人们读取,通常需要二进制中间格式。
2. 默认情况下会隐式采取单一的、不可定制的操作。
3. 调试策略需要手动来确定违反了哪个规则。
4. 编写策略需要对更大系统或操作系统有深入的了解。
IPE尝试避免所有这些缺陷。
策略
~~~~
纯文本
^^^^^^
IPE的策略是纯文本格式的。相较于其他Linux安全模块LSM
策略文件体积略大,但能解决其他平台上部分完整性策略方案存在
的两个核心问题。
第一个问题是代码维护和冗余的问题。为了编写策略,策略必须是
以某种形式的字符串形式呈现(无论是 XML、JSON、YAML 等结构化
格式,还是其他形式),以便策略编写者能够理解所写内容。在假设
的二进制策略设计中,需要一个序列化器将策略将可读的形式转换为
二进制形式,同时还需要一个反序列化器来将二进制形式转换为内核
中的数据结构。
最终,还需要另一个反序列化器将是必要的,用于将二进制形式转换
为人类可读的形式,并尽可能保存所有信息,这是因为使用此访问控
制系统的用户必须维护一个校验表和原始文件,才能理解哪些策略已
经部署在该系统上,哪些没有。对于单个用户来说,这可能没问题,
因为旧的策略可以在更新生效后很快被丢弃。但对于管理成千上万、
甚至数十万台计算机的用户,且这些计算机有不同的操作系统和不同
的操作需求,这很快就成了一个问题,因为数年前的过时策略可能仍然
存在,从而导致需要快速恢复策略或投资大量基础设施来跟踪每个策略
的内容。
有了这三个独立的序列化器/反序列化器,维护成本非常昂贵。如果策略
避免使用二进制格式,则只需要一个序列化器;将人类可读的形式转换
为内核中的数据结构。从而节省了代码维护成本,并保持了可操作性。
第二个关于二进制格式的问题是透明性由于IPE根据系统资源的可信度
来控制访问,因此其策略也必须可信,以便可以被更改。这是通过签名来
完成的,这就需要签名过程。签名过程通常具有很高的安全标准,因为
任何被签名的内容都可以被用来攻击完整性执行系统。签署时,签署者
必须知道他们在签署什么,二进制策略可能会导致这一点的模糊化;签署
者看到的只是一个不透明的二进制数据块。另一方面,对于纯文本策略中,
签署者看到的则是实际提交的策略。
启动策略
~~~~~~~~
如果配置得当IPE能够在内核启动并进入用户模式时立即执行策略。
这意味着需要在用户模式开始的那一刻就存储一定的策略。通常,这种
存储可以通过一下三种方式之一来处理:
1. 策略文件存储在磁盘上,内核在进入可能需要做出执行决策的代码
路径之前,先加载该策略。
2. 策略文件由引导加载程序传递给内核,内核解析这些策略。
3. 将一个策略文件编译到内核中,内核在初始化过程中对其进行解析并
执行。
第一种方式存在问题:内核从用户空间读取文件通常是不推荐的,并且在
内核中极为罕见。
第二种选项同样存在问题Linux在其整个生态系统中支持多种引导加载程序
所有引导加载程序都必须支持这种新方法,或者需要有一个独立的来源,这
可能会导致内核启动过程发生不必要的重大变化。
第三种选项是最佳选择,但需要注意的是,编译进内核的策略会占用磁盘空间。
重要的是要使这一策略足够通用,以便用户空间能够加载新的、更复杂的策略,
同时也要足够严格,以防止过度授权并避免引发安全问题。
initramfs提供了一种建立此启动路径的方法。内核启动时以最小化的策略启动
该策略仅信任initramfs。在initramfs内当真实的根文件系统已挂载且尚未
切换时,它会部署并激活一个信任新根文件系统的策略。这种方法防止了在任何
步骤中出现过度授权,并保持内核策略的最小化。
启动
^^^^
然而并不是每个系统都以initramfs启动因此编译进内核的启动策略需要具备
一定的灵活性,以明确如何为启动的下一个阶段建立信任。为此,如果我们将编译
进内核的策略设计为一个完整的IPE策略这样系统构建者便能合理定义第一阶段启
动的需求。
可更新、无需重启的策略
~~~~~~~~~~~~~~~~~~~~~~
随着时间的推移,系统需求发生变化(例如,之前信任的应用程序中发现漏洞、秘钥
轮换等)。更新内核以满足这些安全目标并非始终是一个合适的选择,因为内核更新并
非完全无风险的而搁置安全更新会使系统处于脆弱状态。这意味着IPE需要一个可以
完全更新的策略(允许撤销现有的策略),并且这个更新来源必须是内核外部的(允许
再不更新内核的情况下更新策略)。
此外,由于内核在调用之间是无状态的,并且从内核空间读取磁盘上的策略文件不是一
个好主意,因此策略更新必须能够在不重启的情况下完成。
为了允许从外部来源进行更新,考虑到外部来源可能是恶意的,因此该策略需要具备可被
识别为可信的机制。这一机制通过签名链实现:策略的签名需与内核中的某个信任源相
关联。通常,这个信任源是 ``SYSTEM_TRUSTED_KEYRING`` ,这是一个在内核编译时就被
初始化填充的密钥环,因为这符合上述编译进来策略的制作者与能够部署策略更新的实体
相同的预期。
防回滚 / 防重放
~~~~~~~~~~~~~~~
随着时间的推移系统可能会发现漏洞曾经受信任的资源可能不再可信IPE的
策略也不例外。可能会出现的情况是,策略制作者误部署了一个不安全的策略,
随后再用一个安全的策略进行修正。
假设一旦不安全的策略被部署攻击者获取了这个不安全的策略IPE需要有一种
方式来防止从安全的策略更新回滚到不安全的策略。
最初IPE的策略可以包含一个policy_version字段声明系统上所有可激活策略
所需的最低版本号。这将在系统运行期间防止回滚。
.. WARNING::
然而,由于内核每次启动都是无状态的,因此该策略版本将在下次
启动时被重置为0.0.0。系统构建者需要意识到这一点,并确保在启
动后尽快部署新的安全策略,以确保攻击者部署不安全的策略的几
率最小化。
隐式操作:
~~~~~~~~~
隐式操作的问题只有在考虑系统中多个操作具有不同级别时才会显现出来。
例如,考虑一个系统,该系统对可执行代码和系统中对其功能至关重要的
特定数据提供强大的完整性保障。在这个系统中,可能存在三种类型的
策略:
1. 一种策略,在这种策略中,如果操作未能匹配到任何规则,则该操
作将被拒绝。
2. 一种策略,在这种策略中,如果操作未能匹配到任何规则,则该操
作将被允许。
3. 一种策略,在这种策略中,如果操作未能匹配到任何规则,则执行
操作由策略作者指定。
第一种类型的策略示例如下::
op=EXECUTE integrity_verified=YES action=ALLOW
在示例系统中,这对于可执行文件来说效果很好,因为所有可执行文件
都应该拥有完整性保障。但问题出现在第二个要求上,即关于特定数据
文件的要求。这将导致如下策略(假设策略按行依次执行)::
op=EXECUTE integrity_verified=YES action=ALLOW
op=READ integrity_verified=NO label=critical_t action=DENY
op=READ action=ALLOW
若阅读过文档,了解策略按顺序执行且默认动作是拒绝,那么这个策略的
逻辑还算清晰;但最后一行规则实际上将读取操作的默认动作改成了允许。
这种设计是必要的,因为在实际系统中,存在一些无需验证的读取操作(例
如向日志文件追加内容时的读取操作)。
第二种策略类型(未匹配任何规则时默认允许)在管控特定数据文件时逻辑
更清晰,其策略可简化为::
op=READ integrity_verified=NO label=critical_t action=DENY
但与第一种策略类似,这种默认允许的策略在管控执行操作时会存在缺陷,
因此仍需显式覆盖默认动作::
op=EXECUTE integrity_verified=YES action=ALLOW
op=EXECUTE action=DENY
op=READ integrity_verified=NO label=critical_t action=DENY
这就引出了第三种策略类型(自定义默认动作)。该类型无需让用户绞尽脑汁
通过空规则覆盖默认动作,而是强制用户根据自身场景思考合适的默认动作是
什么,并显式声明::
DEFAULT op=EXECUTE action=DENY
op=EXECUTE integrity_verified=YES action=ALLOW
DEFAULT op=READ action=ALLOW
op=READ integrity_verified=NO label=critical_t action=DENY
策略调试:
~~~~~~~~~
在开发策略时,知道策略违反了哪一行有助于减少调试成本;可以
将调查的范围缩小到导致该行为的确切行。有些完整性策略系统并
不提供这一信息,而是提供评估过程中使用的信息。这随后需要将
这些信息和策略进行关联,以分析哪里了问题。
相反IPE只会输出匹配到的规则。这将调查范围限制到确切到策略行
在特定规则的情况下或部分在DEFAULT规则的情况下。当在
评估策略时观察到策略失败时,这可以减少迭代和调查的时间。
IPE的策略引擎还被设计成让人类容易理解如何调查策略失败。每一
行都会按编写顺序进行评估,因此算法非常简单,便于人类重现步
骤并找出可能导致失败的原因。而在调查其他的系统中,加载策略
时会进行优化(例如对规则排序)。在这些系统中,调试需要多个
步骤,而且没有先阅读代码的情况下,终端用户可能无法完全理解
该算法的原理。
简化策略:
~~~~~~~~~
最后IPE的策略是为系统管理员设计的而不是内核开发人员。
IPE不涉及单独的LSM钩子或系统调用而是涵盖操作。这
意味着,系统管理员不需要知道像 ``mmap````mprotect``
``execve````uselib`` 这些系统调用必须有规则进行保护,
而只需要知道他们想要限制代码执行。这减少了由于缺乏对底层
系统的了解而可能导致的绕过情况而IPE的维护者作为内核开发
人员,可以做出正确的选择,确定某些操作是否与这些操作匹配,
以及在什么条件下匹配。
实现说明
--------
匿名内存
~~~~~~~~
在IPE中匿名内存的处理方式与其他任何类型的访问没有区别。当匿
名内存使用 ``+X`` 映射时,它仍然会进入 ``file_mmp``
``file_mprotect`` 钩子,但此时会带有一个 ``NULL`` 文件对象
这会像其他文件一样提交进行评估。然而,所有当前的信任属性都会
评估为假,因为它们都是基于文件的,而此次操作并不与任何文件相关联。
.. WARNING::
这也适用于 ``kernel_load_data`` 钩子,当内核从一个没有文件
支持的用户空间缓冲区加载数据时。在这种情况下,所有当前的信任
属性也将评估为false。
Securityfs接口
~~~~~~~~~~~~~~
每个策略的对应的securityfs树是有些独特的。例如对于一个标准的
securityfs策略树::
MyPolicy
|- active
|- delete
|- name
|- pkcs7
|- policy
|- update
|- version
策略存储在MyPolicy对应节点的 ``->i_private`` 数据中。
测试
----
IPE为策略解析器提供了KUnit测试。推荐kunitconfig::
CONFIG_KUNIT=y
CONFIG_SECURITY=y
CONFIG_SECURITYFS=y
CONFIG_PKCS7_MESSAGE_PARSER=y
CONFIG_SYSTEM_DATA_VERIFICATION=y
CONFIG_FS_VERITY=y
CONFIG_FS_VERITY_BUILTIN_SIGNATURES=y
CONFIG_BLOCK=y
CONFIG_MD=y
CONFIG_BLK_DEV_DM=y
CONFIG_DM_VERITY=y
CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG=y
CONFIG_NET=y
CONFIG_AUDIT=y
CONFIG_AUDITSYSCALL=y
CONFIG_BLK_DEV_INITRD=y
CONFIG_SECURITY_IPE=y
CONFIG_IPE_PROP_DM_VERITY=y
CONFIG_IPE_PROP_DM_VERITY_SIGNATURE=y
CONFIG_IPE_PROP_FS_VERITY=y
CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG=y
CONFIG_SECURITY_IPE_KUNIT_TEST=y
此外IPE 具有一个基于 Python 的集成
`测试套件 <https://github.com/microsoft/ipe/tree/test-suite>`_
可以测试用户界面和强制执行功能。

View File

@@ -0,0 +1,19 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/security/lsm-development.rst
:翻译:
赵硕 Shuo Zhao <zhaoshuo@cqsoftware.com.cn>
=================
Linux安全模块开发
=================
基于https://lore.kernel.org/r/20071026073721.618b4778@laptopd505.fenrus.org
当一种新的LSM的意图它试图防范什么以及在哪些情况下人们会期望使用它
``Documentation/admin-guide/LSM/`` 中适当记录下来后,就会被接受进入内核。
这使得LSM的代码可以很轻松的与其目标进行对比从而让最终用户和发行版可以更
明智地决定那些LSM适合他们的需求。
有关可用的 LSM 钩子接口的详细文档,请参阅 ``security/security.c`` 及相关结构。

View File

@@ -0,0 +1,96 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../../disclaimer-zh_CN.rst
:Original: Documentation/security/secrets/coco.rst
:翻译:
赵硕 Shuo Zhao <zhaoshuo@cqsoftware.com.cn>
============
机密计算密钥
============
本文档介绍了在EFI驱动程序和efi_secret内核模块中机密计算密钥从固件
到操作系统的注入处理流程。
简介
====
机密计算硬件如AMD SEVSecure Encrypted Virtualization允许虚拟机
所有者将密钥注入虚拟机VM内存且主机/虚拟机监控程序无法读取这些密
钥。在SEV中密钥注入需在虚拟机启动流程的早期阶段客户机开始运行前
执行。
efi_secret内核模块允许用户空间应用程序通过securityfs安全文件系统访
问这些密钥。
密钥数据流
==========
客户机固件可能会为密钥注入预留一块指定的内存区域,并将该区域的位置(基准
客户机物理地址GPA和长度在EFI配置表中通过 ``LINUX_EFI_COCO_SECRET_AREA_GUID``
条目对应的GUID值为 ``adf956ad-e98c-484c-ae11-b51c7d336447`` )的形式发布。
固件应将此内存区域标记为 ``EFI_RESERVED_TYPE`` ,因此内核不应将其用于自身用途。
虚拟机启动过程中虚拟机管理器可向该区域注入密钥。在AMD SEV和SEV-ES中
操作通过 ``KVM_SEV_LAUNCH_SECRET`` 命令执行(参见 [sev_CN]_ )。注入的“客户机
所有者密钥数据”应采用带GUID的密钥值表结构其二进制格式在 ``drivers/virt/
coco/efi_secret/efi_secret.c`` 文件的EFI密钥区域结构部分中有详细描述。
内核启动时内核的EFI驱动程序将保存密钥区域位置来自EFI配置表``efi.coco_secret``
字段。随后,它会检查密钥区域是否已填充:映射该区域并检查其内容是否以
``EFI_SECRET_TABLE_HEADER_GUID`` 对应的GUID为 ``1e74f542-71dd-4d66-963e-ef4287ff173b``
开头。如果密钥区域已填充EFI驱动程序将自动加载efi_secret内核模块并通过securityfs将密钥
暴露给用户空间应用程序。efi_secret文件系统接口的详细信息请参考 [secrets-coco-abi_CN]_
应用使用示例
============
假设客户机需要对加密文件进行计算处理。客户机所有者通过密钥注入机制提供解密密钥
即密钥。客户机应用程序从efi_secret文件系统读取该密钥然后将文件解密到内存中
接着对内容进行需要的计算。
在此示例中,主机无法从磁盘镜像中读取文件,因为文件是加密的;主机无法读取解密密钥,
因为它是通过密钥注入机制(即安全通道)传递的;主机也无法读取内存中的解密内容,因为
这是一个机密型(内存加密)客户机。
以下是一个简单的示例展示了在客户机中使用efi_secret模块的过程在启动时注入了
一个包含4个密钥的EFI密钥区域::
# ls -la /sys/kernel/security/secrets/coco
total 0
drwxr-xr-x 2 root root 0 Jun 28 11:54 .
drwxr-xr-x 3 root root 0 Jun 28 11:54 ..
-r--r----- 1 root root 0 Jun 28 11:54 736870e5-84f0-4973-92ec-06879ce3da0b
-r--r----- 1 root root 0 Jun 28 11:54 83c83f7f-1356-4975-8b7e-d3a0b54312c6
-r--r----- 1 root root 0 Jun 28 11:54 9553f55d-3da2-43ee-ab5d-ff17f78864d2
-r--r----- 1 root root 0 Jun 28 11:54 e6f5a162-d67f-4750-a67c-5d065f2a9910
# hd /sys/kernel/security/secrets/coco/e6f5a162-d67f-4750-a67c-5d065f2a9910
00000000 74 68 65 73 65 2d 61 72 65 2d 74 68 65 2d 6b 61 |these-are-the-ka|
00000010 74 61 2d 73 65 63 72 65 74 73 00 01 02 03 04 05 |ta-secrets......|
00000020 06 07 |..|
00000022
# rm /sys/kernel/security/secrets/coco/e6f5a162-d67f-4750-a67c-5d065f2a9910
# ls -la /sys/kernel/security/secrets/coco
total 0
drwxr-xr-x 2 root root 0 Jun 28 11:55 .
drwxr-xr-x 3 root root 0 Jun 28 11:54 ..
-r--r----- 1 root root 0 Jun 28 11:54 736870e5-84f0-4973-92ec-06879ce3da0b
-r--r----- 1 root root 0 Jun 28 11:54 83c83f7f-1356-4975-8b7e-d3a0b54312c6
-r--r----- 1 root root 0 Jun 28 11:54 9553f55d-3da2-43ee-ab5d-ff17f78864d2
参考文献
========
请参见 [sev-api-spec_CN]_ 以获取有关SEV ``LAUNCH_SECRET`` 操作的更多信息。
.. [sev_CN] Documentation/virt/kvm/x86/amd-memory-encryption.rst
.. [secrets-coco-abi_CN] Documentation/ABI/testing/securityfs-secrets-coco
.. [sev-api-spec_CN] https://www.amd.com/system/files/TechDocs/55766_SEV-KM_API_Specification.pdf

View File

@@ -5,13 +5,10 @@
:翻译:
=====================
========
密钥文档
=====================
========
.. toctree::
TODOLIST:
* coco
coco

View File

@@ -71,12 +71,12 @@ TODOList:
:maxdepth: 1
filesystems/index
scsi/index
TODOList:
* block/index
* cdrom/index
* scsi/index
* target/index
**Fixme**: 这里还需要更多的分类组织工作。