mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
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:
67
Documentation/translations/zh_CN/filesystems/dnotify.rst
Normal file
67
Documentation/translations/zh_CN/filesystems/dnotify.rst
Normal 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。
|
||||
211
Documentation/translations/zh_CN/filesystems/gfs2-glocks.rst
Normal file
211
Documentation/translations/zh_CN/filesystems/gfs2-glocks.rst
Normal 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 加锁基于单个
|
||||
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 中均会包含此信息。
|
||||
57
Documentation/translations/zh_CN/filesystems/gfs2.rst
Normal file
57
Documentation/translations/zh_CN/filesystems/gfs2.rst
Normal 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 用于创建文件系统
|
||||
============ =============================================
|
||||
@@ -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
|
||||
|
||||
80
Documentation/translations/zh_CN/filesystems/inotify.rst
Normal file
80
Documentation/translations/zh_CN/filesystems/inotify.rst
Normal 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 更有优势。
|
||||
@@ -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
|
||||
114
Documentation/translations/zh_CN/filesystems/ubifs.rst
Normal file
114
Documentation/translations/zh_CN/filesystems/ubifs.rst
Normal 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
|
||||
176
Documentation/translations/zh_CN/networking/generic-hdlc.rst
Normal file
176
Documentation/translations/zh_CN/networking/generic-hdlc.rst
Normal 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 —— 支持IP(IPv4)接口或以太网设备仿真
|
||||
3. Cisco HDLC
|
||||
4. PPP
|
||||
5. X.25(使用X.25协议栈)
|
||||
|
||||
通用HDLC仅作为协议驱动 - 必须配合具体硬件的底层驱动
|
||||
才能运行。
|
||||
|
||||
以太网设备仿真(使用HDLC或帧中继PVC)兼容IEEE 802.1Q(VLAN)和
|
||||
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/>.
|
||||
@@ -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
|
||||
|
||||
139
Documentation/translations/zh_CN/networking/mptcp-sysctl.rst
Normal file
139
Documentation/translations/zh_CN/networking/mptcp-sysctl.rst
Normal 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
|
||||
674
Documentation/translations/zh_CN/networking/timestamping.rst
Normal file
674
Documentation/translations/zh_CN/networking/timestamping.rst
Normal 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_ID(TCP 套接字不支持)传递特定 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
|
||||
的单独控制消息中获取,作为 TLV(struct 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 v1,UDP,任何事件数据包 */
|
||||
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 硬件时钟的特殊考虑
|
||||
-------------------------------
|
||||
|
||||
在数据包的路径中可能存在多个 PHC(PTP 硬件时钟)。内核没有明确的机制允许用
|
||||
户选择用于时间戳以太网帧的 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 时间戳。
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
本文档包含了在内核中使用Rust支持时需要了解的有用信息。
|
||||
|
||||
.. _rust_code_documentation_zh_cn:
|
||||
|
||||
代码文档
|
||||
--------
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
215
Documentation/translations/zh_CN/rust/testing.rst
Normal file
215
Documentation/translations/zh_CN/rust/testing.rst
Normal 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 文档以获取更多信息。
|
||||
92
Documentation/translations/zh_CN/scsi/index.rst
Normal file
92
Documentation/translations/zh_CN/scsi/index.rst
Normal 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
|
||||
@@ -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模式下的延迟。
|
||||
===================== =====================================================
|
||||
118
Documentation/translations/zh_CN/scsi/scsi-parameters.rst
Normal file
118
Documentation/translations/zh_CN/scsi/scsi-parameters.rst
Normal 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 文件头部。
|
||||
48
Documentation/translations/zh_CN/scsi/scsi.rst
Normal file
48
Documentation/translations/zh_CN/scsi/scsi.rst
Normal 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 。
|
||||
482
Documentation/translations/zh_CN/scsi/scsi_eh.rst
Normal file
482
Documentation/translations/zh_CN/scsi/scsi_eh.rst
Normal 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_cmnd(SCSI命令)结构体
|
||||
[1-2] scmd(SCSI 命令)是如何完成的?
|
||||
[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来发送恢复命令。对于
|
||||
超时的scmd,SCSI错误处理机制会确保底层驱动在重用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 UNIT(STU)命令且将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
|
||||
1174
Documentation/translations/zh_CN/scsi/scsi_mid_low_api.rst
Normal file
1174
Documentation/translations/zh_CN/scsi/scsi_mid_low_api.rst
Normal file
File diff suppressed because it is too large
Load Diff
38
Documentation/translations/zh_CN/scsi/sd-parameters.rst
Normal file
38
Documentation/translations/zh_CN/scsi/sd-parameters.rst
Normal 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
|
||||
317
Documentation/translations/zh_CN/security/SCTP.rst
Normal file
317
Documentation/translations/zh_CN/security/SCTP.rst
Normal 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。
|
||||
@@ -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
|
||||
|
||||
398
Documentation/translations/zh_CN/security/ipe.rst
Normal file
398
Documentation/translations/zh_CN/security/ipe.rst
Normal 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>`_
|
||||
可以测试用户界面和强制执行功能。
|
||||
@@ -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`` 及相关结构。
|
||||
96
Documentation/translations/zh_CN/security/secrets/coco.rst
Normal file
96
Documentation/translations/zh_CN/security/secrets/coco.rst
Normal 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 SEV,Secure 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
|
||||
|
||||
@@ -5,13 +5,10 @@
|
||||
|
||||
:翻译:
|
||||
|
||||
=====================
|
||||
========
|
||||
密钥文档
|
||||
=====================
|
||||
========
|
||||
|
||||
.. toctree::
|
||||
|
||||
|
||||
TODOLIST:
|
||||
|
||||
* coco
|
||||
coco
|
||||
|
||||
@@ -71,12 +71,12 @@ TODOList:
|
||||
:maxdepth: 1
|
||||
|
||||
filesystems/index
|
||||
scsi/index
|
||||
|
||||
TODOList:
|
||||
|
||||
* block/index
|
||||
* cdrom/index
|
||||
* scsi/index
|
||||
* target/index
|
||||
|
||||
**Fixme**: 这里还需要更多的分类组织工作。
|
||||
|
||||
Reference in New Issue
Block a user