Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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'

+5056 -15
+67
Documentation/translations/zh_CN/filesystems/dnotify.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + 3 + .. include:: ../disclaimer-zh_CN.rst 4 + 5 + :Original: Documentation/filesystems/dnotify.rst 6 + 7 + :翻译: 8 + 9 + 王龙杰 Wang Longjie <wang.longjie1@zte.com.cn> 10 + 11 + ============== 12 + Linux 目录通知 13 + ============== 14 + 15 + Stephen Rothwell <sfr@canb.auug.org.au> 16 + 17 + 目录通知的目的是使用户应用程序能够在目录或目录中的任何文件发生变更时收到通知。基本机制包括应用程序 18 + 通过 fcntl(2) 调用在目录上注册通知,通知本身则通过信号传递。 19 + 20 + 应用程序可以决定希望收到哪些 “事件” 的通知。当前已定义的事件如下: 21 + 22 + ========= ===================================== 23 + DN_ACCESS 目录中的文件被访问(read) 24 + DN_MODIFY 目录中的文件被修改(write,truncate) 25 + DN_CREATE 目录中创建了文件 26 + DN_DELETE 目录中的文件被取消链接 27 + DN_RENAME 目录中的文件被重命名 28 + DN_ATTRIB 目录中的文件属性被更改(chmod,chown) 29 + ========= ===================================== 30 + 31 + 通常,应用程序必须在每次通知后重新注册,但如果将 DN_MULTISHOT 与事件掩码进行或运算,则注册 32 + 将一直保持有效,直到被显式移除(通过注册为不接收任何事件)。 33 + 34 + 默认情况下,SIGIO 信号将被传递给进程,且不附带其他有用的信息。但是,如果使用 F_SETSIG fcntl(2) 35 + 调用让内核知道要传递哪个信号,一个 siginfo 结构体将被传递给信号处理程序,该结构体的 si_fd 成员将 36 + 包含与发生事件的目录相关联的文件描述符。 37 + 38 + 应用程序最好选择一个实时信号(SIGRTMIN + <n>),以便通知可以被排队。如果指定了 DN_MULTISHOT, 39 + 这一点尤为重要。注意,SIGRTMIN 通常是被阻塞的,因此最好使用(至少)SIGRTMIN + 1。 40 + 41 + 实现预期(特性与缺陷 :-)) 42 + -------------------------- 43 + 44 + 对于文件的任何本地访问,通知都应能正常工作,即使实际文件系统位于远程服务器上。这意味着,对本地用户 45 + 模式服务器提供的文件的远程访问应能触发通知。同样的,对本地内核 NFS 服务器提供的文件的远程访问 46 + 也应能触发通知。 47 + 48 + 为了尽可能减小对文件系统代码的影响,文件硬链接的问题已被忽略。因此,如果一个文件(x)存在于两个 49 + 目录(a 和 b)中,通过名称”a/x”对该文件进行的更改应通知给期望接收目录“a”通知的程序,但不会 50 + 通知给期望接收目录“b”通知的程序。 51 + 52 + 此外,取消链接的文件仍会在它们链接到的最后一个目录中触发通知。 53 + 54 + 配置 55 + ---- 56 + 57 + Dnotify 由 CONFIG_DNOTIFY 配置选项控制。禁用该选项时,fcntl(fd, F_NOTIFY, ...) 将返 58 + 回 -EINVAL。 59 + 60 + 示例 61 + ---- 62 + 具体示例可参见 tools/testing/selftests/filesystems/dnotify_test.c。 63 + 64 + 注意 65 + ---- 66 + 从 Linux 2.6.13 开始,dnotify 已被 inotify 取代。有关 inotify 的更多信息,请参见 67 + Documentation/filesystems/inotify.rst。
+211
Documentation/translations/zh_CN/filesystems/gfs2-glocks.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + 3 + .. include:: ../disclaimer-zh_CN.rst 4 + 5 + :Original: Documentation/filesystems/gfs2-glocks.rst 6 + 7 + :翻译: 8 + 9 + 邵明寅 Shao Mingyin <shao.mingyin@zte.com.cn> 10 + 11 + :校译: 12 + 13 + 杨涛 yang tao <yang.tao172@zte.com.cn> 14 + 15 + ================== 16 + Glock 内部加锁规则 17 + ================== 18 + 19 + 本文档阐述 glock 状态机内部运作的基本原理。每个 glock(即 20 + fs/gfs2/incore.h 中的 struct gfs2_glock)包含两把主要的内部锁: 21 + 22 + 1. 自旋锁(gl_lockref.lock):用于保护内部状态(如 23 + gl_state、gl_target)和持有者列表(gl_holders) 24 + 2. 非阻塞的位锁(GLF_LOCK):用于防止其他线程同时调用 25 + DLM 等操作。若某线程获取此锁,则在释放时必须调用 26 + run_queue(通常通过工作队列),以确保所有待处理任务 27 + 得以完成。 28 + 29 + gl_holders 列表包含与该 glock 关联的所有排队锁请求(不 30 + 仅是持有者)。若存在已持有的锁,它们将位于列表开头的连 31 + 续条目中。锁的授予严格遵循排队顺序。 32 + 33 + glock 层用户可请求三种锁状态:共享(SH)、延迟(DF)和 34 + 排他(EX)。它们对应以下 DLM 锁模式: 35 + 36 + ========== ====== ===================================================== 37 + Glock 模式 DLM 锁模式 38 + ========== ====== ===================================================== 39 + UN IV/NL 未加锁(无关联的 DLM 锁)或 NL 40 + SH PR 受保护读(Protected read) 41 + DF CW 并发写(Concurrent write) 42 + EX EX 排他(Exclusive) 43 + ========== ====== ===================================================== 44 + 45 + 因此,DF 本质上是一种与“常规”共享锁模式(SH)互斥的共 46 + 享模式。在 GFS2 中,DF 模式专用于直接 I/O 操作。Glock 47 + 本质上是锁加缓存管理例程的组合,其缓存规则如下: 48 + 49 + ========== ============== ========== ========== ============== 50 + Glock 模式 缓存元数据 缓存数据 脏数据 脏元数据 51 + ========== ============== ========== ========== ============== 52 + UN 否 否 否 否 53 + DF 是 否 否 否 54 + SH 是 是 否 否 55 + EX 是 是 是 是 56 + ========== ============== ========== ========== ============== 57 + 58 + 这些规则通过为每种 glock 定义的操作函数实现。并非所有 59 + glock 类型都使用全部的模式,例如仅 inode glock 使用 DF 模 60 + 式。 61 + 62 + glock 操作函数及类型常量说明表: 63 + 64 + ============== ======================================================== 65 + 字段 用途 66 + ============== ======================================================== 67 + go_sync 远程状态变更前调用(如同步脏数据) 68 + go_xmote_bh 远程状态变更后调用(如刷新缓存) 69 + go_inval 远程状态变更需使缓存失效时调用 70 + go_instantiate 获取 glock 时调用 71 + go_held 每次获取 glock 持有者时调用 72 + go_dump 为 debugfs 文件打印对象内容,或出错时将 glock 转储至日志 73 + go_callback 若 DLM 发送回调以释放此锁时调用 74 + go_unlocked 当 glock 解锁时调用(dlm_unlock()) 75 + go_type glock 类型,``LM_TYPE_*`` 76 + go_flags 若 glock 关联地址空间,则设置GLOF_ASPACE 标志 77 + ============== ======================================================== 78 + 79 + 每种锁的最短持有时间是指在远程锁授予后忽略远程降级请求 80 + 的时间段。此举旨在防止锁在集群节点间持续弹跳而无实质进 81 + 展的情况,此现象常见于多节点写入的共享内存映射文件。通 82 + 过延迟响应远程回调的降级操作,为用户空间程序争取页面取 83 + 消映射前的处理时间。 84 + 85 + 未来计划将 glock 的 "EX" 模式设为本地共享,使本地锁通 86 + 过 i_mutex 实现而非 glock。 87 + 88 + glock 操作函数的加锁规则: 89 + 90 + ============== ====================== ============================= 91 + 操作 GLF_LOCK 位锁持有 gl_lockref.lock 自旋锁持有 92 + ============== ====================== ============================= 93 + go_sync 是 否 94 + go_xmote_bh 是 否 95 + go_inval 是 否 96 + go_instantiate 否 否 97 + go_held 否 否 98 + go_dump 有时 是 99 + go_callback 有时(N/A) 是 100 + go_unlocked 是 否 101 + ============== ====================== ============================= 102 + 103 + .. Note:: 104 + 105 + 若入口处持有锁则操作期间不得释放位锁或自旋锁。 106 + go_dump 和 do_demote_ok 严禁阻塞。 107 + 仅当 glock 状态指示其缓存最新数据时才会调用 go_dump。 108 + 109 + GFS2 内部的 glock 加锁顺序: 110 + 111 + 1. i_rwsem(如需要) 112 + 2. 重命名 glock(仅用于重命名) 113 + 3. Inode glock 114 + (父级优先于子级,同级 inode 按锁编号排序) 115 + 4. Rgrp glock(用于(反)分配操作) 116 + 5. 事务 glock(通过 gfs2_trans_begin,非读操作) 117 + 6. i_rw_mutex(如需要) 118 + 7. 页锁(始终最后,至关重要!) 119 + 120 + 每个 inode 对应两把 glock:一把管理 inode 本身(加锁顺 121 + 序如上),另一把(称为 iopen glock)结合 inode 的 122 + i_nlink 字段决定 inode 生命周期。inode 加锁基于单个 123 + inode,rgrp 加锁基于单个 rgrp。通常优先获取本地锁再获 124 + 取集群锁。 125 + 126 + Glock 统计 127 + ---------- 128 + 129 + 统计分为两类:超级块相关统计和单个 glock 相关统计。超级 130 + 块统计按每 CPU 执行以减少收集开销,并进一步按 glock 类 131 + 型细分。所有时间单位为纳秒。 132 + 133 + 超级块和 glock 统计收集相同信息。超级块时序统计为 glock 134 + 时序统计提供默认值,使新建 glock 具有合理的初始值。每个 135 + glock 的计数器在创建时初始化为零,当 glock 从内存移除时 136 + 统计丢失。 137 + 138 + 统计包含三组均值/方差对及两个计数器。均值/方差对为平滑 139 + 指数估计,算法与网络代码中的往返时间计算类似(参见《 140 + TCP/IP详解 卷1》第21.3节及《卷2》第25.10节)。与 TCP/IP 141 + 案例不同,此处均值/方差未缩放且单位为整数纳秒。 142 + 143 + 三组均值/方差对测量以下内容: 144 + 145 + 1. DLM 锁时间(非阻塞请求) 146 + 2. DLM 锁时间(阻塞请求) 147 + 3. 请求间隔时间(指向 DLM) 148 + 149 + 非阻塞请求指无论目标 DLM 锁处于何种状态均能立即完成的请求。 150 + 当前满足条件的请求包括:(a)锁当前状态为互斥(如锁降级)、 151 + (b)请求状态为空置或解锁(同样如锁降级)、或(c)设置"try lock" 152 + 标志的请求。其余锁请求均属阻塞请求。 153 + 154 + 两个计数器分别统计: 155 + 1. 锁请求总数(决定均值/方差计算的数据量) 156 + 2. glock 代码顶层的持有者排队数(通常远大于 DLM 锁请求数) 157 + 158 + 为什么收集这些统计数据?我们需深入分析时序参数的动因如下: 159 + 160 + 1. 更精准设置 glock "最短持有时间" 161 + 2. 快速识别性能问题 162 + 3. 改进资源组分配算法(基于锁等待时间而非盲目 "try lock") 163 + 164 + 因平滑更新的特性,采样量的阶跃变化需经 8 次采样(方差需 165 + 4 次)才能完全体现,解析结果时需审慎考虑。 166 + 167 + 通过锁请求完成时间和 glock 平均锁请求间隔时间,可计算节 168 + 点使用 glock 时长与集群共享时长的占比,对设置锁最短持有 169 + 时间至关重要。 170 + 171 + 我们已采取严谨措施,力求精准测量目标量值。任何测量系统均 172 + 存在误差,但我期望当前方案已达到合理精度极限。 173 + 174 + 超级块状态统计路径:: 175 + 176 + /sys/kernel/debug/gfs2/<fsname>/sbstats 177 + 178 + Glock 状态统计路径:: 179 + 180 + /sys/kernel/debug/gfs2/<fsname>/glstats 181 + 182 + (假设 debugfs 挂载于 /sys/kernel/debug,且 <fsname> 替 183 + 换为对应 GFS2 文件系统名) 184 + 185 + 输出缩写说明: 186 + 187 + ========= ============================================ 188 + srtt 非阻塞 DLM 请求的平滑往返时间 189 + srttvar srtt 的方差估计 190 + srttb (潜在)阻塞 DLM 请求的平滑往返时间 191 + srttvarb srttb 的方差估计 192 + sirt DLM 请求的平滑请求间隔时间 193 + sirtvar sirt 的方差估计 194 + dlm DLM 请求数(glstats 文件中的 dcnt) 195 + queue 排队的 glock 请求数(glstats 文件中的 qcnt) 196 + ========= ============================================ 197 + 198 + sbstats文件按glock类型(每种类型8行)和CPU核心(每CPU一列) 199 + 记录统计数据集。glstats文件则为每个glock提供统计集,其格式 200 + 与glocks文件类似,但所有时序统计量均采用均值/方差格式存储。 201 + 202 + gfs2_glock_lock_time 跟踪点实时输出目标 glock 的当前统计 203 + 值,并附带每次接收到的dlm响应附加信息: 204 + 205 + ====== ============ 206 + status DLM 请求状态 207 + flags DLM 请求标志 208 + tdiff 该请求的耗时 209 + ====== ============ 210 + 211 + (其余字段同上表)
+97
Documentation/translations/zh_CN/filesystems/gfs2-uevents.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + 3 + .. include:: ../disclaimer-zh_CN.rst 4 + 5 + :Original: Documentation/filesystems/gfs2-uevents.rst 6 + 7 + :翻译: 8 + 9 + 邵明寅 Shao Mingyin <shao.mingyin@zte.com.cn> 10 + 11 + :校译: 12 + 13 + 杨涛 yang tao <yang.tao172@zte.com.cn> 14 + 15 + =============== 16 + uevents 与 GFS2 17 + =============== 18 + 19 + 在 GFS2 文件系统的挂载生命周期内,会生成多个 uevent。 20 + 本文档解释了这些事件的含义及其用途(被 gfs2-utils 中的 gfs_controld 使用)。 21 + 22 + GFS2 uevents 列表 23 + ================= 24 + 25 + 1. ADD 26 + ------ 27 + 28 + ADD 事件发生在挂载时。它始终是新建文件系统生成的第一个 uevent。如果挂载成 29 + 功,随后会生成 ONLINE uevent。如果挂载失败,则随后会生成 REMOVE uevent。 30 + 31 + ADD uevent 包含两个环境变量:SPECTATOR=[0|1] 和 RDONLY=[0|1],分别用 32 + 于指定文件系统的观察者状态(一种未分配日志的只读挂载)和只读状态(已分配日志)。 33 + 34 + 2. ONLINE 35 + --------- 36 + 37 + ONLINE uevent 在成功挂载或重新挂载后生成。它具有与 ADD uevent 相同的环 38 + 境变量。ONLINE uevent 及其用于标识观察者和 RDONLY 状态的两个环境变量是较 39 + 新版本内核引入的功能(2.6.32-rc+ 及以上),旧版本内核不会生成此事件。 40 + 41 + 3. CHANGE 42 + --------- 43 + 44 + CHANGE uevent 在两种场景下使用。一是报告第一个节点成功挂载文件系统时 45 + (FIRSTMOUNT=Done)。这作为信号告知 gfs_controld,此时集群中其他节点可以 46 + 安全挂载该文件系统。 47 + 48 + 另一个 CHANGE uevent 用于通知文件系统某个日志的日志恢复已完成。它包含两个 49 + 环境变量:JID= 指定刚恢复的日志 ID,RECOVERY=[Done|Failed] 表示操作成 50 + 功与否。这些 uevent 会在每次日志恢复时生成,无论是在初始挂载过程中,还是 51 + gfs_controld 通过 /sys/fs/gfs2/<fsname>/lock_module/recovery 文件 52 + 请求特定日志恢复的结果。 53 + 54 + 由于早期版本的 gfs_controld 使用 CHANGE uevent 时未检查环境变量以确定状 55 + 态,若为其添加新功能,存在用户工具版本过旧导致集群故障的风险。因此,在新增用 56 + 于标识成功挂载或重新挂载的 uevent 时,选择了使用 ONLINE uevent。 57 + 58 + 4. OFFLINE 59 + ---------- 60 + 61 + OFFLINE uevent 仅在文件系统发生错误时生成,是 "withdraw" 机制的一部分。 62 + 当前该事件未提供具体错误信息,此问题有待修复。 63 + 64 + 5. REMOVE 65 + --------- 66 + 67 + REMOVE uevent 在挂载失败结束或卸载文件系统时生成。所有 REMOVE uevent 68 + 之前都至少存在同一文件系统的 ADD uevent。与其他 uevent 不同,它由内核的 69 + kobject 子系统自动生成。 70 + 71 + 72 + 所有 GFS2 uevents 的通用信息(uevent 环境变量) 73 + =============================================== 74 + 75 + 1. LOCKTABLE= 76 + -------------- 77 + 78 + LOCKTABLE 是一个字符串,其值来源于挂载命令行(locktable=)或 fstab 文件。 79 + 它用作文件系统标签,并为 lock_dlm 类型的挂载提供加入集群所需的信息。 80 + 81 + 2. LOCKPROTO= 82 + ------------- 83 + 84 + LOCKPROTO 是一个字符串,其值取决于挂载命令行或 fstab 中的设置。其值将是 85 + lock_nolock 或 lock_dlm。未来可能支持其他锁管理器。 86 + 87 + 3. JOURNALID= 88 + ------------- 89 + 90 + 如果文件系统正在使用日志(观察者挂载不分配日志),则所有 GFS2 uevent 中都 91 + 会包含此变量,其值为数字形式的日志 ID。 92 + 93 + 4. UUID= 94 + -------- 95 + 96 + 在较新版本的 gfs2-utils 中,mkfs.gfs2 会向文件系统超级块写入 UUID。若存 97 + 在 UUID,所有与该文件系统相关的 uevent 中均会包含此信息。
+57
Documentation/translations/zh_CN/filesystems/gfs2.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + 3 + .. include:: ../disclaimer-zh_CN.rst 4 + 5 + :Original: Documentation/filesystems/gfs2.rst 6 + 7 + :翻译: 8 + 9 + 邵明寅 Shao Mingyin <shao.mingyin@zte.com.cn> 10 + 11 + :校译: 12 + 13 + 杨涛 yang tao <yang.tao172@zte.com.cn> 14 + 15 + ===================================== 16 + 全局文件系统 2 (Global File System 2) 17 + ===================================== 18 + 19 + GFS2 是一个集群文件系统。它允许一组计算机同时使用在它们之间共享的块设备(通 20 + 过 FC、iSCSI、NBD 等)。GFS2 像本地文件系统一样读写块设备,但也使用一个锁 21 + 模块来让计算机协调它们的 I/O 操作,从而维护文件系统的一致性。GFS2 的出色特 22 + 性之一是完美一致性——在一台机器上对文件系统所做的更改会立即显示在集群中的所 23 + 有其他机器上。 24 + 25 + GFS2 使用可互换的节点间锁定机制,当前支持的机制有: 26 + 27 + lock_nolock 28 + - 允许将 GFS2 用作本地文件系统 29 + 30 + lock_dlm 31 + - 使用分布式锁管理器 (dlm) 进行节点间锁定。 32 + 该 dlm 位于 linux/fs/dlm/ 33 + 34 + lock_dlm 依赖于在上述 URL 中找到的用户空间集群管理系统。 35 + 36 + 若要将 GFS2 用作本地文件系统,则不需要外部集群系统,只需:: 37 + 38 + $ mkfs -t gfs2 -p lock_nolock -j 1 /dev/block_device 39 + $ mount -t gfs2 /dev/block_device /dir 40 + 41 + 在所有集群节点上都需要安装 gfs2-utils 软件包;对于 lock_dlm,您还需要按 42 + 照文档配置 dlm 和 corosync 用户空间工具。 43 + 44 + gfs2-utils 可在 https://pagure.io/gfs2-utils 找到。 45 + 46 + GFS2 在磁盘格式上与早期版本的 GFS 不兼容,但它已相当接近。 47 + 48 + 以下手册页 (man pages) 可在 gfs2-utils 中找到: 49 + 50 + ============ ============================================= 51 + fsck.gfs2 用于修复文件系统 52 + gfs2_grow 用于在线扩展文件系统 53 + gfs2_jadd 用于在线向文件系统添加日志 54 + tunegfs2 用于操作、检查和调优文件系统 55 + gfs2_convert 用于将 gfs 文件系统原地转换为 GFS2 56 + mkfs.gfs2 用于创建文件系统 57 + ============ =============================================
+16 -1
Documentation/translations/zh_CN/filesystems/index.rst
··· 15 15 文件系统(VFS)层以及基于其上的各种文件系统如何工作呈现给大家。当前\ 16 16 可以看到下面的内容。 17 17 18 + 核心 VFS 文档 19 + ============= 20 + 21 + 有关 VFS 层本身以及其算法工作方式的文档,请参阅这些手册。 22 + 23 + .. toctree:: 24 + :maxdepth: 1 25 + 26 + dnotify 27 + 18 28 文件系统 19 29 ======== 20 30 ··· 36 26 virtiofs 37 27 debugfs 38 28 tmpfs 39 - 29 + ubifs 30 + ubifs-authentication 31 + gfs2 32 + gfs2-uevents 33 + gfs2-glocks 34 + inotify
+80
Documentation/translations/zh_CN/filesystems/inotify.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + 3 + .. include:: ../disclaimer-zh_CN.rst 4 + 5 + :Original: Documentation/filesystems/inotify.rst 6 + 7 + :翻译: 8 + 9 + 王龙杰 Wang Longjie <wang.longjie1@zte.com.cn> 10 + 11 + ========================================== 12 + Inotify - 一个强大且简单的文件变更通知系统 13 + ========================================== 14 + 15 + 16 + 17 + 文档由 Robert Love <rml@novell.com> 于 2005 年 3 月 15 日开始撰写 18 + 19 + 文档由 Zhang Zhen <zhenzhang.zhang@huawei.com> 于 2015 年 1 月 4 日更新 20 + 21 + - 删除了已废弃的接口,关于用户接口请参考手册页。 22 + 23 + (i) 基本原理 24 + 25 + 问: 26 + 不将监控项与被监控对象打开的文件描述符(fd)绑定,这背后的设计决策是什么? 27 + 28 + 答: 29 + 监控项会与打开的 inotify 设备相关联,而非与打开的文件相关联。这解决了 dnotify 的主要问题: 30 + 保持文件打开会锁定文件,更糟的是,还会锁定挂载点。因此,dnotify 在带有可移动介质的桌面系统 31 + 上难以使用,因为介质将无法被卸载。监控文件不应要求文件处于打开状态。 32 + 33 + 问: 34 + 与每个监控项一个文件描述符的方式相比,采用每个实例一个文件描述符的设计决策是出于什么 35 + 考虑? 36 + 37 + 答: 38 + 每个监控项一个文件描述符会很快的消耗掉超出允许数量的文件描述符,其数量会超出实际可管理的范 39 + 围,也会超出 select() 能高效处理的范围。诚然,root 用户可以提高每个进程的文件描述符限制, 40 + 用户也可以使用 epoll,但同时要求这两者是不合理且多余的。一个监控项所消耗的内存比一个打开的文 41 + 件要少,因此将这两个数量空间分开是合理的。当前的设计正是用户空间开发者所期望的:用户只需初始 42 + 化一次 inotify,然后添加 n 个监控项,而这只需要一个文件描述符,无需调整文件描述符限制。初 43 + 始化 inotify 实例初始化两千次是很荒谬的。如果我们能够简洁地实现用户空间的偏好——而且我们 44 + 确实可以,idr 层让这类事情变得轻而易举——那么我们就应该这么做。 45 + 46 + 还有其他合理的理由。如果只有一个文件描述符,那就只需要在该描述符上阻塞,它对应着一个事件队列。 47 + 这个单一文件描述符会返回所有的监控事件以及任何可能的带外数据。而如果每个文件描述符都是一个独 48 + 立的监控项, 49 + 50 + - 将无法知晓事件的顺序。文件 foo 和文件 bar 上的事件会触发两个文件描述符上的 poll(), 51 + 但无法判断哪个事件先发生。而用单个队列就可以很容易的提供事件的顺序。这种顺序对现有的应用程 52 + 序(如 Beagle)至关重要。想象一下,如果“mv a b ; mv b a”这样的事件没有顺序会是什么 53 + 情况。 54 + 55 + - 我们将不得不维护 n 个文件描述符和 n 个带有状态的内部队列,而不是仅仅一个。这在 kernel 中 56 + 会混乱得多。单个线性队列是合理的数据结构。 57 + 58 + - 用户空间开发者更青睐当前的 API。例如,Beagle 的开发者们就很喜欢它。相信我,我问过他们。 59 + 这并不奇怪:谁会想通过 select 来管理以及阻塞在 1000 个文件描述符上呢? 60 + 61 + - 无法获取带外数据。 62 + 63 + - 1024 这个数量仍然太少。 ;-) 64 + 65 + 当要设计一个可扩展到数千个目录的文件变更通知系统时,处理数千个文件描述符似乎并不是合适的接口。 66 + 这太繁琐了。 67 + 68 + 此外,创建多个实例、处理多个队列以及相应的多个文件描述符是可行的。不必是每个进程对应一个文件描 69 + 述符;而是每个队列对应一个文件描述符,一个进程完全可能需要多个队列。 70 + 71 + 问: 72 + 为什么采用系统调用的方式? 73 + 74 + 答: 75 + 糟糕的用户空间接口是 dnotify 的第二大问题。信号对于文件通知来说是一种非常糟糕的接口。其实对 76 + 于其他任何事情,信号也都不是好的接口。从各个角度来看,理想的解决方案是基于文件描述符的,它允许 77 + 基本的文件 I/O 操作以及 poll/select 操作。获取文件描述符和管理监控项既可以通过设备文件来 78 + 实现,也可以通过一系列新的系统调用来实现。我们决定采用一系列系统调用,因为这是提供新的内核接口 79 + 的首选方法。两者之间唯一真正的区别在于,我们是想使用 open(2) 和 ioctl(2),还是想使用几 80 + 个新的系统调用。系统调用比 ioctl 更有优势。
+354
Documentation/translations/zh_CN/filesystems/ubifs-authentication.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + 3 + .. include:: ../disclaimer-zh_CN.rst 4 + 5 + :Original: Documentation/filesystems/ubifs-authentication.rst 6 + 7 + :翻译: 8 + 9 + 邵明寅 Shao Mingyin <shao.mingyin@zte.com.cn> 10 + 11 + :校译: 12 + 13 + 杨涛 yang tao <yang.tao172@zte.com.cn> 14 + 15 + ============= 16 + UBIFS认证支持 17 + ============= 18 + 19 + 引言 20 + ==== 21 + UBIFS 利用 fscrypt 框架为文件内容及文件名提供保密性。这能防止攻击者在单一 22 + 时间点读取文件系统内容的攻击行为。典型案例是智能手机丢失时,攻击者若没有文件 23 + 系统解密密钥则无法读取设备上的个人数据。 24 + 25 + 在现阶段,UBIFS 加密尚不能防止攻击者篡改文件系统内容后用户继续使用设备的攻 26 + 击场景。这种情况下,攻击者可任意修改文件系统内容而不被用户察觉。例如修改二 27 + 进制文件使其执行时触发恶意行为 [DMC-CBC-ATTACK]。由于 UBIFS 大部分文件 28 + 系统元数据以明文存储,使得文件替换和内容篡改变得相当容易。 29 + 30 + 其他全盘加密系统(如 dm-crypt)可以覆盖所有文件系统元数据,这类系统虽然能 31 + 增加这种攻击的难度,但特别是当攻击者能多次访问设备时,也有可能实现攻击。对于 32 + 基于 Linux 块 IO 层的 dm-crypt 等文件系统,可通过 dm-integrity 或 33 + dm-verity 子系统[DM-INTEGRITY, DM-VERITY]在块层实现完整数据认证,这些 34 + 功能也可与 dm-crypt 结合使用[CRYPTSETUP2]。 35 + 36 + 本文描述一种为 UBIFS 实现文件内容认证和完整元数据认证的方法。由于 UBIFS 37 + 使用 fscrypt 进行文件内容和文件名加密,认证系统可与 fscrypt 集成以利用密 38 + 钥派生等现有功能。但系统同时也应支持在不启用加密的情况下使用 UBIFS 认证。 39 + 40 + 41 + MTD, UBI & UBIFS 42 + ---------------- 43 + 在 Linux 中,MTD(内存技术设备)子系统提供访问裸闪存设备的统一接口。运行于 44 + MTD 之上的重要子系统是 UBI(无序块映像),它为闪存设备提供卷管理功能,类似 45 + 于块设备的 LVM。此外,UBI 还处理闪存特有的磨损均衡和透明 I/O 错误处理。 46 + UBI 向上层提供逻辑擦除块(LEB),并透明地映射到闪存的物理擦除块(PEB)。 47 + 48 + UBIFS 是运行于 UBI 之上的裸闪存文件系统。因此 UBI 处理磨损均衡和部分闪存 49 + 特性,而 UBIFS专注于可扩展性、性能和可恢复性。 50 + 51 + :: 52 + 53 + +------------+ +*******+ +-----------+ +-----+ 54 + | | * UBIFS * | UBI-BLOCK | | ... | 55 + | JFFS/JFFS2 | +*******+ +-----------+ +-----+ 56 + | | +-----------------------------+ +-----------+ +-----+ 57 + | | | UBI | | MTD-BLOCK | | ... | 58 + +------------+ +-----------------------------+ +-----------+ +-----+ 59 + +------------------------------------------------------------------+ 60 + | MEMORY TECHNOLOGY DEVICES (MTD) | 61 + +------------------------------------------------------------------+ 62 + +-----------------------------+ +--------------------------+ +-----+ 63 + | NAND DRIVERS | | NOR DRIVERS | | ... | 64 + +-----------------------------+ +--------------------------+ +-----+ 65 + 66 + 图1:处理裸闪存的 Linux 内核子系统 67 + 68 + 69 + 70 + UBIFS 内部维护多个持久化在闪存上的数据结构: 71 + 72 + - *索引*:存储在闪存上的 B+ 树,叶节点包含文件系统数据 73 + - *日志*:在更新闪存索引前收集文件系统变更的辅助数据结构,可减少闪存磨损 74 + - *树节点缓存(TNC)*:反映当前文件系统状态的内存 B+ 树,避免频繁读取闪存。 75 + 本质上是索引的内存表示,但包含额外属性 76 + - *LEB属性树(LPT)*:用于统计每个 UBI LEB 空闲空间的闪存B+树 77 + 78 + 本节后续将详细讨论UBIFS的闪存数据结构。因为 TNC 不直接持久化到闪存,其在此 79 + 处的重要性较低。更多 UBIFS 细节详见[UBIFS-WP]。 80 + 81 + 82 + UBIFS 索引与树节点缓存 83 + ~~~~~~~~~~~~~~~~~~~~~~ 84 + 85 + UBIFS 在闪存上的基础实体称为 *节点* ,包含多种类型。如存储文件内容块的数据 86 + 节点 87 + ( ``struct ubifs_data_node`` ),或表示 VFS 索引节点的 inode 节点 88 + ( ``struct ubifs_ino_node`` )。几乎所有节点共享包含节点类型、长度、序列 89 + 号等基础信息的通用头 90 + ( ``ubifs_ch`` )(见内核源码 ``fs/ubifs/ubifs-media.h`` )。LPT条目 91 + 和填充节点(用于填充 LEB 92 + 尾部不可用空间)等次要节点类型除外。 93 + 94 + 为避免每次变更重写整个 B+ 树,UBIFS 采用 *wandering tree* 实现:仅重写 95 + 变更节点,旧版本被标记废弃而非立即擦除。因此索引不固定存储于闪存某处,而是在 96 + 闪存上 *wanders* ,在 LEB 被 UBIFS 重用前,闪存上会存在废弃部分。为定位 97 + 最新索引,UBIFS 在 UBI LEB 1 存储称为 *主节点* 的特殊节点,始终指向最新 98 + UBIFS 索引根节点。为增强可恢复性,主节点还备份到 LEB 2。因此挂载 UBIFS 只 99 + 需读取 LEB 1 和 2 获取当前主节点,进而定位最新闪存索引。 100 + 101 + TNC 是闪存索引的内存表示,包含未持久化的运行时属性(如脏标记)。TNC 作为回 102 + 写式缓存,所有闪存索引修改都通过 TNC 完成。与其他缓存类似,TNC 无需将完整 103 + 索引全部加载到内存中,需要时从闪存读取部分内容。 *提交* 是更新闪存文件系统 104 + 结构(如索引)的 UBIFS 操作。每次提交时,标记为脏的 TNC 节点被写入闪存以更 105 + 新持久化索引。 106 + 107 + 108 + 日志 109 + ~~~~ 110 + 111 + 为避免闪存磨损,索引仅在满足特定条件(如 ``fsync(2)`` )时才持久化(提交)。 112 + 日志用于记录索引提交之间的所有变更(以 inode 节点、数据节点等形式)。挂载时 113 + 从闪存读取日志并重放到 TNC(此时 TNC 按需从闪存索引创建)。 114 + 115 + UBIFS 保留一组专用于日志的 LEB(称为 *日志区* )。日志区 LEB 数量在文件系 116 + 统创建时配置(使用 ``mkfs.ubifs`` )并存储于超级块节点。日志区仅含两类节 117 + 点: *引用节点* 和 *提交起始节点* 。执行索引提交时写入提交起始节点,每次日 118 + 志更新时写入引用节点。每个引用节点指向构成日志条目的其他节点( inode 节点、 119 + 数据节点等)在闪存上的位置,这些节点称为 *bud* ,描述包含数据的实际文件系 120 + 统变更。 121 + 122 + 日志区以环形缓冲区维护。当日志将满时触发提交操作,同时写入提交起始节点。因此 123 + 挂载时 UBIFS 查找最新提交起始节点,仅重放其后的引用节点。提交起始节点前的引 124 + 用节点将被忽略(因其已属于闪存索引)。 125 + 126 + 写入日志条目时,UBIFS 首先确保有足够空间写入引用节点和该条目的 bud。然后先 127 + 写引用节点,再写描述文件变更的 bud。在日志重放阶段,UBIFS 会记录每个参考节 128 + 点,并检查其引用的 LEB位置以定位 buds。若这些数据损坏或丢失,UBIFS 会尝试 129 + 通过重新读取 LEB 来恢复,但仅针对日志中最后引用的 LEB,因为只有它可能因断 130 + 电而损坏。若恢复失败,UBIFS 将拒绝挂载。对于其他 LEB 的错误,UBIFS 会直接 131 + 终止挂载操作。 132 + 133 + :: 134 + 135 + | ---- LOG AREA ---- | ---------- MAIN AREA ------------ | 136 + 137 + -----+------+-----+--------+---- ------+-----+-----+--------------- 138 + \ | | | | / / | | | \ 139 + / CS | REF | REF | | \ \ DENT | INO | INO | / 140 + \ | | | | / / | | | \ 141 + ----+------+-----+--------+--- -------+-----+-----+---------------- 142 + | | ^ ^ 143 + | | | | 144 + +------------------------+ | 145 + | | 146 + +-------------------------------+ 147 + 148 + 149 + 图2:包含提交起始节点(CS)和引用节点(REF)的日志区闪存布局,引用节点指向含 150 + bud 的主区 151 + 152 + 153 + LEB属性树/表 154 + ~~~~~~~~~~~~ 155 + 156 + LEB 属性树用于存储每个 LEB 的信息,包括 LEB 类型、LEB 上的空闲空间和 157 + *脏空间* (旧空间,废弃内容) [1]_ 的数量。因为 UBIFS 从不在单个 LEB 混 158 + 合存储索引节点和数据节点,所以 LEB 的类型至关重要,每个 LEB 都有特定用途, 159 + 这对空闲空间计算非常有帮助。详见[UBIFS-WP]。 160 + 161 + LEB 属性树也是 B+ 树,但远小于索引。因为其体积小,所以每次提交时都整块写入, 162 + 保存 LPT 是原子操作。 163 + 164 + 165 + .. [1] 由于LEB只能追加写入不能覆盖,空闲空间(即 LEB 剩余可写空间)与废弃 166 + 内容(先前写入但未擦除前不能覆盖)存在区别。 167 + 168 + 169 + UBIFS认证 170 + ========= 171 + 172 + 本章介绍UBIFS认证,使UBIFS能验证闪存上元数据和文件内容的真实性与完整性。 173 + 174 + 175 + 威胁模型 176 + -------- 177 + 178 + UBIFS 认证可检测离线数据篡改。虽然不能防止篡改,但是能让(可信)代码检查闪 179 + 存文件内容和文件系统元数据的完整性与真实性,也能检查文件内容被替换的攻击。 180 + 181 + UBIFS 认证不防护全闪存内容回滚(攻击者可转储闪存内容并在后期还原)。也不防护 182 + 单个索引提交的部分回滚(攻击者能部分撤销变更)。这是因为 UBIFS 不立即覆盖索 183 + 引树或日志的旧版本,而是标记为废弃,稍后由垃圾回收擦除。攻击者可擦除当前树部 184 + 分内容并还原闪存上尚未擦除的旧版本。因每次提交总会写入索引根节点和主节点的新 185 + 版本而不覆盖旧版本,UBI 的磨损均衡操作(将内容从物理擦除块复制到另一擦除块 186 + 且非原子擦除原块)进一步助长此问题。 187 + 188 + UBIFS 认证不覆盖认证密钥提供后攻击者在设备执行代码的攻击,需结合安全启动和 189 + 可信启动等措施确保设备仅执行可信代码。 190 + 191 + 192 + 认证 193 + ---- 194 + 195 + 为完全信任从闪存读取的数据,所有存储在闪存的 UBIFS 数据结构均需认证: 196 + - 包含文件内容、扩展属性、文件长度等元数据的索引 197 + - 通过记录文件系统变更来包含文件内容和元数据的日志 198 + - 存储 UBIFS 用于空闲空间统计的 UBI LEB 元数据的 LPT 199 + 200 + 201 + 索引认证 202 + ~~~~~~~~ 203 + 204 + 借助 *wandering tree* 概念,UBIFS 仅更新和持久化从叶节点到根节点的变更 205 + 部分。这允许用子节点哈希增强索引树节点。最终索引基本成为 Merkle 树:因索引 206 + 叶节点含实际文件系统数据,其父索引节点的哈希覆盖所有文件内容和元数据。文件 207 + 变更时,UBIFS 索引从叶节点到根节点(含主节点)相应更新,此过程可挂钩以同步 208 + 重新计算各变更节点的哈希。读取文件时,UBIFS 可从叶节点到根节点逐级验证哈希 209 + 确保节点完整性。 210 + 211 + 为确保整个索引真实性,UBIFS 主节点存储基于密钥的哈希(HMAC),覆盖自身内容及 212 + 索引树根节点哈希。如前所述,主节点在索引持久化时(即索引提交时)总会写入闪存。 213 + 214 + 此方法仅修改 UBIFS 索引节点和主节点以包含哈希,其他类型节点保持不变,减少了 215 + 对 UBIFS 用户(如嵌入式设备)宝贵的存储开销。 216 + 217 + :: 218 + 219 + +---------------+ 220 + | Master Node | 221 + | (hash) | 222 + +---------------+ 223 + | 224 + v 225 + +-------------------+ 226 + | Index Node #1 | 227 + | | 228 + | branch0 branchn | 229 + | (hash) (hash) | 230 + +-------------------+ 231 + | ... | (fanout: 8) 232 + | | 233 + +-------+ +------+ 234 + | | 235 + v v 236 + +-------------------+ +-------------------+ 237 + | Index Node #2 | | Index Node #3 | 238 + | | | | 239 + | branch0 branchn | | branch0 branchn | 240 + | (hash) (hash) | | (hash) (hash) | 241 + +-------------------+ +-------------------+ 242 + | ... | ... | 243 + v v v 244 + +-----------+ +----------+ +-----------+ 245 + | Data Node | | INO Node | | DENT Node | 246 + +-----------+ +----------+ +-----------+ 247 + 248 + 249 + 图3:索引节点哈希与主节点 HMAC 的覆盖范围 250 + 251 + 252 + 253 + 健壮性性和断电安全性的关键在于以原子操作持久化哈希值与文件内容。UBIFS 现有 254 + 的变更节点持久化机制专为此设计,能够确保断电时安全恢复。为索引节点添加哈希值 255 + 不会改变该机制,因为每个哈希值都与其对应节点以原子操作同步持久化。 256 + 257 + 258 + 日志认证 259 + ~~~~~~~~ 260 + 261 + 日志也需要认证。因为日志持续写入,必须频繁地添加认证信息以确保断电时未认证数 262 + 据量可控。方法是从提交起始节点开始,对先前引用节点、当前引用节点和 bud 节点 263 + 创建连续哈希链。适时地在bud节点间插入认证节点,这种新节点类型包含哈希链当前 264 + 状态的 HMAC。因此日志可认证至最后一个认证节点。日志尾部无认证节点的部分无法 265 + 认证,在日志重放时跳过。 266 + 267 + 日志认证示意图如下:: 268 + 269 + ,,,,,,,, 270 + ,......,........................................... 271 + ,. CS , hash1.----. hash2.----. 272 + ,. | , . |hmac . |hmac 273 + ,. v , . v . v 274 + ,.REF#0,-> bud -> bud -> bud.-> auth -> bud -> bud.-> auth ... 275 + ,..|...,........................................... 276 + , | , 277 + , | ,,,,,,,,,,,,,,, 278 + . | hash3,----. 279 + , | , |hmac 280 + , v , v 281 + , REF#1 -> bud -> bud,-> auth ... 282 + ,,,|,,,,,,,,,,,,,,,,,, 283 + v 284 + REF#2 -> ... 285 + | 286 + V 287 + ... 288 + 289 + 因为哈希值包含引用节点,攻击者无法重排或跳过日志头重放,仅能移除日志尾部的 290 + bud 节点或引用节点,最大限度将文件系统回退至上次提交。 291 + 292 + 日志区位置存储于主节点。因为主节点通过 HMAC 认证,所以未经检测无法篡改。日 293 + 志区大小在文件系统创建时由 `mkfs.ubifs` 指定并存储于超级块节点。为避免篡 294 + 改此值及其他参数,超级块结构添加 HMAC。超级块节点存储在 LEB 0,仅在功能标 295 + 志等变更时修改,文件变更时不修改。 296 + 297 + 298 + LPT认证 299 + ~~~~~~~ 300 + 301 + LPT 根节点在闪存上的位置存储于 UBIFS 主节点。因为 LPT 每次提交时都以原子 302 + 操作写入和读取,无需单独认证树节点。通过主节点存储的简单哈希保护完整 LPT 303 + 即可。因为主节点自身已认证,通过验证主节点真实性并比对存储的 LTP 哈希与读 304 + 取的闪存 LPT 计算哈希值,即可验证 LPT 真实性。 305 + 306 + 307 + 密钥管理 308 + -------- 309 + 310 + 为了简化实现,UBIFS 认证使用单一密钥计算超级块、主节点、提交起始节点和引用 311 + 节点的 HMAC。创建文件系统(`mkfs.ubifs`) 时需提供此密钥以认证超级块节点。 312 + 挂载文件系统时也需此密钥验证认证节点并为变更生成新 HMAC。 313 + 314 + UBIFS 认证旨在与 UBIFS 加密(fscrypt)协同工作以提供保密性和真实性。因为 315 + UBIFS 加密采用基于目录的差异化加密策略,可能存在多个 fscrypt 主密钥甚至未 316 + 加密目录。而 UBIFS 认证采用全有或全无方式,要么认证整个文件系统要么完全不 317 + 认证。基于此特性,且为确保认证机制可独立于加密功能使用,UBIFS 认证不与 318 + fscrypt 共享主密钥,而是维护独立的认证专用密钥。 319 + 320 + 提供认证密钥的API尚未定义,但可通过类似 fscrypt 的用户空间密钥环提供。需注 321 + 意当前 fscrypt 方案存在缺陷,用户空间 API 终将变更[FSCRYPT-POLICY2]。 322 + 323 + 用户仍可通过用户空间提供单一口令或密钥覆盖 UBIFS 认证与加密。相应用户空间工 324 + 具可解决此问题:除派生的 fscrypt 加密主密钥外,额外派生认证密钥。 325 + 326 + 为检查挂载时密钥可用性,UBIFS 超级块节点将额外存储认证密钥的哈希。此方法类 327 + 似 fscrypt 加密策略 v2 提出的方法[FSCRYPT-POLICY2]。 328 + 329 + 330 + 未来扩展 331 + ======== 332 + 333 + 特定场景下,若供应商需要向客户提供认证文件系统镜像,应该能在不共享 UBIFS 认 334 + 证密钥的前提下实现。方法是在每个 HMAC 外额外存储数字签名,供应商随文件系统 335 + 镜像分发公钥。若该文件系统后续需要修改,若后续需修改该文件系统,UBIFS 可在 336 + 首次挂载时将全部数字签名替换为 HMAC,其处理逻辑与 IMA/EVM 子系统应对此类情 337 + 况的方式类似。此时,HMAC 密钥需按常规方式预先提供。 338 + 339 + 340 + 参考 341 + ==== 342 + 343 + [CRYPTSETUP2] https://www.saout.de/pipermail/dm-crypt/2017-November/005745.html 344 + 345 + [DMC-CBC-ATTACK] https://www.jakoblell.com/blog/2013/12/22/practical-malleability-attack-against-cbc-en 346 + crypted-luks-partitions/ 347 + 348 + [DM-INTEGRITY] https://www.kernel.org/doc/Documentation/device-mapper/dm-integrity.rst 349 + 350 + [DM-VERITY] https://www.kernel.org/doc/Documentation/device-mapper/verity.rst 351 + 352 + [FSCRYPT-POLICY2] https://www.spinics.net/lists/linux-ext4/msg58710.html 353 + 354 + [UBIFS-WP] http://www.linux-mtd.infradead.org/doc/ubifs_whitepaper.pdf
+114
Documentation/translations/zh_CN/filesystems/ubifs.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + 3 + .. include:: ../disclaimer-zh_CN.rst 4 + 5 + :Original: Documentation/filesystems/ubifs.rst 6 + 7 + :翻译: 8 + 9 + 邵明寅 Shao Mingyin <shao.mingyin@zte.com.cn> 10 + 11 + :校译: 12 + 13 + 杨涛 yang tao <yang.tao172@zte.com.cn> 14 + 15 + ============ 16 + UBI 文件系统 17 + ============ 18 + 19 + 简介 20 + ==== 21 + 22 + UBIFS 文件系统全称为 UBI 文件系统(UBI File System)。UBI 代表无序块镜 23 + 像(Unsorted Block Images)。UBIFS 是一种闪存文件系统,这意味着它专为闪 24 + 存设备设计。需要理解的是,UBIFS与 Linux 中任何传统文件系统(如 Ext2、 25 + XFS、JFS 等)完全不同。UBIFS 代表一类特殊的文件系统,它们工作在 MTD 设备 26 + 而非块设备上。该类别的另一个 Linux 文件系统是 JFFS2。 27 + 28 + 为更清晰说明,以下是 MTD 设备与块设备的简要比较: 29 + 30 + 1. MTD 设备代表闪存设备,由较大尺寸的擦除块组成,通常约 128KiB。块设备由 31 + 小块组成,通常 512 字节。 32 + 2. MTD 设备支持 3 种主要操作:在擦除块内偏移位置读取、在擦除块内偏移位置写 33 + 入、以及擦除整个擦除块。块设备支持 2 种主要操作:读取整个块和写入整个块。 34 + 3. 整个擦除块必须先擦除才能重写内容。块可直接重写。 35 + 4. 擦除块在经历一定次数的擦写周期后会磨损,通常 SLC NAND 和 NOR 闪存为 36 + 100K-1G 次,MLC NAND 闪存为 1K-10K 次。块设备不具备磨损特性。 37 + 5. 擦除块可能损坏(仅限 NAND 闪存),软件需处理此问题。硬盘上的块通常不会损 38 + 坏,因为硬件有坏块替换机制(至少现代 LBA 硬盘如此)。 39 + 40 + 这充分说明了 UBIFS 与传统文件系统的本质差异。 41 + 42 + UBIFS 工作在 UBI 层之上。UBI 是一个独立的软件层(位于 drivers/mtd/ubi), 43 + 本质上是卷管理和磨损均衡层。它提供称为 UBI 卷的高级抽象,比 MTD 设备更上层。 44 + UBI 设备的编程模型与 MTD 设备非常相似,仍由大容量擦除块组成,支持读/写/擦 45 + 除操作,但 UBI 设备消除了磨损和坏块限制(上述列表的第 4 和第 5 项)。 46 + 47 + 某种意义上,UBIFS 是 JFFS2 文件系统的下一代产品,但它与 JFFS2 差异巨大且 48 + 不兼容。主要区别如下: 49 + 50 + * JFFS2 工作在 MTD 设备之上,UBIFS 依赖于 UBI 并工作在 UBI 卷之上。 51 + * JFFS2 没有介质索引,需在挂载时构建索引,这要求全介质扫描。UBIFS 在闪存 52 + 介质上维护文件系统索引信息,无需全介质扫描,因此挂载速度远快于 JFFS2。 53 + * JFFS2 是直写(write-through)文件系统,而 UBIFS 支持回写 54 + (write-back),这使得 UBIFS 写入速度快得多。 55 + 56 + 与 JFFS2 类似,UBIFS 支持实时压缩,可将大量数据存入闪存。 57 + 58 + 与 JFFS2 类似,UBIFS 能容忍异常重启和断电。它不需要类似 fsck.ext2 的工 59 + 具。UBIFS 会自动重放日志并从崩溃中恢复,确保闪存数据结构的一致性。 60 + 61 + UBIFS 具有对数级扩展性(其使用的数据结构多为树形),因此挂载时间和内存消耗不 62 + 像 JFFS2 那样线性依赖于闪存容量。这是因为 UBIFS 在闪存介质上维护文件系统 63 + 索引。但 UBIFS 依赖于线性扩展的 UBI 层,因此整体 UBI/UBIFS 栈仍是线性扩 64 + 展。尽管如此,UBIFS/UBI 的扩展性仍显著优于 JFFS2。 65 + 66 + UBIFS 开发者认为,未来可开发同样具备对数级扩展性的 UBI2。UBI2 将支持与 67 + UBI 相同的 API,但二进制不兼容。因此 UBIFS 无需修改即可使用 UBI2。 68 + 69 + 挂载选项 70 + ======== 71 + 72 + (*) 表示默认选项。 73 + 74 + ==================== ======================================================= 75 + bulk_read 批量读取以利用闪存介质的顺序读取加速特性 76 + no_bulk_read (*) 禁用批量读取 77 + no_chk_data_crc (*) 跳过数据节点的 CRC 校验以提高读取性能。 仅在闪存 78 + 介质高度可靠时使用此选项。 此选项可能导致文件内容损坏无法被 79 + 察觉。 80 + chk_data_crc 强制校验数据节点的 CRC 81 + compr=none 覆盖默认压缩器,设置为"none" 82 + compr=lzo 覆盖默认压缩器,设置为"LZO" 83 + compr=zlib 覆盖默认压缩器,设置为"zlib" 84 + auth_key= 指定用于文件系统身份验证的密钥。 85 + 使用此选项将强制启用身份验证。 86 + 传入的密钥必须存在于内核密钥环中, 且类型必须是'logon' 87 + auth_hash_name= 用于身份验证的哈希算法。同时用于哈希计算和 HMAC 88 + 生成。典型值包括"sha256"或"sha512" 89 + ==================== ======================================================= 90 + 91 + 快速使用指南 92 + ============ 93 + 94 + 挂载的 UBI 卷通过 "ubiX_Y" 或 "ubiX:NAME" 语法指定,其中 "X" 是 UBI 95 + 设备编号,"Y" 是 UBI 卷编号,"NAME" 是 UBI 卷名称。 96 + 97 + 将 UBI 设备 0 的卷 0 挂载到 /mnt/ubifs:: 98 + 99 + $ mount -t ubifs ubi0_0 /mnt/ubifs 100 + 101 + 将 UBI 设备 0 的 "rootfs" 卷挂载到 /mnt/ubifs("rootfs" 是卷名):: 102 + 103 + $ mount -t ubifs ubi0:rootfs /mnt/ubifs 104 + 105 + 以下是内核启动参数的示例,用于将 mtd0 附加到 UBI 并挂载 "rootfs" 卷: 106 + ubi.mtd=0 root=ubi0:rootfs rootfstype=ubifs 107 + 108 + 参考资料 109 + ======== 110 + 111 + UBIFS 文档及常见问题解答/操作指南请访问 MTD 官网: 112 + 113 + - http://www.linux-mtd.infradead.org/doc/ubifs.html 114 + - http://www.linux-mtd.infradead.org/faq/ubifs.html
+176
Documentation/translations/zh_CN/networking/generic-hdlc.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + 3 + .. include:: ../disclaimer-zh_CN.rst 4 + 5 + :Original: Documentation/networking/generic-hdlc.rst 6 + 7 + :翻译: 8 + 9 + 孙渔喜 Sun yuxi <sun.yuxi@zte.com.cn> 10 + 11 + ========== 12 + 通用HDLC层 13 + ========== 14 + 15 + Krzysztof Halasa <khc@pm.waw.pl> 16 + 17 + 18 + 通用HDLC层当前支持以下协议: 19 + 20 + 1. 帧中继(支持ANSI、CCITT、Cisco及无LMI模式) 21 + 22 + - 常规(路由)接口和以太网桥接(以太网设备仿真)接口 23 + 可共享同一条PVC。 24 + - 支持ARP(内核暂不支持InARP,但可通过实验性用户空间守护程序实现, 25 + 下载地址:http://www.kernel.org/pub/linux/utils/net/hdlc/)。 26 + 27 + 2. 原始HDLC —— 支持IP(IPv4)接口或以太网设备仿真 28 + 3. Cisco HDLC 29 + 4. PPP 30 + 5. X.25(使用X.25协议栈) 31 + 32 + 通用HDLC仅作为协议驱动 - 必须配合具体硬件的底层驱动 33 + 才能运行。 34 + 35 + 以太网设备仿真(使用HDLC或帧中继PVC)兼容IEEE 802.1Q(VLAN)和 36 + 802.1D(以太网桥接)。 37 + 38 + 39 + 请确保已加载 hdlc.o 和硬件驱动程序。系统将为每个WAN端口创建一个 40 + "hdlc"网络设备(如hdlc0等)。您需要使用"sethdlc"工具,可从以下 41 + 地址获取: 42 + 43 + http://www.kernel.org/pub/linux/utils/net/hdlc/ 44 + 45 + 编译 sethdlc.c 工具:: 46 + 47 + gcc -O2 -Wall -o sethdlc sethdlc.c 48 + 49 + 请确保使用与您内核版本匹配的 sethdlc 工具。 50 + 51 + 使用 sethdlc 工具设置物理接口、时钟频率、HDLC 模式, 52 + 若使用帧中继还需添加所需的 PVC。 53 + 通常您需要执行类似以下命令:: 54 + 55 + sethdlc hdlc0 clock int rate 128000 56 + sethdlc hdlc0 cisco interval 10 timeout 25 57 + 58 + 或:: 59 + 60 + sethdlc hdlc0 rs232 clock ext 61 + sethdlc hdlc0 fr lmi ansi 62 + sethdlc hdlc0 create 99 63 + ifconfig hdlc0 up 64 + ifconfig pvc0 localIP pointopoint remoteIP 65 + 66 + 在帧中继模式下,请先启用主hdlc设备(不分配IP地址),再 67 + 使用pvc设备。 68 + 69 + 70 + 接口设置选项: 71 + 72 + * v35 | rs232 | x21 | t1 | e1 73 + - 当网卡支持软件可选接口时,可为指定端口设置物理接口 74 + loopback 75 + - 启用硬件环回(仅用于测试) 76 + * clock ext 77 + - RX与TX时钟均使用外部时钟源 78 + * clock int 79 + - RX与TX时钟均使用内部时钟源 80 + * clock txint 81 + - RX时钟使用外部时钟源,TX时钟使用内部时钟源 82 + * clock txfromrx 83 + - RX时钟使用外部时钟源,TX时钟从RX时钟派生 84 + * rate 85 + - 设置时钟速率(仅适用于"int"或"txint"时钟模式) 86 + 87 + 88 + 设置协议选项: 89 + 90 + * hdlc - 设置原始HDLC模式(仅支持IP协议) 91 + 92 + nrz / nrzi / fm-mark / fm-space / manchester - 传输编码选项 93 + 94 + no-parity / crc16 / crc16-pr0 (预设零值的CRC16) / crc32-itu 95 + 96 + crc16-itu (使用ITU-T多项式的CRC16) / crc16-itu-pr0 - 校验方式选项 97 + 98 + * hdlc-eth - 使用HDLC进行以太网设备仿真. 校验和编码方式同上 99 + as above. 100 + 101 + * cisco - 设置Cisco HDLC模式(支持IP、IPv6和IPX协议) 102 + 103 + interval - 保活数据包发送间隔(秒) 104 + 105 + timeout - 未收到保活数据包的超时时间(秒),超过此时长将判定 106 + 链路断开 107 + 108 + * ppp - 设置同步PPP模式 109 + 110 + * x25 - 设置X.25模式 111 + 112 + * fr - 帧中继模式 113 + 114 + lmi ansi / ccitt / cisco / none - LMI(链路管理)类型 115 + 116 + dce - 将帧中继设置为DCE(网络侧)LMI模式(默认为DTE用户侧)。 117 + 118 + 此设置与时钟无关! 119 + 120 + - t391 - 链路完整性验证轮询定时器(秒)- 用户侧 121 + - t392 - 轮询验证定时器(秒)- 网络侧 122 + - n391 - 全状态轮询计数器 - 用户侧 123 + - n392 - 错误阈值 - 用户侧和网络侧共用 124 + - n393 - 监控事件计数 - 用户侧和网络侧共用 125 + 126 + 帧中继专用命令: 127 + 128 + * create n | delete n - 添加/删除DLCI编号为n的PVC接口。 129 + 新创建的接口将命名为pvc0、pvc1等。 130 + 131 + * create ether n | delete ether n - 添加/删除用于以太网 132 + 桥接帧的设备设备将命名为pvceth0、pvceth1等。 133 + 134 + 135 + 136 + 137 + 板卡特定问题 138 + ------------ 139 + 140 + n2.o 和 c101.o 驱动模块需要参数才能工作:: 141 + 142 + insmod n2 hw=io,irq,ram,ports[:io,irq,...] 143 + 144 + 示例:: 145 + 146 + insmod n2 hw=0x300,10,0xD0000,01 147 + 148 + 或:: 149 + 150 + insmod c101 hw=irq,ram[:irq,...] 151 + 152 + 示例:: 153 + 154 + insmod c101 hw=9,0xdc000 155 + 156 + 若直接编译进内核,这些驱动需要通过内核(命令行)参数配置:: 157 + 158 + n2.hw=io,irq,ram,ports:... 159 + 160 + 或:: 161 + 162 + c101.hw=irq,ram:... 163 + 164 + 165 + 166 + 若您的N2、C101或PLX200SYN板卡出现问题,可通过"private" 167 + 命令查看端口数据包描述符环(显示在内核日志中) 168 + 169 + sethdlc hdlc0 private 170 + 171 + 硬件驱动需使用#define DEBUG_RINGS编译选项构建。 172 + 在提交错误报告时附上这些信息将很有帮助。如在使用过程中遇 173 + 到任何问题,请随时告知。 174 + 175 + 获取补丁和其他信息,请访问: 176 + <http://www.kernel.org/pub/linux/utils/net/hdlc/>.
+3 -4
Documentation/translations/zh_CN/networking/index.rst
··· 27 27 xfrm_proc 28 28 netmem 29 29 alias 30 + mptcp-sysctl 31 + generic-hdlc 32 + timestamping 30 33 31 34 Todolist: 32 35 ··· 79 76 * eql 80 77 * fib_trie 81 78 * filter 82 - * generic-hdlc 83 79 * generic_netlink 84 80 * netlink_spec/index 85 81 * gen_stats ··· 98 96 * mctp 99 97 * mpls-sysctl 100 98 * mptcp 101 - * mptcp-sysctl 102 99 * multiqueue 103 100 * multi-pf-netdev 104 101 * net_cachelines/index ··· 127 126 * sctp 128 127 * secid 129 128 * seg6-sysctl 130 - * skbuff 131 129 * smc-sysctl 132 130 * sriov 133 131 * statistics ··· 138 138 * tcp_ao 139 139 * tcp-thin 140 140 * team 141 - * timestamping 142 141 * tipc 143 142 * tproxy 144 143 * tuntap
+139
Documentation/translations/zh_CN/networking/mptcp-sysctl.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + 3 + .. include:: ../disclaimer-zh_CN.rst 4 + 5 + :Original: Documentation/networking/mptcp-sysctl.rst 6 + 7 + :翻译: 8 + 9 + 孙渔喜 Sun yuxi <sun.yuxi@zte.com.cn> 10 + 11 + ================ 12 + MPTCP Sysfs 变量 13 + ================ 14 + 15 + /proc/sys/net/mptcp/* Variables 16 + =============================== 17 + 18 + add_addr_timeout - INTEGER (秒) 19 + 设置ADD_ADDR控制消息的重传超时时间。当MPTCP对端未确认 20 + 先前的ADD_ADDR消息时,将在该超时时间后重新发送。 21 + 22 + 默认值与TCP_RTO_MAX相同。此为每个命名空间的sysctl参数。 23 + 24 + 默认值:120 25 + 26 + allow_join_initial_addr_port - BOOLEAN 27 + 控制是否允许对端向初始子流使用的IP地址和端口号发送加入 28 + 请求(1表示允许)。此参数会设置连接时发送给对端的标志位, 29 + 并决定是否接受此类加入请求。 30 + 31 + 通过ADD_ADDR通告的地址不受此参数影响。 32 + 33 + 此为每个命名空间的sysctl参数。 34 + 35 + 默认值:1 36 + 37 + available_path_managers - STRING 38 + 显示已注册的可用路径管理器选项。可能有更多路径管理器可用 39 + 但尚未加载。 40 + 41 + available_schedulers - STRING 42 + 显示已注册的可用调度器选项。可能有更多数据包调度器可用 43 + 但尚未加载。 44 + 45 + blackhole_timeout - INTEGER (秒) 46 + 当发生MPTCP防火墙黑洞问题时,初始禁用活跃MPTCP套接字上MPTCP 47 + 功能的时间(秒)。如果在重新启用MPTCP后立即检测到更多黑洞问题, 48 + 此时间段将呈指数增长;当黑洞问题消失时,将重置为初始值。 49 + 50 + 设置为0可禁用黑洞检测功能。此为每个命名空间的sysctl参数。 51 + 52 + 默认值:3600 53 + 54 + checksum_enabled - BOOLEAN 55 + 控制是否启用DSS校验和功能。 56 + 57 + 当值为非零时可启用DSS校验和。此为每个命名空间的sysctl参数。 58 + 59 + 默认值:0 60 + 61 + close_timeout - INTEGER (seconds) 62 + 设置"先断后连"超时时间:在未调用close或shutdown系统调用时, 63 + MPTCP套接字将在最后一个子流移除后保持当前状态达到该时长,才 64 + 会转为TCP_CLOSE状态。 65 + 66 + 默认值与TCP_TIMEWAIT_LEN相同。此为每个命名空间的sysctl参数。 67 + 68 + 默认值:60 69 + 70 + enabled - BOOLEAN 71 + 控制是否允许创建MPTCP套接字。 72 + 73 + 当值为1时允许创建MPTCP套接字。此为每个命名空间的sysctl参数。 74 + 75 + 默认值:1(启用) 76 + 77 + path_manager - STRING 78 + 设置用于每个新MPTCP套接字的默认路径管理器名称。内核路径管理将 79 + 根据通过MPTCP netlink API配置的每个命名空间值来控制子流连接 80 + 和地址通告。用户空间路径管理将每个MPTCP连接的子流连接决策和地 81 + 址通告交由特权用户空间程序控制,代价是需要更多netlink流量来 82 + 传播所有相关事件和命令。 83 + 84 + 此为每个命名空间的sysctl参数。 85 + 86 + * "kernel" - 内核路径管理器 87 + * "userspace" - 用户空间路径管理器 88 + 89 + 默认值:"kernel" 90 + 91 + pm_type - INTEGER 92 + 设置用于每个新MPTCP套接字的默认路径管理器类型。内核路径管理将 93 + 根据通过MPTCP netlink API配置的每个命名空间值来控制子流连接 94 + 和地址通告。用户空间路径管理将每个MPTCP连接的子流连接决策和地 95 + 址通告交由特权用户空间程序控制,代价是需要更多netlink流量来 96 + 传播所有相关事件和命令。 97 + 98 + 此为每个命名空间的sysctl参数。 99 + 100 + 自v6.15起已弃用,请改用path_manager参数。 101 + 102 + * 0 - 内核路径管理器 103 + * 1 - 用户空间路径管理器 104 + 105 + 默认值:0 106 + 107 + scheduler - STRING 108 + 选择所需的调度器类型。 109 + 110 + 支持选择不同的数据包调度器。此为每个命名空间的sysctl参数。 111 + 112 + 默认值:"default" 113 + 114 + stale_loss_cnt - INTEGER 115 + 用于判定子流失效(stale)的MPTCP层重传间隔次数阈值。当指定 116 + 子流在连续多个重传间隔内既无数据传输又有待处理数据时,将被标 117 + 记为失效状态。失效子流将被数据包调度器忽略。 118 + 设置较低的stale_loss_cnt值可实现快速主备切换,较高的值则能 119 + 最大化边缘场景(如高误码率链路或对端暂停数据处理等异常情况) 120 + 的链路利用率。 121 + 122 + 此为每个命名空间的sysctl参数。 123 + 124 + 默认值:4 125 + 126 + syn_retrans_before_tcp_fallback - INTEGER 127 + 在回退到 TCP(即丢弃 MPTCP 选项)之前,SYN + MP_CAPABLE 128 + 报文的重传次数。换句话说,如果所有报文在传输过程中都被丢弃, 129 + 那么将会: 130 + 131 + * 首次SYN携带MPTCP支持选项 132 + * 按本参数值重传携带MPTCP选项的SYN包 133 + * 后续重传将不再携带MPTCP支持选项 134 + 135 + 0 表示首次重传即丢弃MPTCP选项。 136 + >=128 表示所有SYN重传均保留MPTCP选项设置过低的值可能增加 137 + MPTCP黑洞误判几率。此为每个命名空间的sysctl参数。 138 + 139 + 默认值:2
+674
Documentation/translations/zh_CN/networking/timestamping.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + 3 + .. include:: ../disclaimer-zh_CN.rst 4 + 5 + :Original: Documentation/networking/timestamping.rst 6 + 7 + :翻译: 8 + 9 + 王亚鑫 Wang Yaxin <wang.yaxin@zte.com.cn> 10 + 11 + ====== 12 + 时间戳 13 + ====== 14 + 15 + 16 + 1. 控制接口 17 + =========== 18 + 19 + 接收网络数据包时间戳的接口包括: 20 + 21 + SO_TIMESTAMP 22 + 为每个传入数据包生成(不一定是单调的)系统时间时间戳。通过 recvmsg() 23 + 在控制消息中以微秒分辨率报告时间戳。 24 + SO_TIMESTAMP 根据架构类型和 libc 的 lib 中的 time_t 表示方式定义为 25 + SO_TIMESTAMP_NEW 或 SO_TIMESTAMP_OLD。 26 + SO_TIMESTAMP_OLD 和 SO_TIMESTAMP_NEW 的控制消息格式分别为 27 + struct __kernel_old_timeval 和 struct __kernel_sock_timeval。 28 + 29 + SO_TIMESTAMPNS 30 + 与 SO_TIMESTAMP 相同的时间戳机制,但以 struct timespec 格式报告时间戳, 31 + 纳秒分辨率。 32 + SO_TIMESTAMPNS 根据架构类型和 libc 的 time_t 表示方式定义为 33 + SO_TIMESTAMPNS_NEW 或 SO_TIMESTAMPNS_OLD。 34 + 控制消息格式对于 SO_TIMESTAMPNS_OLD 为 struct timespec, 35 + 对于 SO_TIMESTAMPNS_NEW 为 struct __kernel_timespec。 36 + 37 + IP_MULTICAST_LOOP + SO_TIMESTAMP[NS] 38 + 仅用于多播:通过读取回环数据包接收时间戳,获得近似的传输时间戳。 39 + 40 + SO_TIMESTAMPING 41 + 在接收、传输或两者时生成时间戳。支持多个时间戳源,包括硬件。 42 + 支持为流套接字生成时间戳。 43 + 44 + 45 + 1.1 SO_TIMESTAMP(也包括 SO_TIMESTAMP_OLD 和 SO_TIMESTAMP_NEW) 46 + --------------------------------------------------------------- 47 + 48 + 此套接字选项在接收路径上启用数据报的时间戳。由于目标套接字(如果有) 49 + 在网络栈早期未知,因此必须为所有数据包启用此功能。所有早期接收的时间 50 + 戳选项也是如此。 51 + 52 + 有关接口详细信息,请参阅 `man 7 socket`。 53 + 54 + 始终使用 SO_TIMESTAMP_NEW 时间戳以获得 struct __kernel_sock_timeval 55 + 格式的时间戳。 56 + 57 + 如果时间在 2038 年后,SO_TIMESTAMP_OLD 在 32 位机器上将返回错误的时间戳。 58 + 59 + 1.2 SO_TIMESTAMPNS(也包括 SO_TIMESTAMPNS_OLD 和 SO_TIMESTAMPNS_NEW) 60 + --------------------------------------------------------------------- 61 + 62 + 此选项与 SO_TIMESTAMP 相同,但返回数据类型有所不同。其 struct timespec 63 + 能达到比 SO_TIMESTAMP 的 timeval(毫秒)更高的分辨率(纳秒)时间戳。 64 + 65 + 始终使用 SO_TIMESTAMPNS_NEW 时间戳获得 struct __kernel_timespec 格式 66 + 的时间戳。 67 + 68 + 如果时间在 2038 年后,SO_TIMESTAMPNS_OLD 在 32 位机器上将返回错误的时间戳。 69 + 70 + 1.3 SO_TIMESTAMPING(也包括 SO_TIMESTAMPING_OLD 和 SO_TIMESTAMPING_NEW) 71 + ------------------------------------------------------------------------ 72 + 73 + 支持多种类型的时间戳请求。因此,此套接字选项接受标志位图,而不是布尔值。在:: 74 + 75 + err = setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &val, sizeof(val)); 76 + 77 + val 是一个整数,设置了以下任何位。设置其他位将返回 EINVAL 且不更改当前状态。 78 + 79 + 这个套接字选项配置以下几个方面的时间戳生成: 80 + 为单个 sk_buff 结构体生成时间戳(1.3.1); 81 + 将时间戳报告到套接字的错误队列(1.3.2); 82 + 配置相关选项(1.3.3); 83 + 也可以通过 cmsg 为单个 sendmsg 调用启用时间戳生成(1.3.4)。 84 + 85 + 1.3.1 时间戳生成 86 + ^^^^^^^^^^^^^^^^ 87 + 88 + 某些位是向协议栈请求尝试生成时间戳。它们的任何组合都是有效的。对这些位的更改适 89 + 用于新创建的数据包,而不是已经在协议栈中的数据包。因此,可以通过在两个 setsockopt 90 + 调用之间嵌入 send() 调用来选择性地为数据包子集请求时间戳(例如,用于采样), 91 + 一个用于启用时间戳生成,一个用于禁用它。时间戳也可能由于特定套接字请求之外的原 92 + 因而生成,例如在当系统范围内启用接收时间戳时,如前所述。 93 + 94 + SOF_TIMESTAMPING_RX_HARDWARE: 95 + 请求由网络适配器生成的接收时间戳。 96 + 97 + SOF_TIMESTAMPING_RX_SOFTWARE: 98 + 当数据进入内核时请求接收时间戳。这些时间戳在设备驱动程序将数据包交给内核接收 99 + 协议栈后生成。 100 + 101 + SOF_TIMESTAMPING_TX_HARDWARE: 102 + 请求由网络适配器生成的传输时间戳。此标志可以通过套接字选项和控制消息启用。 103 + 104 + SOF_TIMESTAMPING_TX_SOFTWARE: 105 + 当数据离开内核时请求传输(TX)时间戳。这些时间戳由设备驱动程序生成,并且尽 106 + 可能贴近网络接口发送点,但始终在内核将数据包传递给网络接口之前生成。因此, 107 + 它们需要驱动程序支持,且可能并非所有设备都可用。此标志可通过套接字选项和 108 + 控制消息两种方式启用。 109 + 110 + SOF_TIMESTAMPING_TX_SCHED: 111 + 在进入数据包调度器之前请求传输时间戳。内核传输延迟(如果很长)通常由排队 112 + 延迟主导。此时间戳与在 SOF_TIMESTAMPING_TX_SOFTWARE 处获取的时间戳之 113 + 间的差异将暴露此延迟,并且与协议处理无关。协议处理中产生的延迟(如果有) 114 + 可以通过从 send() 之前立即获取的用户空间时间戳中减去此时间戳来计算。在 115 + 具有虚拟设备的机器上,传输的数据包通过多个设备和多个数据包调度器,在每层 116 + 生成时间戳。这允许对排队延迟进行细粒度测量。此标志可以通过套接字选项和控 117 + 制消息启用。 118 + 119 + SOF_TIMESTAMPING_TX_ACK: 120 + 请求在发送缓冲区中的所有数据都已得到确认时生成传输(TX)时间戳。此选项 121 + 仅适用于可靠协议,目前仅在TCP协议中实现。对于该协议,它可能会过度报告 122 + 测量结果,因为时间戳是在send()调用时缓冲区中的所有数据(包括该缓冲区) 123 + 都被确认时生成的,即累积确认。该机制会忽略选择确认(SACK)和前向确认 124 + (FACK)。此标志可通过套接字选项和控制消息两种方式启用。 125 + 126 + SOF_TIMESTAMPING_TX_COMPLETION: 127 + 在数据包传输完成时请求传输时间戳。完成时间戳由内核在从硬件接收数据包完成 128 + 报告时生成。硬件可能一次报告多个数据包,完成时间戳反映报告的时序而不是实 129 + 际传输时间。此标志可以通过套接字选项和控制消息启用。 130 + 131 + 132 + 1.3.2 时间戳报告 133 + ^^^^^^^^^^^^^^^^ 134 + 135 + 其他三个位控制将在生成的控制消息中报告哪些时间戳。对这些位的更改在协议栈中 136 + 的时间戳报告位置立即生效。仅当数据包设置了相关的时间戳生成请求时,才会报告 137 + 其时间戳。 138 + 139 + SOF_TIMESTAMPING_SOFTWARE: 140 + 在可用时报告任何软件时间戳。 141 + 142 + SOF_TIMESTAMPING_SYS_HARDWARE: 143 + 此选项已被弃用和忽略。 144 + 145 + SOF_TIMESTAMPING_RAW_HARDWARE: 146 + 在可用时报告由 SOF_TIMESTAMPING_TX_HARDWARE 或 SOF_TIMESTAMPING_RX_HARDWARE 147 + 生成的硬件时间戳。 148 + 149 + 150 + 1.3.3 时间戳选项 151 + ^^^^^^^^^^^^^^^^ 152 + 153 + 接口支持以下选项 154 + 155 + SOF_TIMESTAMPING_OPT_ID: 156 + 每个数据包生成一个唯一标识符。一个进程可以同时存在多个未完成的时间戳请求。 157 + 数据包在传输路径中可能会发生重排序(例如在数据包调度器中)。在这种情况下, 158 + 时间戳会以与原始send()调用不同的顺序排队到错误队列中。如此一来,仅根据 159 + 时间戳顺序或 payload(有效载荷)检查,并不总能将时间戳与原始send()调用 160 + 唯一匹配。 161 + 162 + 此选项在 send() 时将每个数据包与唯一标识符关联,并与时间戳一起返回。 163 + 标识符源自每个套接字的 u32 计数器(会回绕)。对于数据报套接字,计数器 164 + 随每个发送的数据包递增。对于流套接字,它随每个字节递增。对于流套接字, 165 + 还要设置 SOF_TIMESTAMPING_OPT_ID_TCP,请参阅下面的部分。 166 + 167 + 计数器从零开始。在首次启用套接字选项时初始化。在禁用后再重新启用选项时 168 + 重置。重置计数器不会更改系统中现有数据包的标识符。 169 + 170 + 此选项仅针对传输时间戳实现。在这种情况下,时间戳总是与sock_extended_err 171 + 结构体一起回环。该选项会修改ee_data字段,以传递一个在该套接字所有同时 172 + 存在的未完成时间戳请求中唯一的 ID。 173 + 174 + 进程可以通过控制消息SCM_TS_OPT_ID(TCP 套接字不支持)传递特定 ID, 175 + 从而选择性地覆盖默认生成的 ID,示例如下:: 176 + 177 + struct msghdr *msg; 178 + ... 179 + cmsg = CMSG_FIRSTHDR(msg); 180 + cmsg->cmsg_level = SOL_SOCKET; 181 + cmsg->cmsg_type = SCM_TS_OPT_ID; 182 + cmsg->cmsg_len = CMSG_LEN(sizeof(__u32)); 183 + *((__u32 *) CMSG_DATA(cmsg)) = opt_id; 184 + err = sendmsg(fd, msg, 0); 185 + 186 + 187 + SOF_TIMESTAMPING_OPT_ID_TCP: 188 + 与 SOF_TIMESTAMPING_OPT_ID 一起传递给新的 TCP 时间戳应用程序。 189 + SOF_TIMESTAMPING_OPT_ID 定义了流套接字计数器的增量,但其起始点 190 + 并不完全显而易见。此选项修复了这一点。 191 + 192 + 对于流套接字,如果设置了 SOF_TIMESTAMPING_OPT_ID,则此选项应始终 193 + 设置。在数据报套接字上,选项没有效果。 194 + 195 + 一个合理的期望是系统调用后计数器重置为零,因此后续写入 N 字节将生成 196 + 计数器为 N-1 的时间戳。SOF_TIMESTAMPING_OPT_ID_TCP 在所有条件下 197 + 都实现了此行为。 198 + 199 + SOF_TIMESTAMPING_OPT_ID 不带修饰符时通常报告相同,特别是在套接字选项 200 + 在无数据传输时设置时。如果正在传输数据,它可能与输出队列的长度(SIOCOUTQ) 201 + 偏差。 202 + 203 + 差异是由于基于 snd_una 与 write_seq 的。snd_una 是 peer 确认的 stream 204 + 的偏移量。这取决于外部因素,例如网络 RTT。write_seq 是进程写入的最后一个 205 + 字节。此偏移量不受外部输入影响。 206 + 207 + 差异细微,在套接字选项初始化时配置时不易察觉,但 SOF_TIMESTAMPING_OPT_ID_TCP 208 + 行为在任何时候都更稳健。 209 + 210 + SOF_TIMESTAMPING_OPT_CMSG: 211 + 支持所有时间戳数据包的 recv() cmsg。控制消息已无条件地在所有接收时间戳数据包 212 + 和 IPv6 数据包上支持,以及在发送时间戳数据包的 IPv4 数据包上支持。此选项扩展 213 + 了它们以在发送时间戳数据包的 IPv4 数据包上支持。一个用例是启用 socket 选项 214 + IP_PKTINFO 以关联数据包与其出口设备,通过启用 socket 选项 IP_PKTINFO 同时。 215 + 216 + 217 + SOF_TIMESTAMPING_OPT_TSONLY: 218 + 仅适用于传输时间戳。使内核返回一个 cmsg 与一个空数据包一起,而不是与原 219 + 始数据包一起。这减少了套接字接收预算(SO_RCVBUF)中收取的内存量,并即使 220 + 在 sysctl net.core.tstamp_allow_data 为 0 时也提供时间戳。此选项禁用 221 + SOF_TIMESTAMPING_OPT_CMSG。 222 + 223 + SOF_TIMESTAMPING_OPT_STATS: 224 + 与传输时间戳一起获取的选项性统计信息。它必须与 SOF_TIMESTAMPING_OPT_TSONLY 225 + 一起使用。当传输时间戳可用时,统计信息可在类型为 SCM_TIMESTAMPING_OPT_STATS 226 + 的单独控制消息中获取,作为 TLV(struct nlattr)类型的列表。这些统计信息允许应 227 + 用程序将各种传输层统计信息与传输时间戳关联,例如某个数据块被 peer 的接收窗口限 228 + 制了多长时间。 229 + 230 + SOF_TIMESTAMPING_OPT_PKTINFO: 231 + 启用 SCM_TIMESTAMPING_PKTINFO 控制消息以接收带有硬件时间戳的数据包。 232 + 消息包含 struct scm_ts_pktinfo,它提供接收数据包的实际接口索引和层 2 长度。 233 + 只有在 CONFIG_NET_RX_BUSY_POLL 启用且驱动程序使用 NAPI 时,才会返回非零的 234 + 有效接口索引。该结构还包含另外两个字段,但它们是保留字段且未定义。 235 + 236 + SOF_TIMESTAMPING_OPT_TX_SWHW: 237 + 请求在 SOF_TIMESTAMPING_TX_HARDWARE 和 SOF_TIMESTAMPING_TX_SOFTWARE 238 + 同时启用时,为传出数据包生成硬件和软件时间戳。如果同时生成两个时间戳,两个单 239 + 独的消息将回环到套接字的错误队列,每个消息仅包含一个时间戳。 240 + 241 + SOF_TIMESTAMPING_OPT_RX_FILTER: 242 + 过滤掉虚假接收时间戳:仅当匹配的时间戳生成标志已启用时才报告接收时间戳。 243 + 244 + 接收时间戳在入口路径中生成较早,在数据包的目的套接字确定之前。如果任何套接 245 + 字启用接收时间戳,所有套接字的数据包将接收时间戳数据包。包括那些请求时间戳 246 + 报告与 SOF_TIMESTAMPING_SOFTWARE 和/或 SOF_TIMESTAMPING_RAW_HARDWARE, 247 + 但未请求接收时间戳生成。这可能发生在仅请求发送时间戳时。 248 + 249 + 接收虚假时间戳通常是无害的。进程可以忽略意外的非零值。但它使行为在其他套接 250 + 字上微妙地依赖。此标志隔离套接字以获得更确定的行为。 251 + 252 + 新应用程序鼓励传递 SOF_TIMESTAMPING_OPT_ID 以区分时间戳并传递 253 + SOF_TIMESTAMPING_OPT_TSONLY 以操作,而不管 sysctl net.core.tstamp_allow_data 254 + 的设置。 255 + 256 + 例外情况是当进程需要额外的 cmsg 数据时,例如 SOL_IP/IP_PKTINFO 以检测出 257 + 口网络接口。然后传递选项 SOF_TIMESTAMPING_OPT_CMSG。此选项依赖于访问原 258 + 始数据包的内容,因此不能与 SOF_TIMESTAMPING_OPT_TSONLY 组合。 259 + 260 + 261 + 1.3.4. 通过控制消息启用时间戳 262 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 263 + 264 + 除了套接字选项外,时间戳生成还可以通过 cmsg 按写入请求,仅适用于 265 + SOF_TIMESTAMPING_TX_*(见第 1.3.1 节)。使用此功能,应用程序可以无需启用和 266 + 禁用时间戳即可采样每个 sendmsg() 的时间戳:: 267 + 268 + struct msghdr *msg; 269 + ... 270 + cmsg = CMSG_FIRSTHDR(msg); 271 + cmsg->cmsg_level = SOL_SOCKET; 272 + cmsg->cmsg_type = SO_TIMESTAMPING; 273 + cmsg->cmsg_len = CMSG_LEN(sizeof(__u32)); 274 + *((__u32 *) CMSG_DATA(cmsg)) = SOF_TIMESTAMPING_TX_SCHED | 275 + SOF_TIMESTAMPING_TX_SOFTWARE | 276 + SOF_TIMESTAMPING_TX_ACK; 277 + err = sendmsg(fd, msg, 0); 278 + 279 + 通过 cmsg 设置的 SOF_TIMESTAMPING_TX_* 标志将覆盖通过 setsockopt 设置的 280 + SOF_TIMESTAMPING_TX_* 标志。 281 + 282 + 此外,应用程序仍然需要通过 setsockopt 启用时间戳报告以接收时间戳:: 283 + 284 + __u32 val = SOF_TIMESTAMPING_SOFTWARE | 285 + SOF_TIMESTAMPING_OPT_ID /* 或任何其他标志 */; 286 + err = setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &val, sizeof(val)); 287 + 288 + 289 + 1.4 字节流时间戳 290 + ---------------- 291 + 292 + SO_TIMESTAMPING 接口支持字节流的时间戳。每个请求解释为请求当整个缓冲区内容 293 + 通过时间戳点时。也就是说,对于流选项 SOF_TIMESTAMPING_TX_SOFTWARE 将记录 294 + 当所有字节都到达设备驱动程序时,无论数据被转换成多少个数据包。 295 + 296 + 一般来说,字节流没有自然分隔符,因此将时间戳与数据相关联是非平凡的。字节范围 297 + 可能跨段,任何段可能合并(可能合并先前分段缓冲区关联的独立 send() 调用)。段 298 + 可以重新排序,同一字节范围可以在多个段中并存,对于实现重传的协议。 299 + 300 + 所有时间戳必须实现相同的语义,否则它们是不可比较的。以不同于简单情况(缓冲区 301 + 到 skb 的 1:1 映射)的方式处理“罕见”角落情况是不够的,因为性能调试通常需要 302 + 关注这些异常。 303 + 304 + 在实践中,时间戳可以与字节流段一致地关联,如果时间戳语义和测量时序的选择正确。 305 + 此挑战与决定 IP 分片策略没有不同。在那里,定义是仅对第一个分片进行时间戳。对 306 + 于字节流,我们选择仅在所有字节通过某个点时生成时间戳。SOF_TIMESTAMPING_TX_ACK 307 + 定义的实现和推理是容易的。一个需要考虑 SACK 的实现会更复杂,因为可能存在传输 308 + 空洞和乱序到达。 309 + 310 + 在主机上,TCP 也可以通过 Nagle、cork、autocork、分段和 GSO 打破简单的 1:1 311 + 缓冲区到 skbuff 映射。实现确保在所有情况下都正确,通过跟踪每个 send() 传递 312 + 给send() 的最后一个字节,即使它在 skbuff 扩展或合并操作后不再是最后一个字 313 + 节。它存储相关的序列号在 skb_shinfo(skb)->tskey。因为一个 skbuff 只有一 314 + 个这样的字段,所以只能生成一个时间戳。 315 + 316 + 在罕见情况下,如果两个请求折叠到同一个 skb,则时间戳请求可能会被错过。进程可 317 + 以通过始终在请求之间刷新 TCP 栈来检测此情况,例如启用 TCP_NODELAY 和禁用 318 + TCP_CORK和 autocork。在 linux-4.7 之后,更好的预防合并方法是使用 MSG_EOR 319 + 标志在sendmsg()时。 320 + 321 + 这些预防措施确保时间戳仅在所有字节通过时间戳点时生成,假设网络栈本身不会重新 322 + 排序段。栈确实试图避免重新排序。唯一的例外是管理员控制:可以构造一个数据包调 323 + 度器配置,将来自同一流的不同段延迟不同。这种设置通常不常见。 324 + 325 + 326 + 2 数据接口 327 + ========== 328 + 329 + 时间戳通过 recvmsg() 的辅助数据功能读取。请参阅 `man 3 cmsg` 了解此接口的 330 + 详细信息。套接字手册页面 (`man 7 socket`) 描述了如何检索SO_TIMESTAMP 和 331 + SO_TIMESTAMPNS 生成的数据包时间戳。 332 + 333 + 334 + 2.1 SCM_TIMESTAMPING 记录 335 + ------------------------- 336 + 337 + 这些时间戳在 cmsg_level SOL_SOCKET、cmsg_type SCM_TIMESTAMPING 和类型为 338 + 339 + 对于 SO_TIMESTAMPING_OLD:: 340 + 341 + struct scm_timestamping { 342 + struct timespec ts[3]; 343 + }; 344 + 345 + 对于 SO_TIMESTAMPING_NEW:: 346 + 347 + struct scm_timestamping64 { 348 + struct __kernel_timespec ts[3]; 349 + 350 + 始终使用 SO_TIMESTAMPING_NEW 时间戳以始终获得 struct scm_timestamping64 351 + 格式的时间戳。 352 + 353 + SO_TIMESTAMPING_OLD 在 32 位机器上 2038 年后返回错误的时间戳。 354 + 355 + 该结构可以返回最多三个时间戳。这是一个遗留功能。任何时候至少有一个字 356 + 段不为零。大多数时间戳都通过 ts[0] 传递。硬件时间戳通过 ts[2] 传递。 357 + 358 + ts[1] 以前用于存储硬件时间戳转换为系统时间。相反,将硬件时钟设备直接 359 + 暴露为HW PTP时钟源,以允许用户空间进行时间转换,并可选地与用户空间 360 + PTP 堆栈(如linuxptp)同步系统时间。对于 PTP 时钟 API,请参阅 361 + Documentation/driver-api/ptp.rst。 362 + 363 + 注意,如果同时启用了 SO_TIMESTAMP 或 SO_TIMESTAMPNS 与 364 + SO_TIMESTAMPING 使用 SOF_TIMESTAMPING_SOFTWARE,在 recvmsg() 365 + 调用时会生成一个虚假的软件时间戳,并传递给 ts[0] 当真实软件时间戳缺 366 + 失时。这也发生在硬件传输时间戳上。 367 + 368 + 2.1.1 传输时间戳与 MSG_ERRQUEUE 369 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 370 + 371 + 对于传输时间戳,传出数据包回环到套接字的错误队列,并附加发送时间戳(s)。 372 + 进程通过调用带有 MSG_ERRQUEUE 标志的 recvmsg() 接收时间戳,并传递 373 + 一个足够大的 msg_control缓冲区以接收相关的元数据结构。recvmsg 调用 374 + 返回原始传出数据包,并附加两个辅助消息。 375 + 376 + 一个 cm_level SOL_IP(V6) 和 cm_type IP(V6)_RECVERR 嵌入一个 377 + struct sock_extended_err这定义了错误类型。对于时间戳,ee_errno 378 + 字段是 ENOMSG。另一个辅助消息将具有 cm_level SOL_SOCKET 和 cm_type 379 + SCM_TIMESTAMPING。这嵌入了 struct scm_timestamping。 380 + 381 + 382 + 2.1.1.2 时间戳类型 383 + ~~~~~~~~~~~~~~~~~~ 384 + 385 + 三个 struct timespec 的语义由 struct sock_extended_err 中的 386 + ee_info 字段定义。它包含一个类型 SCM_TSTAMP_* 来定义实际传递给 387 + scm_timestamping 的时间戳。 388 + 389 + SCM_TSTAMP_* 类型与之前讨论的 SOF_TIMESTAMPING_* 控制字段完全 390 + 匹配,只有一个例外对于遗留原因,SCM_TSTAMP_SND 等于零,可以设置为 391 + SOF_TIMESTAMPING_TX_HARDWARE 和 SOF_TIMESTAMPING_TX_SOFTWARE。 392 + 它是第一个,如果 ts[2] 不为零,否则是第二个,在这种情况下,时间戳存 393 + 储在ts[0] 中。 394 + 395 + 396 + 2.1.1.3 分片 397 + ~~~~~~~~~~~~ 398 + 399 + 传出数据报分片很少见,但可能发生,例如通过显式禁用 PMTU 发现。如果 400 + 传出数据包被分片,则仅对第一个分片进行时间戳,并返回给发送套接字。 401 + 402 + 403 + 2.1.1.4 数据包负载 404 + ~~~~~~~~~~~~~~~~~~ 405 + 406 + 调用应用程序通常不关心接收它传递给堆栈的整个数据包负载:套接字错误队 407 + 列机制仅是一种将时间戳附加到其上的方法。在这种情况下,应用程序可以选 408 + 择读取较小的数据报,甚至长度为 0。负载相应地被截断。直到进程调用 409 + recvmsg() 到错误队列,然而,整个数据包仍在队列中,占用 SO_RCVBUF 预算。 410 + 411 + 412 + 2.1.1.5 阻塞读取 413 + ~~~~~~~~~~~~~~~~ 414 + 415 + 从错误队列读取始终是非阻塞操作。要阻塞等待时间戳,请使用 poll 或 416 + select。poll() 将在 pollfd.revents 中返回 POLLERR,如果错误队列 417 + 中有数据。没有必要在 pollfd.events中传递此标志。此标志在请求时被忽 418 + 略。另请参阅 `man 2 poll`。 419 + 420 + 421 + 2.1.2 接收时间戳 422 + ^^^^^^^^^^^^^^^^ 423 + 424 + 在接收时,没有理由从套接字错误队列读取。SCM_TIMESTAMPING 辅助数据与 425 + 数据包数据一起通过正常 recvmsg() 发送。由于这不是套接字错误,它不伴 426 + 随消息 SOL_IP(V6)/IP(V6)_RECVERROR。在这种情况下,struct 427 + scm_timestamping 中的三个字段含义隐式定义。ts[0] 在设置时包含软件 428 + 时间戳,ts[1] 再次被弃用,ts[2] 在设置时包含硬件时间戳。 429 + 430 + 431 + 3. 硬件时间戳配置:ETHTOOL_MSG_TSCONFIG_SET/GET 432 + =============================================== 433 + 434 + 硬件时间戳也必须为每个设备驱动程序初始化,该驱动程序预期执行硬件时间戳。 435 + 参数在 include/uapi/linux/net_tstamp.h 中定义为:: 436 + 437 + struct hwtstamp_config { 438 + int flags; /* 目前没有定义的标志,必须为零 */ 439 + int tx_type; /* HWTSTAMP_TX_* */ 440 + int rx_filter; /* HWTSTAMP_FILTER_* */ 441 + }; 442 + 443 + 期望的行为通过 tsconfig netlink 套接字 ``ETHTOOL_MSG_TSCONFIG_SET`` 444 + 传递到内核,并通过 ``ETHTOOL_A_TSCONFIG_TX_TYPES``、 445 + ``ETHTOOL_A_TSCONFIG_RX_FILTERS`` 和 ``ETHTOOL_A_TSCONFIG_HWTSTAMP_FLAGS`` 446 + netlink 属性设置 struct hwtstamp_config 相应地。 447 + 448 + ``ETHTOOL_A_TSCONFIG_HWTSTAMP_PROVIDER`` netlink 嵌套属性用于选择 449 + 硬件时间戳的来源。它由设备源的索引和时间戳类型限定符组成。 450 + 451 + 驱动程序可以自由使用比请求更宽松的配置。预期驱动程序应仅实现可以直接支持的 452 + 最通用模式。例如,如果硬件可以支持 HWTSTAMP_FILTER_PTP_V2_EVENT,则它 453 + 通常应始终升级HWTSTAMP_FILTER_PTP_V2_L2_SYNC,依此类推,因为 454 + HWTSTAMP_FILTER_PTP_V2_EVENT 更通用(更实用)。 455 + 456 + 支持硬件时间戳的驱动程序应更新 struct,并可能返回更宽松的实际配置。如果 457 + 请求的数据包无法进行时间戳,则不应更改任何内容,并返回 ERANGE(与 EINVAL 458 + 相反,这表明 SIOCSHWTSTAMP 根本不支持)。 459 + 460 + 只有具有管理权限的进程才能更改配置。用户空间负责确保多个进程不会相互干扰, 461 + 并确保设置被重置。 462 + 463 + 任何进程都可以通过请求 tsconfig netlink 套接字 ``ETHTOOL_MSG_TSCONFIG_GET`` 464 + 读取实际配置。 465 + 466 + 遗留配置是使用 ioctl(SIOCSHWTSTAMP) 与指向 struct ifreq 的指针,其 467 + ifr_data指向 struct hwtstamp_config。tx_type 和 rx_filter 是驱动 468 + 程序期望执行的提示。如果请求的细粒度过滤对传入数据包不支持,驱动程序可能 469 + 会对请求的数据包进行时间戳。ioctl(SIOCGHWTSTAMP) 以与 470 + ioctl(SIOCSHWTSTAMP) 相同的方式使用。然而,并非所有驱动程序都实现了这一点。 471 + 472 + :: 473 + 474 + /* 可能的 hwtstamp_config->tx_type 值 */ 475 + enum { 476 + /* 477 + * 不会需要硬件时间戳的传出数据包; 478 + * 如果数据包到达并请求它,则不会进行硬件时间戳 479 + */ 480 + HWTSTAMP_TX_OFF, 481 + 482 + /* 483 + * 启用传出数据包的硬件时间戳; 484 + * 数据包的发送者决定哪些数据包需要时间戳, 485 + * 在发送数据包之前设置 SOF_TIMESTAMPING_TX_SOFTWARE 486 + */ 487 + HWTSTAMP_TX_ON, 488 + }; 489 + 490 + /* 可能的 hwtstamp_config->rx_filter 值 */ 491 + enum { 492 + /* 时间戳不传入任何数据包 */ 493 + HWTSTAMP_FILTER_NONE, 494 + 495 + /* 时间戳任何传入数据包 */ 496 + HWTSTAMP_FILTER_ALL, 497 + 498 + /* 返回值:时间戳所有请求的数据包加上一些其他数据包 */ 499 + HWTSTAMP_FILTER_SOME, 500 + 501 + /* PTP v1,UDP,任何事件数据包 */ 502 + HWTSTAMP_FILTER_PTP_V1_L4_EVENT, 503 + 504 + /* 有关完整值列表,请检查 505 + * 文件 include/uapi/linux/net_tstamp.h 506 + */ 507 + }; 508 + 509 + 3.1 硬件时间戳实现:设备驱动程序 510 + -------------------------------- 511 + 512 + 支持硬件时间戳的驱动程序必须支持 ndo_hwtstamp_set NDO 或遗留 SIOCSHWTSTAMP 513 + ioctl 并更新提供的 struct hwtstamp_config 与实际值,如 SIOCSHWTSTAMP 部分 514 + 所述。它还应支持 ndo_hwtstamp_get 或遗留 SIOCGHWTSTAMP。 515 + 516 + 接收数据包的时间戳必须存储在 skb 中。要获取 skb 的共享时间戳结构,请调用 517 + skb_hwtstamps()。然后设置结构中的时间戳:: 518 + 519 + struct skb_shared_hwtstamps { 520 + /* 硬件时间戳转换为自任意时间点的持续时间 521 + * 自定义点 522 + */ 523 + ktime_t hwtstamp; 524 + }; 525 + 526 + 传出数据包的时间戳应按如下方式生成: 527 + 528 + - 在 hard_start_xmit() 中,检查 (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) 529 + 是否不为零。如果是,则驱动程序期望执行硬件时间戳。 530 + - 如果此 skb 和请求都可能,则声明驱动程序正在执行时间戳,通过设置 skb_shinfo(skb)->tx_flags 531 + 中的标志SKBTX_IN_PROGRESS,例如:: 532 + 533 + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; 534 + 535 + 您可能希望保留与 skb 关联的指针,而不是释放 skb。不支持硬件时间戳的驱 536 + 动程序不会这样做。驱动程序绝不能触及 sk_buff::tstamp!它用于存储网络 537 + 子系统生成的软件时间戳。 538 + - 驱动程序应在尽可能接近将 sk_buff 传递给硬件时调用 skb_tx_timestamp()。 539 + skb_tx_timestamp()提供软件时间戳(如果请求),并且硬件时间戳不可用 540 + (SKBTX_IN_PROGRESS 未设置)。 541 + - 一旦驱动程序发送数据包并/或获取硬件时间戳,它就会通过 skb_tstamp_tx() 542 + 传递时间戳,原始 skb,原始硬件时间戳。skb_tstamp_tx() 克隆原始 skb 并 543 + 添加时间戳,因此原始 skb 现在必须释放。如果获取硬件时间戳失败,则驱动程序 544 + 不应回退到软件时间戳。理由是,这会在处理管道中的稍后时间发生,而不是其他软 545 + 件时间戳,因此可能导致时间戳之间的差异。 546 + 547 + 3.2 堆叠 PTP 硬件时钟的特殊考虑 548 + ------------------------------- 549 + 550 + 在数据包的路径中可能存在多个 PHC(PTP 硬件时钟)。内核没有明确的机制允许用 551 + 户选择用于时间戳以太网帧的 PHC。相反,假设最外层的 PHC 始终是最优的,并且 552 + 内核驱动程序协作以实现这一目标。目前有 3 种堆叠 PHC 的情况,如下所示: 553 + 554 + 3.2.1 DSA(分布式交换架构)交换机 555 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 556 + 557 + 这些是具有一个端口连接到(完全不知情的)主机以太网接口的以太网交换机,并且 558 + 执行端口多路复用或可选转发加速功能。每个 DSA 交换机端口在用户看来都是独立的 559 + (虚拟)网络接口,其网络 I/O 在底层通过主机接口(在 TX 上重定向到主机端口, 560 + 在 RX 上拦截帧)执行。 561 + 562 + 当 DSA 交换机连接到主机端口时,PTP 同步必须受到限制,因为交换机的可变排队 563 + 延迟引入了主机端口与其 PTP 伙伴之间的路径延迟抖动。因此,一些 DSA 交换机 564 + 包含自己的时间戳时钟,并具有在自身 MAC上执行网络时间戳的能力,因此路径延迟 565 + 仅测量线缆和 PHY 传播延迟。支持 Linux 的 DSA 交换机暴露了与任何其他网络 566 + 接口相同的 ABI(除了 DSA 接口在网络 I/O 方面实际上是虚拟的,它们确实有自 567 + 己的PHC)。典型地,但不是强制性地,所有DSA 交换机接口共享相同的 PHC。 568 + 569 + 通过设计,DSA 交换机对连接到其主机端口的 PTP 时间戳不需要任何特殊的驱动程 570 + 序处理。然而,当主机端口也支持 PTP 时间戳时,DSA 将负责拦截 571 + ``.ndo_eth_ioctl`` 调用,并阻止尝试在主机端口上启用硬件时间戳。这是因为 572 + SO_TIMESTAMPING API 不允许为同一数据包传递多个硬件时间戳,因此除了 DSA 573 + 交换机端口之外的任何人都不应阻止这样做。 574 + 575 + 在通用层,DSA 提供了以下基础设施用于 PTP 时间戳: 576 + 577 + - ``.port_txtstamp()``:在用户空间从用户空间请求带有硬件 TX 时间戳请求 578 + 的数据包之前调用的钩子。这是必需的,因为硬件时间戳在实际 MAC 传输后才可 579 + 用,因此驱动程序必须准备将时间戳与原始数据包相关联,以便它可以重新入队数 580 + 据包到套接字的错误队列。为了保存可能在时间戳可用时需要的数据包,驱动程序 581 + 可以调用 ``skb_clone_sk``,在 skb->cb 中保存克隆指针,并入队一个 tx 582 + skb 队列。通常,交换机会有一个PTP TX 时间戳寄存器(或有时是一个 FIFO), 583 + 其中时间戳可用。在 FIFO 的情况下,硬件可能会存储PTP 序列 ID/消息类型/ 584 + 域号和实际时间戳的键值对。为了在等待时间戳的数据包队列和实际时间戳之间正 585 + 确关联,驱动程序可以使用 BPF 分类器(``ptp_classify_raw``) 来识别 PTP 586 + 传输类型,并使用 ``ptp_parse_header`` 解释 PTP 头字段。可能存在一个 IRQ, 587 + 当此时间戳可用时触发,或者驱动程序可能需要轮询,在调用 ``dev_queue_xmit()`` 588 + 到主机接口之后。单步 TX 时间戳不需要数据包克隆,因为 PTP 协议不需要后续消 589 + 息(因为TX 时间戳已嵌入到数据包中),因此用户空间不期望数据包带有 TX 时间戳 590 + 被重新入队到其套接字的错误队列。 591 + 592 + - ``.port_rxtstamp()``:在 RX 上,DSA 运行 BPF 分类器以识别 PTP 事件消息 593 + (任何其他数据包,包括 PTP 通用消息,不进行时间戳)。驱动程序提供原始(也是唯一) 594 + 时间戳数据包,以便它可以标记它,如果它是立即可用的,或者延迟。在接收时,时间 595 + 戳可能要么在频带内(通过DSA 头中的元数据,或以其他方式附加到数据包),要么在频 596 + 带外(通过另一个 RX 时间戳FIFO)。在 RX 上延迟通常是必要的,当检索时间戳需要 597 + 可睡眠上下文时。在这种情况下,DSA驱动程序有责任调用 ``netif_rx()`` 在新鲜时 598 + 间戳的 skb 上。 599 + 600 + 3.2.2 以太网 PHYs 601 + ^^^^^^^^^^^^^^^^^ 602 + 603 + 这些是通常在网络栈中履行第 1 层角色的设备,因此它们在 DSA 交换机中没有网络接 604 + 口的表示。然而,PHY可能能够检测和时间戳 PTP 数据包,出于性能原因:在尽可能接 605 + 近导线的地方获取的时间戳具有更稳定的同步性和更精确的精度。 606 + 607 + 支持 PTP 时间戳的 PHY 驱动程序必须创建 ``struct mii_timestamper`` 并添加 608 + 指向它的指针在 ``phydev->mii_ts`` 中。 ``phydev->mii_ts`` 的存在将由网络 609 + 堆栈检查。 610 + 611 + 由于 PHY 没有网络接口表示,PHY 的时间戳和 ethtool ioctl 操作需要通过其各自 612 + 的 MAC驱动程序进行中介。因此,与 DSA 交换机不同,需要对每个单独的 MAC 驱动 613 + 程序进行 PHY时间戳支持的修改。这包括: 614 + 615 + - 在 ``.ndo_eth_ioctl`` 中检查,是否 ``phy_has_hwtstamp(netdev->phydev)`` 616 + 为真或假。如果是,则 MAC 驱动程序不应处理此请求,而应将其传递给 PHY 使用 617 + ``phy_mii_ioctl()``。 618 + 619 + - 在 RX 上,特殊干预可能或可能不需要,具体取决于将 skb 传递到网络堆栈的函数。 620 + 在 plain ``netif_rx()`` 和类似情况下,MAC 驱动程序必须检查是否 621 + ``skb_defer_rx_timestamp(skb)`` 是必要的,如果是,则不调用 ``netif_rx()``。 622 + 如果 ``CONFIG_NETWORK_PHY_TIMESTAMPING`` 启用,并且 623 + ``skb->dev->phydev->mii_ts`` 存在,它的 ``.rxtstamp()`` 钩子现在将被调 624 + 用,以使用与 DSA 类似的逻辑确定 RX 时间戳延迟是否必要。同样像 DSA,它成为 625 + PHY 驱动程序的责任,在时间戳可用时发送数据包到堆栈。 626 + 627 + 对于其他 skb 接收函数,例如 ``napi_gro_receive`` 和 ``netif_receive_skb``, 628 + 堆栈会自动检查是否 ``skb_defer_rx_timestamp()`` 是必要的,因此此检查不 629 + 需要在驱动程序内部。 630 + 631 + - 在 TX 上,同样,特殊干预可能或可能不需要。调用 ``mii_ts->txtstamp()``钩 632 + 子的函数名为``skb_clone_tx_timestamp()``。此函数可以直接调用(在这种情 633 + 况下,确实需要显式 MAC 驱动程序支持),但函数也 piggybacks 从 634 + ``skb_tx_timestamp()`` 调用,许多 MAC 驱动程序已经为软件时间戳目的执行。 635 + 因此,如果 MAC 支持软件时间戳,则它不需要在此阶段执行任何其他操作。 636 + 637 + 3.2.3 MII 总线嗅探设备 638 + ^^^^^^^^^^^^^^^^^^^^^^ 639 + 640 + 这些执行与时间戳以太网 PHY 相同的角色,除了它们是离散设备,因此可以与任何 PHY 641 + 组合,即使它不支持时间戳。在 Linux 中,它们是可发现的,可以通过 Device Tree 642 + 附加到 ``struct phy_device``,对于其余部分,它们使用与那些相同的 mii_ts 基 643 + 础设施。请参阅 Documentation/devicetree/bindings/ptp/timestamper.txt 了 644 + 解更多详细信息。 645 + 646 + 3.2.4 MAC 驱动程序的其他注意事项 647 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 648 + 649 + 堆叠 PHC 可能会暴露 MAC 驱动程序的错误,这些错误在未堆叠 PHC 时无法触发。一个 650 + 例子涉及此行代码,已经在前面的部分中介绍过:: 651 + 652 + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; 653 + 654 + 任何 TX 时间戳逻辑,无论是普通的 MAC 驱动程序、DSA 交换机驱动程序、PHY 驱动程 655 + 序还是 MII 总线嗅探设备驱动程序,都应该设置此标志。但一个未意识到 PHC 堆叠的 656 + MAC 驱动程序可能会被其他不是它自己的实体设置此标志,并传递一个重复的时间戳。例 657 + 如,典型的 TX 时间戳逻辑可能是将传输部分分为 2 个部分: 658 + 659 + 1. "TX":检查是否通过 ``.ndo_eth_ioctl``("``priv->hwtstamp_tx_enabled 660 + == true``")和当前 skb 是否需要 TX 时间戳("``skb_shinfo(skb)->tx_flags 661 + & SKBTX_HW_TSTAMP``")。如果为真,则设置 "``skb_shinfo(skb)->tx_flags 662 + |= SKBTX_IN_PROGRESS``" 标志。注意:如上所述,在堆叠 PHC 系统中,此条件 663 + 不应触发,因为此 MAC 肯定不是最外层的 PHC。但这是典型的错误所在。传输继续 664 + 使用此数据包。 665 + 666 + 2. "TX 确认":传输完成。驱动程序检查是否需要收集任何 TX 时间戳。这里通常是典 667 + 型的错误所在:驱动程序采取捷径,只检查 "``skb_shinfo(skb)->tx_flags & 668 + SKBTX_IN_PROGRESS``" 是否设置。在堆叠 PHC 系统中,这是错误的,因为此 MAC 669 + 驱动程序不是唯一在 TX 数据路径中启用 SKBTX_IN_PROGRESS 的实体。 670 + 671 + 此问题的正确解决方案是 MAC 驱动程序在其 "TX 确认" 部分中有一个复合检查,不仅 672 + 针对 "``skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS``",还针对 673 + "``priv->hwtstamp_tx_enabled == true``"。因为系统确保 PTP 时间戳仅对最 674 + 外层 PHC 启用,此增强检查将避免向用户空间传递重复的 TX 时间戳。
+1
Documentation/translations/zh_CN/rust/general-information.rst
··· 13 13 14 14 本文档包含了在内核中使用Rust支持时需要了解的有用信息。 15 15 16 + .. _rust_code_documentation_zh_cn: 16 17 17 18 代码文档 18 19 --------
+32 -1
Documentation/translations/zh_CN/rust/index.rst
··· 10 10 Rust 11 11 ==== 12 12 13 - 与内核中的Rust有关的文档。若要开始在内核中使用Rust,请阅读quick-start.rst指南。 13 + 与内核中的Rust有关的文档。若要开始在内核中使用Rust,请阅读 quick-start.rst 指南。 14 + 15 + Rust 实验 16 + --------- 17 + Rust 支持在 v6.1 版本中合并到主线,以帮助确定 Rust 作为一种语言是否适合内核, 18 + 即是否值得进行权衡。 19 + 20 + 目前,Rust 支持主要面向对 Rust 支持感兴趣的内核开发人员和维护者, 21 + 以便他们可以开始处理抽象和驱动程序,并帮助开发基础设施和工具。 22 + 23 + 如果您是终端用户,请注意,目前没有适合或旨在生产使用的内置驱动程序或模块, 24 + 并且 Rust 支持仍处于开发/实验阶段,尤其是对于特定内核配置。 25 + 26 + 代码文档 27 + -------- 28 + 29 + 给定一个内核配置,内核可能会生成 Rust 代码文档,即由 ``rustdoc`` 工具呈现的 HTML。 30 + 31 + .. only:: rustdoc and html 32 + 33 + 该内核文档使用 `Rust 代码文档 <rustdoc/kernel/index.html>`_ 构建。 34 + 35 + .. only:: not rustdoc and html 36 + 37 + 该内核文档不使用 Rust 代码文档构建。 38 + 39 + 预生成版本提供在:https://rust.docs.kernel.org。 40 + 41 + 请参阅 :ref:`代码文档 <rust_code_documentation_zh_cn>` 部分以获取更多详细信息。 14 42 15 43 .. toctree:: 16 44 :maxdepth: 1 ··· 47 19 general-information 48 20 coding-guidelines 49 21 arch-support 22 + testing 23 + 24 + 你还可以在 :doc:`../../../process/kernel-docs` 中找到 Rust 的学习材料。 50 25 51 26 .. only:: subproject and html 52 27
+215
Documentation/translations/zh_CN/rust/testing.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + .. include:: ../disclaimer-zh_CN.rst 3 + 4 + :Original: Documentation/rust/testing.rst 5 + 6 + :翻译: 7 + 8 + 郭杰 Ben Guo <benx.guo@gmail.com> 9 + 10 + 测试 11 + ==== 12 + 13 + 本文介绍了如何在内核中测试 Rust 代码。 14 + 15 + 有三种测试类型: 16 + 17 + - KUnit 测试 18 + - ``#[test]`` 测试 19 + - Kselftests 20 + 21 + KUnit 测试 22 + ---------- 23 + 24 + 这些测试来自 Rust 文档中的示例。它们会被转换为 KUnit 测试。 25 + 26 + 使用 27 + **** 28 + 29 + 这些测试可以通过 KUnit 运行。例如,在命令行中使用 ``kunit_tool`` ( ``kunit.py`` ):: 30 + 31 + ./tools/testing/kunit/kunit.py run --make_options LLVM=1 --arch x86_64 --kconfig_add CONFIG_RUST=y 32 + 33 + 或者,KUnit 也可以在内核启动时以内置方式运行。获取更多 KUnit 信息,请参阅 34 + Documentation/dev-tools/kunit/index.rst。 35 + 关于内核内置与命令行测试的详细信息,请参阅 Documentation/dev-tools/kunit/architecture.rst。 36 + 37 + 要使用这些 KUnit 文档测试,需要在内核配置中启用以下选项:: 38 + 39 + CONFIG_KUNIT 40 + Kernel hacking -> Kernel Testing and Coverage -> KUnit - Enable support for unit tests 41 + CONFIG_RUST_KERNEL_DOCTESTS 42 + Kernel hacking -> Rust hacking -> Doctests for the `kernel` crate 43 + 44 + KUnit 测试即文档测试 45 + ******************** 46 + 47 + 文档测试( *doctests* )一般用于展示函数、结构体或模块等的使用方法。 48 + 49 + 它们非常方便,因为它们就写在文档旁边。例如: 50 + 51 + .. code-block:: rust 52 + 53 + /// 求和两个数字。 54 + /// 55 + /// ``` 56 + /// assert_eq!(mymod::f(10, 20), 30); 57 + /// ``` 58 + pub fn f(a: i32, b: i32) -> i32 { 59 + a + b 60 + } 61 + 62 + 在用户空间中,这些测试由 ``rustdoc`` 负责收集并运行。单独使用这个工具已经很有价值, 63 + 因为它可以验证示例能否成功编译(确保和代码保持同步), 64 + 同时还可以运行那些不依赖内核 API 的示例。 65 + 66 + 然而,在内核中,这些测试会转换成 KUnit 测试套件。 67 + 这意味着文档测试会被编译成 Rust 内核对象,从而可以在构建的内核环境中运行。 68 + 69 + 通过与 KUnit 集成,Rust 的文档测试可以复用内核现有的测试设施。 70 + 例如,内核日志会显示:: 71 + 72 + KTAP version 1 73 + 1..1 74 + KTAP version 1 75 + # Subtest: rust_doctests_kernel 76 + 1..59 77 + # rust_doctest_kernel_build_assert_rs_0.location: rust/kernel/build_assert.rs:13 78 + ok 1 rust_doctest_kernel_build_assert_rs_0 79 + # rust_doctest_kernel_build_assert_rs_1.location: rust/kernel/build_assert.rs:56 80 + ok 2 rust_doctest_kernel_build_assert_rs_1 81 + # rust_doctest_kernel_init_rs_0.location: rust/kernel/init.rs:122 82 + ok 3 rust_doctest_kernel_init_rs_0 83 + ... 84 + # rust_doctest_kernel_types_rs_2.location: rust/kernel/types.rs:150 85 + ok 59 rust_doctest_kernel_types_rs_2 86 + # rust_doctests_kernel: pass:59 fail:0 skip:0 total:59 87 + # Totals: pass:59 fail:0 skip:0 total:59 88 + ok 1 rust_doctests_kernel 89 + 90 + 文档测试中,也可以正常使用 `? <https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator>`_ 运算符,例如: 91 + 92 + .. code-block:: rust 93 + 94 + /// ``` 95 + /// # use kernel::{spawn_work_item, workqueue}; 96 + /// spawn_work_item!(workqueue::system(), || pr_info!("x\n"))?; 97 + /// # Ok::<(), Error>(()) 98 + /// ``` 99 + 100 + 这些测试和普通代码一样,也可以在 ``CLIPPY=1`` 条件下通过 Clippy 进行编译, 101 + 因此可以从额外的 lint 检查中获益。 102 + 103 + 为了便于开发者定位文档测试出错的具体行号,日志会输出一条 KTAP 诊断信息。 104 + 其中标明了原始测试的文件和行号(不是 ``rustdoc`` 生成的临时 Rust 文件位置):: 105 + 106 + # rust_doctest_kernel_types_rs_2.location: rust/kernel/types.rs:150 107 + 108 + Rust 测试中常用的断言宏是来自 Rust 标准库( ``core`` )中的 ``assert!`` 和 ``assert_eq!`` 宏。 109 + 内核提供了一个定制版本,这些宏的调用会被转发到 KUnit。 110 + 和 KUnit 测试不同的是,这些宏不需要传递上下文参数( ``struct kunit *`` )。 111 + 这使得它们更易于使用,同时文档的读者无需关心底层用的是什么测试框架。 112 + 此外,这种方式未来也许可以让我们更容易测试第三方代码。 113 + 114 + 当前有一个限制:KUnit 不支持在其他任务中执行断言。 115 + 因此,如果断言真的失败了,我们只是简单地把错误打印到内核日志里。 116 + 另外,文档测试不适用于非公开的函数。 117 + 118 + 作为文档中的测试示例,应当像 “实际代码” 一样编写。 119 + 例如:不要使用 ``unwrap()`` 或 ``expect()``,请使用 `? <https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator>`_ 运算符。 120 + 更多背景信息,请参阅: 121 + 122 + https://rust.docs.kernel.org/kernel/error/type.Result.html#error-codes-in-c-and-rust 123 + 124 + ``#[test]`` 测试 125 + ---------------- 126 + 127 + 此外,还有 ``#[test]`` 测试。与文档测试类似,这些测试与用户空间中的测试方式也非常相近,并且同样会映射到 KUnit。 128 + 129 + 这些测试通过 ``kunit_tests`` 过程宏引入,该宏将测试套件的名称作为参数。 130 + 131 + 例如,假设想要测试前面文档测试示例中的函数 ``f``,我们可以在定义该函数的同一文件中编写: 132 + 133 + .. code-block:: rust 134 + 135 + #[kunit_tests(rust_kernel_mymod)] 136 + mod tests { 137 + use super::*; 138 + 139 + #[test] 140 + fn test_f() { 141 + assert_eq!(f(10, 20), 30); 142 + } 143 + } 144 + 145 + 如果我们执行这段代码,内核日志会显示:: 146 + 147 + KTAP version 1 148 + # Subtest: rust_kernel_mymod 149 + # speed: normal 150 + 1..1 151 + # test_f.speed: normal 152 + ok 1 test_f 153 + ok 1 rust_kernel_mymod 154 + 155 + 与文档测试类似, ``assert!`` 和 ``assert_eq!`` 宏被映射回 KUnit 并且不会发生 panic。 156 + 同样,支持 `? <https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator>`_ 运算符, 157 + 测试函数可以什么都不返回(单元类型 ``()``)或 ``Result`` (任何 ``Result<T, E>``)。例如: 158 + 159 + .. code-block:: rust 160 + 161 + #[kunit_tests(rust_kernel_mymod)] 162 + mod tests { 163 + use super::*; 164 + 165 + #[test] 166 + fn test_g() -> Result { 167 + let x = g()?; 168 + assert_eq!(x, 30); 169 + Ok(()) 170 + } 171 + } 172 + 173 + 如果我们运行测试并且调用 ``g`` 失败,那么内核日志会显示:: 174 + 175 + KTAP version 1 176 + # Subtest: rust_kernel_mymod 177 + # speed: normal 178 + 1..1 179 + # test_g: ASSERTION FAILED at rust/kernel/lib.rs:335 180 + Expected is_test_result_ok(test_g()) to be true, but is false 181 + # test_g.speed: normal 182 + not ok 1 test_g 183 + not ok 1 rust_kernel_mymod 184 + 185 + 如果 ``#[test]`` 测试可以对用户起到示例作用,那就应该改用文档测试。 186 + 即使是 API 的边界情况,例如错误或边界问题,放在示例中展示也同样有价值。 187 + 188 + ``rusttest`` 宿主机测试 189 + ----------------------- 190 + 191 + 这类测试运行在用户空间,可以通过 ``rusttest`` 目标在构建内核的宿主机中编译并运行:: 192 + 193 + make LLVM=1 rusttest 194 + 195 + 当前操作需要内核 ``.config``。 196 + 197 + 目前,它们主要用于测试 ``macros`` crate 的示例。 198 + 199 + Kselftests 200 + ---------- 201 + 202 + Kselftests 可以在 ``tools/testing/selftests/rust`` 文件夹中找到。 203 + 204 + 测试所需的内核配置选项列在 ``tools/testing/selftests/rust/config`` 文件中, 205 + 可以借助 ``merge_config.sh`` 脚本合并到现有配置中:: 206 + 207 + ./scripts/kconfig/merge_config.sh .config tools/testing/selftests/rust/config 208 + 209 + Kselftests 会在内核源码树中构建,以便在运行相同版本内核的系统上执行测试。 210 + 211 + 一旦安装并启动了与源码树匹配的内核,测试即可通过以下命令编译并执行:: 212 + 213 + make TARGETS="rust" kselftest 214 + 215 + 请参阅 Documentation/dev-tools/kselftest.rst 文档以获取更多信息。
+92
Documentation/translations/zh_CN/scsi/index.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + .. include:: ../disclaimer-zh_CN.rst 3 + 4 + :Original: Documentation/scsi/index.rst 5 + 6 + :翻译: 7 + 8 + 郝栋栋 doubled <doubled@leap-io-kernel.com> 9 + 10 + :校译: 11 + 12 + 13 + 14 + ========== 15 + SCSI子系统 16 + ========== 17 + 18 + .. toctree:: 19 + :maxdepth: 1 20 + 21 + 简介 22 + ==== 23 + 24 + .. toctree:: 25 + :maxdepth: 1 26 + 27 + scsi 28 + 29 + SCSI驱动接口 30 + ============ 31 + 32 + .. toctree:: 33 + :maxdepth: 1 34 + 35 + scsi_mid_low_api 36 + scsi_eh 37 + 38 + SCSI驱动参数 39 + ============ 40 + 41 + .. toctree:: 42 + :maxdepth: 1 43 + 44 + scsi-parameters 45 + link_power_management_policy 46 + 47 + SCSI主机适配器驱动 48 + ================== 49 + 50 + .. toctree:: 51 + :maxdepth: 1 52 + 53 + sd-parameters 54 + 55 + Todolist: 56 + 57 + * 53c700 58 + * aacraid 59 + * advansys 60 + * aha152x 61 + * aic79xx 62 + * aic7xxx 63 + * arcmsr_spec 64 + * bfa 65 + * bnx2fc 66 + * BusLogic 67 + * cxgb3i 68 + * dc395x 69 + * dpti 70 + * FlashPoint 71 + * g_NCR5380 72 + * hpsa 73 + * hptiop 74 + * libsas 75 + * lpfc 76 + * megaraid 77 + * ncr53c8xx 78 + * NinjaSCSI 79 + * ppa 80 + * qlogicfas 81 + * scsi-changer 82 + * scsi_fc_transport 83 + * scsi-generic 84 + * smartpqi 85 + * st 86 + * sym53c500_cs 87 + * sym53c8xx_2 88 + * tcm_qla2xxx 89 + * ufs 90 + * wd719x 91 + 92 + * scsi_transport_srp/figures
+118
Documentation/translations/zh_CN/scsi/scsi-parameters.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + .. include:: ../disclaimer-zh_CN.rst 3 + 4 + :Original: Documentation/scsi/scsi-parameters.rst 5 + 6 + :翻译: 7 + 8 + 郝栋栋 doubled <doubled@leap-io-kernel.com> 9 + 10 + :校译: 11 + 12 + 13 + 14 + ============ 15 + SCSI内核参数 16 + ============ 17 + 18 + 请查阅Documentation/admin-guide/kernel-parameters.rst以获取 19 + 指定模块参数相关的通用信息。 20 + 21 + 当前文档可能不完全是最新和全面的。命令 ``modinfo -p ${modulename}`` 22 + 显示了可加载模块的参数列表。可加载模块被加载到内核中后,也会在 23 + /sys/module/${modulename}/parameters/ 目录下显示其参数。其 24 + 中某些参数可以通过命令 25 + ``echo -n ${value} > /sys/module/${modulename}/parameters/${parm}`` 26 + 在运行时修改。 27 + 28 + :: 29 + 30 + advansys= [HW,SCSI] 31 + 请查阅 drivers/scsi/advansys.c 文件头部。 32 + 33 + aha152x= [HW,SCSI] 34 + 请查阅 Documentation/translations/zh_CN/scsi/aha152x.rst。 35 + 36 + aha1542= [HW,SCSI] 37 + 格式:<portbase>[,<buson>,<busoff>[,<dmaspeed>]] 38 + 39 + aic7xxx= [HW,SCSI] 40 + 请查阅 Documentation/translations/zh_CN/scsi/aic7xxx.rst。 41 + 42 + aic79xx= [HW,SCSI] 43 + 请查阅 Documentation/translations/zh_CN/scsi/aic79xx.rst。 44 + 45 + atascsi= [HW,SCSI] 46 + 请查阅 drivers/scsi/atari_scsi.c。 47 + 48 + BusLogic= [HW,SCSI] 49 + 请查阅 drivers/scsi/BusLogic.c 文件中 50 + BusLogic_ParseDriverOptions()函数前的注释。 51 + 52 + gvp11= [HW,SCSI] 53 + 54 + ips= [HW,SCSI] Adaptec / IBM ServeRAID 控制器 55 + 请查阅 drivers/scsi/ips.c 文件头部。 56 + 57 + mac5380= [HW,SCSI] 58 + 请查阅 drivers/scsi/mac_scsi.c。 59 + 60 + scsi_mod.max_luns= 61 + [SCSI] 最大可探测LUN数。 62 + 取值范围为 1 到 2^32-1。 63 + 64 + scsi_mod.max_report_luns= 65 + [SCSI] 接收到的最大LUN数。 66 + 取值范围为 1 到 16384。 67 + 68 + NCR_D700= [HW,SCSI] 69 + 请查阅 drivers/scsi/NCR_D700.c 文件头部。 70 + 71 + ncr5380= [HW,SCSI] 72 + 请查阅 Documentation/translations/zh_CN/scsi/g_NCR5380.rst。 73 + 74 + ncr53c400= [HW,SCSI] 75 + 请查阅 Documentation/translations/zh_CN/scsi/g_NCR5380.rst。 76 + 77 + ncr53c400a= [HW,SCSI] 78 + 请查阅 Documentation/translations/zh_CN/scsi/g_NCR5380.rst。 79 + 80 + ncr53c8xx= [HW,SCSI] 81 + 82 + osst= [HW,SCSI] SCSI磁带驱动 83 + 格式:<buffer_size>,<write_threshold> 84 + 另请查阅 Documentation/translations/zh_CN/scsi/st.rst。 85 + 86 + scsi_debug_*= [SCSI] 87 + 请查阅 drivers/scsi/scsi_debug.c。 88 + 89 + scsi_mod.default_dev_flags= 90 + [SCSI] SCSI默认设备标志 91 + 格式:<integer> 92 + 93 + scsi_mod.dev_flags= 94 + [SCSI] 厂商和型号的黑/白名单条目 95 + 格式:<vendor>:<model>:<flags> 96 + (flags 为整数值) 97 + 98 + scsi_mod.scsi_logging_level= 99 + [SCSI] 日志级别的位掩码 100 + 位的定义请查阅 drivers/scsi/scsi_logging.h。 101 + 此参数也可以通过sysctl对dev.scsi.logging_level 102 + 进行设置(/proc/sys/dev/scsi/logging_level)。 103 + 此外,S390-tools软件包提供了一个便捷的 104 + ‘scsi_logging_level’ 脚本,可以从以下地址下载: 105 + https://github.com/ibm-s390-linux/s390-tools/blob/master/scripts/scsi_logging_level 106 + 107 + scsi_mod.scan= [SCSI] sync(默认)在发现SCSI总线过程中 108 + 同步扫描。async在内核线程中异步扫描,允许系统继续 109 + 启动流程。none忽略扫描,预期由用户空间完成扫描。 110 + 111 + sim710= [SCSI,HW] 112 + 请查阅 drivers/scsi/sim710.c 文件头部。 113 + 114 + st= [HW,SCSI] SCSI磁带参数(缓冲区大小等) 115 + 请查阅 Documentation/translations/zh_CN/scsi/st.rst。 116 + 117 + wd33c93= [HW,SCSI] 118 + 请查阅 drivers/scsi/wd33c93.c 文件头部。
+48
Documentation/translations/zh_CN/scsi/scsi.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + .. include:: ../disclaimer-zh_CN.rst 3 + 4 + :Original: Documentation/scsi/scsi.rst 5 + 6 + :翻译: 7 + 8 + 郝栋栋 doubled <doubled@leap-io-kernel.com> 9 + 10 + :校译: 11 + 12 + 13 + 14 + ============== 15 + SCSI子系统文档 16 + ============== 17 + 18 + Linux文档项目(LDP)维护了一份描述Linux内核(lk) 2.4中SCSI 19 + 子系统的文档。请参考: 20 + https://www.tldp.org/HOWTO/SCSI-2.4-HOWTO 。LDP提供单页和 21 + 多页的HTML版本,以及PostScript与PDF格式的文档。 22 + 23 + 在SCSI子系统中使用模块的注意事项 24 + ================================ 25 + Linux内核中的SCSI支持可以根据终端用户的需求以不同的方式模块 26 + 化。为了理解你的选择,我们首先需要定义一些术语。 27 + 28 + scsi-core(也被称为“中间层”)包含SCSI支持的核心。没有他你将 29 + 无法使用任何其他SCSI驱动程序。SCSI核心支持可以是一个模块( 30 + scsi_mod.o),也可以编译进内核。如果SCSI核心是一个模块,那么 31 + 他必须是第一个被加载的SCSI模块,如果你将卸载该模块,那么他必 32 + 须是最后一个被卸载的模块。实际上,modprobe和rmmod命令将确保 33 + SCSI子系统中模块加载与卸载的正确顺序。 34 + 35 + 一旦SCSI核心存在于内核中(无论是编译进内核还是作为模块加载), 36 + 独立的上层驱动和底层驱动可以按照任意顺序加载。磁盘驱动程序 37 + (sd_mod.o)、光盘驱动程序(sr_mod.o)、磁带驱动程序 [1]_ 38 + (st.o)以及SCSI通用驱动程序(sg.o)代表了上层驱动,用于控制 39 + 相应的各种设备。例如,你可以加载磁带驱动程序来使用磁带驱动器, 40 + 然后在不需要该驱动程序时卸载他(并释放相关内存)。 41 + 42 + 底层驱动程序用于支持您所运行硬件平台支持的不同主机卡。这些不同 43 + 的主机卡通常被称为主机总线适配器(HBAs)。例如,aic7xxx.o驱动 44 + 程序被用于控制Adaptec所属的所有最新的SCSI控制器。几乎所有的底 45 + 层驱动都可以被编译为模块或直接编译进内核。 46 + 47 + .. [1] 磁带驱动程序有一个变种用于控制OnStream磁带设备。其模块 48 + 名称为osst.o 。
+482
Documentation/translations/zh_CN/scsi/scsi_eh.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + .. include:: ../disclaimer-zh_CN.rst 3 + 4 + :Original: Documentation/scsi/scsi_eh.rst 5 + 6 + :翻译: 7 + 8 + 郝栋栋 doubled <doubled@leap-io-kernel.com> 9 + 10 + :校译: 11 + 12 + 13 + =================== 14 + SCSI 中间层错误处理 15 + =================== 16 + 17 + 本文档描述了SCSI中间层(mid layer)的错误处理基础架构。 18 + 关于SCSI中间层的更多信息,请参阅: 19 + Documentation/scsi/scsi_mid_low_api.rst。 20 + 21 + .. 目录 22 + 23 + [1] SCSI 命令如何通过中间层传递并进入错误处理(EH) 24 + [1-1] scsi_cmnd(SCSI命令)结构体 25 + [1-2] scmd(SCSI 命令)是如何完成的? 26 + [1-2-1] 通过scsi_done完成scmd 27 + [1-2-2] 通过超时机制完成scmd 28 + [1-3] 错误处理模块如何接管流程 29 + [2] SCSI错误处理机制工作原理 30 + [2-1] 基于细粒度回调的错误处理 31 + [2-1-1] 概览 32 + [2-1-2] scmd在错误处理流程中的传递路径 33 + [2-1-3] 控制流分析 34 + [2-2] 通过transportt->eh_strategy_handler()实现的错误处理 35 + [2-2-1] transportt->eh_strategy_handler()调用前的中间层状态 36 + [2-2-2] transportt->eh_strategy_handler()调用后的中间层状态 37 + [2-2-3] 注意事项 38 + 39 + 40 + 1. SCSI命令在中间层及错误处理中的传递流程 41 + ========================================= 42 + 43 + 1.1 scsi_cmnd结构体 44 + ------------------- 45 + 46 + 每个SCSI命令都由struct scsi_cmnd(简称scmd)结构体 47 + 表示。scmd包含两个list_head类型的链表节点:scmd->list 48 + 与scmd->eh_entry。其中scmd->list是用于空闲链表或设备 49 + 专属的scmd分配链表,与错误处理讨论关联不大。而 50 + scmd->eh_entry则是专用于命令完成和错误处理链表,除非 51 + 特别说明,本文讨论中所有scmd的链表操作均通过 52 + scmd->eh_entry实现。 53 + 54 + 55 + 1.2 scmd是如何完成的? 56 + ---------------------- 57 + 58 + 底层设备驱动(LLDD)在获取SCSI命令(scmd)后,存在两种 59 + 完成路径:底层驱动可通过调用hostt->queuecommand()时从 60 + 中间层传递的scsi_done回调函数主动完成命令,或者当命令未 61 + 及时完成时由块层(block layer)触发超时处理机制。 62 + 63 + 64 + 1.2.1 通过scsi_done回调完成SCSI命令 65 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 66 + 67 + 对于所有非错误处理(EH)命令,scsi_done()是其完成回调 68 + 函数。它只调用blk_mq_complete_request()来删除块层的 69 + 定时器并触发块设备软中断(BLOCK_SOFTIRQ)。 70 + 71 + BLOCK_SOFTIRQ会间接调用scsi_complete(),进而调用 72 + scsi_decide_disposition()来决定如何处理该命令。 73 + scsi_decide_disposition()会查看scmd->result值和感 74 + 应码数据来决定如何处理命令。 75 + 76 + - SUCCESS 77 + 78 + 调用scsi_finish_command()来处理该命令。该函数会 79 + 执行一些维护操作,然后调用scsi_io_completion()来 80 + 完成I/O操作。scsi_io_completion()会通过调用 81 + blk_end_request及其相关函数来通知块层该请求已完成, 82 + 如果发生错误,还会判断如何处理剩余的数据。 83 + 84 + - NEEDS_RETRY 85 + 86 + - ADD_TO_MLQUEUE 87 + 88 + scmd被重新加入到块设备队列中。 89 + 90 + - otherwise 91 + 92 + 调用scsi_eh_scmd_add(scmd)来处理该命令。 93 + 关于此函数的详细信息,请参见 [1-3]。 94 + 95 + 96 + 1.2.2 scmd超时完成机制 97 + ^^^^^^^^^^^^^^^^^^^^^^ 98 + 99 + SCSI命令超时处理机制由scsi_timeout()函数实现。 100 + 当发生超时事件时,该函数 101 + 102 + 1. 首先调用可选的hostt->eh_timed_out()回调函数。 103 + 返回值可能是以下3种情况之一: 104 + 105 + - ``SCSI_EH_RESET_TIMER`` 106 + 表示需要延长命令执行时间并重启计时器。 107 + 108 + - ``SCSI_EH_NOT_HANDLED`` 109 + 表示eh_timed_out()未处理该命令。 110 + 此时将执行第2步的处理流程。 111 + 112 + - ``SCSI_EH_DONE`` 113 + 表示eh_timed_out()已完成该命令。 114 + 115 + 2. 若未通过回调函数解决,系统将调用 116 + scsi_abort_command()发起异步中止操作,该操作最多 117 + 可执行scmd->allowed + 1次。但存在三种例外情况会跳 118 + 过异步中止而直接进入第3步处理:当检测到 119 + SCSI_EH_ABORT_SCHEDULED标志位已置位(表明该命令先 120 + 前已被中止过一次且当前重试仍失败)、当重试次数已达上 121 + 限、或当错误处理时限已到期时。在这些情况下,系统将跳 122 + 过异步中止流程而直接执行第3步处理方案。 123 + 124 + 3. 最终未解决的命令会通过scsi_eh_scmd_add(scmd)移交给 125 + 错误处理子系统,具体流程详见[1-4]章节说明。 126 + 127 + 1.3 异步命令中止机制 128 + -------------------- 129 + 130 + 当命令超时触发后,系统会通过scsi_abort_command()调度异 131 + 步中止操作。若中止操作执行成功,则根据重试次数决定后续处 132 + 理:若未达最大重试限制,命令将重新下发执行;若重试次数已 133 + 耗尽,则命令最终以DID_TIME_OUT状态终止。当中止操作失败 134 + 时,系统会调用scsi_eh_scmd_add()将该命令移交错误处理子 135 + 系统,具体处理流程详见[1-4]。 136 + 137 + 1.4 错误处理(EH)接管机制 138 + ------------------------ 139 + 140 + SCSI命令通过scsi_eh_scmd_add()函数进入错误处理流程,该函 141 + 数执行以下操作: 142 + 143 + 1. 将scmd->eh_entry链接到shost->eh_cmd_q 144 + 145 + 2. 在shost->shost_state中设置SHOST_RECOVERY状态位 146 + 147 + 3. 递增shost->host_failed失败计数器 148 + 149 + 4. 当检测到shost->host_busy == shost->host_failed 150 + 时(即所有进行中命令均已失败)立即唤醒SCSI错误处理 151 + 线程。 152 + 153 + 如上所述,当任一scmd被加入到shost->eh_cmd_q队列时,系统 154 + 会立即置位shost_state中的SHOST_RECOVERY状态标志位,该操 155 + 作将阻止块层向对应主机控制器下发任何新的SCSI命令。在此状 156 + 态下,主机控制器上所有正在处理的scmd最终会进入以下三种状 157 + 态之一:正常完成、失败后被移入到eh_cmd_q队列、或因超时被 158 + 添加到shost->eh_cmd_q队列。 159 + 160 + 如果所有的SCSI命令都已经完成或失败,系统中正在执行的命令 161 + 数量与失败命令数量相等( 162 + 即shost->host_busy == shost->host_failed),此时将唤 163 + 醒SCSI错误处理线程。SCSI错误处理线程一旦被唤醒,就可以确 164 + 保所有未完成命令均已标记为失败状态,并且已经被链接到 165 + shost->eh_cmd_q队列中。 166 + 167 + 需要特别说明的是,这并不意味着底层处理流程完全静止。当底层 168 + 驱动以错误状态完成某个scmd时,底层驱动及其下层组件会立刻遗 169 + 忘该命令的所有关联状态。但对于超时命令,除非 170 + hostt->eh_timed_out()回调函数已经明确通知底层驱动丢弃该 171 + 命令(当前所有底层驱动均未实现此功能),否则从底层驱动视角 172 + 看该命令仍处于活跃状态,理论上仍可能在某时刻完成。当然,由 173 + 于超时计时器早已触发,所有此类延迟完成都将被系统直接忽略。 174 + 175 + 我们将在后续章节详细讨论关于SCSI错误处理如何执行中止操作( 176 + 即强制底层驱动丢弃已超时SCSI命令)。 177 + 178 + 179 + 2. SCSI错误处理机制详解 180 + ======================= 181 + 182 + SCSI底层驱动可以通过以下两种方式之一来实现SCSI错误处理。 183 + 184 + - 细粒度的错误处理回调机制 185 + 底层驱动可选择实现细粒度的错误处理回调函数,由SCSI中间层 186 + 主导错误恢复流程并自动调用对应的回调函数。此实现模式的详 187 + 细设计规范在[2-1]节中展开讨论。 188 + 189 + - eh_strategy_handler()回调函数 190 + 该回调函数作为统一的错误处理入口,需要完整实现所有的恢复 191 + 操作。具体而言,它必须涵盖SCSI中间层在常规恢复过程中执行 192 + 的全部处理流程,相关实现将在[2-2]节中详细描述。 193 + 194 + 当错误恢复流程完成后,SCSI错误处理系统通过调用 195 + scsi_restart_operations()函数恢复正常运行,该函数按顺序执行 196 + 以下操作: 197 + 198 + 1. 验证是否需要执行驱动器安全门锁定机制 199 + 200 + 2. 清除shost_state中的SHOST_RECOVERY状态标志位 201 + 202 + 3. 唤醒所有在shost->host_wait上等待的任务。如果有人调用了 203 + scsi_block_when_processing_errors()则会发生这种情况。 204 + (疑问:由于错误处理期间块层队列已被阻塞,为何仍需显式 205 + 唤醒?) 206 + 207 + 4. 强制激活该主机控制器下所有设备的I/O队列 208 + 209 + 210 + 2.1 基于细粒度回调的错误处理机制 211 + -------------------------------- 212 + 213 + 2.1.1 概述 214 + ^^^^^^^^^^^ 215 + 216 + 如果不存在eh_strategy_handler(),SCSI中间层将负责驱动的 217 + 错误处理。错误处理(EH)的目标有两个:一是让底层驱动程序、 218 + 主机和设备不再维护已超时的SCSI命令(scmd);二是使他们准备 219 + 好接收新命令。当一个SCSI命令(scmd)被底层遗忘且底层已准备 220 + 好再次处理或拒绝该命令时,即可认为该scmd已恢复。 221 + 222 + 为实现这些目标,错误处理(EH)会逐步执行严重性递增的恢复 223 + 操作。部分操作通过下发SCSI命令完成,而其他操作则通过调用 224 + 以下细粒度的错误处理回调函数实现。这些回调函数可以省略, 225 + 若被省略则默认始终视为执行失败。 226 + 227 + :: 228 + 229 + int (* eh_abort_handler)(struct scsi_cmnd *); 230 + int (* eh_device_reset_handler)(struct scsi_cmnd *); 231 + int (* eh_bus_reset_handler)(struct scsi_cmnd *); 232 + int (* eh_host_reset_handler)(struct scsi_cmnd *); 233 + 234 + 只有在低级别的错误恢复操作无法恢复部分失败的SCSI命令 235 + (scmd)时,才会采取更高级别的恢复操作。如果最高级别的错误 236 + 处理失败,就意味着整个错误恢复(EH)过程失败,所有未能恢复 237 + 的设备被强制下线。 238 + 239 + 在恢复过程中,需遵循以下规则: 240 + 241 + - 错误恢复操作针对待处理列表eh_work_q中的失败的scmds执 242 + 行。如果某个恢复操作成功恢复了一个scmd,那么该scmd会 243 + 从eh_work_q链表中移除。 244 + 245 + 需要注意的是,对某个scmd执行的单个恢复操作可能会恢复 246 + 多个scmd。例如,对某个设备执行复位操作可能会恢复该设 247 + 备上所有失败的scmd。 248 + 249 + - 仅当低级别的恢复操作完成且eh_work_q仍然非空时,才会 250 + 触发更高级别的操作 251 + 252 + - SCSI错误恢复机制会重用失败的scmd来发送恢复命令。对于 253 + 超时的scmd,SCSI错误处理机制会确保底层驱动在重用scmd 254 + 前已不再维护该命令。 255 + 256 + 当一个SCSI命令(scmd)被成功恢复后,错误处理逻辑会通过 257 + scsi_eh_finish_cmd()将其从待处理队列(eh_work_q)移 258 + 至错误处理的本地完成队列(eh_done_q)。当所有scmd均恢 259 + 复完成(即eh_work_q为空时),错误处理逻辑会调用 260 + scsi_eh_flush_done_q()对这些已恢复的scmd进行处理,即 261 + 重新尝试或错误总终止(向上层通知失败)。 262 + 263 + SCSI命令仅在满足以下全部条件时才会被重试:对应的SCSI设 264 + 备仍处于在线状态,未设置REQ_FAILFAST标志或递增后的 265 + scmd->retries值仍小于scmd->allowed。 266 + 267 + 2.1.2 SCSI命令在错误处理过程中的流转路径 268 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 269 + 270 + 1. 错误完成/超时 271 + 272 + :处理: 调用scsi_eh_scmd_add()处理scmd 273 + 274 + - 将scmd添加到shost->eh_cmd_q 275 + - 设置SHOST_RECOVERY标记位 276 + - shost->host_failed++ 277 + 278 + :锁要求: shost->host_lock 279 + 280 + 2. 启动错误处理(EH) 281 + 282 + :操作: 将所有scmd移动到EH本地eh_work_q队列,并 283 + 清空 shost->eh_cmd_q。 284 + 285 + :锁要求: shost->host_lock(非严格必需,仅为保持一致性) 286 + 287 + 3. scmd恢复 288 + 289 + :操作: 调用scsi_eh_finish_cmd()完成scmd的EH 290 + 291 + - 将scmd从本地eh_work_q队列移至本地eh_done_q队列 292 + 293 + :锁要求: 无 294 + 295 + :并发控制: 每个独立的eh_work_q至多一个线程,确保无锁 296 + 队列的访问 297 + 298 + 4. EH完成 299 + 300 + :操作: 调用scsi_eh_flush_done_q()重试scmd或通知上层处理 301 + 失败。此函数可以被并发调用,但每个独立的eh_work_q队 302 + 列至多一个线程,以确保无锁队列的访问。 303 + 304 + - 从eh_done_q队列中移除scmd,清除scmd->eh_entry 305 + - 如果需要重试,调用scsi_queue_insert()重新入队scmd 306 + - 否则,调用scsi_finish_command()完成scmd 307 + - 将shost->host_failed置为零 308 + 309 + :锁要求: 队列或完成函数会执行适当的加锁操作 310 + 311 + 312 + 2.1.3 控制流 313 + ^^^^^^^^^^^^ 314 + 315 + 通过细粒度回调机制执行的SCSI错误处理(EH)是从 316 + scsi_unjam_host()函数开始的 317 + 318 + ``scsi_unjam_host`` 319 + 320 + 1. 持有shost->host_lock锁,将shost->eh_cmd_q中的命令移动 321 + 到本地的eh_work_q队里中,并释放host_lock锁。注意,这一步 322 + 会清空shost->eh_cmd_q。 323 + 324 + 2. 调用scsi_eh_get_sense函数。 325 + 326 + ``scsi_eh_get_sense`` 327 + 328 + 该操作针对没有有效感知数据的错误完成命令。大部分SCSI传输协议 329 + 或底层驱动在命令失败时会自动获取感知数据(自动感知)。出于性 330 + 能原因,建议使用自动感知,推荐使用自动感知机制,因为它不仅有 331 + 助于提升性能,还能避免从发生CHECK CONDITION到执行本操作之间, 332 + 感知信息出现不同步的问题。 333 + 334 + 注意,如果不支持自动感知,那么在使用scsi_done()以错误状态完成 335 + scmd 时,scmd->sense_buffer将包含无效感知数据。在这种情况下, 336 + scsi_decide_disposition()总是返回FAILED从而触发SCSI错误处理 337 + (EH)。当该scmd执行到这里时,会重新获取感知数据,并再次调用 338 + scsi_decide_disposition()进行处理。 339 + 340 + 1. 调用scsi_request_sense()发送REQUEST_SENSE命令。如果失败, 341 + 则不采取任何操作。请注意,不采取任何操作会导致对该scmd执行 342 + 更高级别的恢复操作。 343 + 344 + 2. 调用scsi_decide_disposition()处理scmd 345 + 346 + - SUCCESS 347 + scmd->retries被设置为scmd->allowed以防止 348 + scsi_eh_flush_done_q()重试该scmd,并调用 349 + scsi_eh_finish_cmd()。 350 + 351 + - NEEDS_RETRY 352 + 调用scsi_eh_finish_cmd() 353 + 354 + - 其他情况 355 + 无操作。 356 + 357 + 4. 如果!list_empty(&eh_work_q),则调用scsi_eh_ready_devs()。 358 + 359 + ``scsi_eh_ready_devs`` 360 + 361 + 该函数采取四种逐步增强的措施,使失败的设备准备好处理新的命令。 362 + 363 + 1. 调用scsi_eh_stu() 364 + 365 + ``scsi_eh_stu`` 366 + 367 + 对于每个具有有效感知数据且scsi_check_sense()判断为失败的 368 + scmd发送START STOP UNIT(STU)命令且将start置1。注意,由 369 + 于我们明确选择错误完成的scmd,可以确定底层驱动已不再维护该 370 + scmd,我们可以重用它进行STU。 371 + 372 + 如果STU操作成功且sdev处于离线或就绪状态,所有在sdev上失败的 373 + scmd都会通过scsi_eh_finish_cmd()完成。 374 + 375 + *注意* 如果hostt->eh_abort_handler()未实现或返回失败,可能 376 + 此时仍有超时的scmd,此时STU不会导致底层驱动不再维护scmd。但 377 + 是,如果STU执行成功,该函数会通过scsi_eh_finish_cmd()来完成 378 + sdev上的所有scmd,这会导致底层驱动处于不一致的状态。看来STU 379 + 操作应仅在sdev不包含超时scmd时进行。 380 + 381 + 2. 如果!list_empty(&eh_work_q),调用scsi_eh_bus_device_reset()。 382 + 383 + ``scsi_eh_bus_device_reset`` 384 + 385 + 此操作与scsi_eh_stu()非常相似,区别在于使用 386 + hostt->eh_device_reset_handler()替代STU命令。此外,由于我们 387 + 没有发送SCSI命令且重置会清空该sdev上所有的scmd,所以无需筛选错 388 + 误完成的scmd。 389 + 390 + 3. 如果!list_empty(&eh_work_q),调用scsi_eh_bus_reset()。 391 + 392 + ``scsi_eh_bus_reset`` 393 + 394 + 对于每个包含失败scmd的SCSI通道调用 395 + hostt->eh_bus_reset_handler()。如果总线重置成功,那么该通道上 396 + 所有准备就绪或离线状态sdev上的失败scmd都会被处理处理完成。 397 + 398 + 4. 如果!list_empty(&eh_work_q),调用scsi_eh_host_reset()。 399 + 400 + ``scsi_eh_host_reset`` 401 + 402 + 调用hostt->eh_host_reset_handler()是最终的手段。如果SCSI主机 403 + 重置成功,主机上所有就绪或离线sdev上的失败scmd都会通过错误处理 404 + 完成。 405 + 406 + 5. 如果!list_empty(&eh_work_q),调用scsi_eh_offline_sdevs()。 407 + 408 + ``scsi_eh_offline_sdevs`` 409 + 410 + 离线所有包含未恢复scmd的所有sdev,并通过 411 + scsi_eh_finish_cmd()完成这些scmd。 412 + 413 + 5. 调用scsi_eh_flush_done_q()。 414 + 415 + ``scsi_eh_flush_done_q`` 416 + 417 + 此时所有的scmd都已经恢复(或放弃),并通过 418 + scsi_eh_finish_cmd()函数加入eh_done_q队列。该函数通过 419 + 重试或显示通知上层scmd的失败来刷新eh_done_q。 420 + 421 + 422 + 2.2 基于transportt->eh_strategy_handler()的错误处理机制 423 + ------------------------------------------------------------- 424 + 425 + 在该机制中,transportt->eh_strategy_handler()替代 426 + scsi_unjam_host()的被调用,并负责整个错误恢复过程。该处理 427 + 函数完成后应该确保底层驱动不再维护任何失败的scmd并且将设备 428 + 设置为就绪(准备接收新命令)或离线状态。此外,该函数还应该 429 + 执行SCSI错误处理的维护任务,以维护SCSI中间层的数据完整性。 430 + 换句话说,eh_strategy_handler()必须实现[2-1-2]中除第1步 431 + 外的所有步骤。 432 + 433 + 434 + 2.2.1 transportt->eh_strategy_handler()调用前的SCSI中间层状态 435 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 436 + 437 + 进入该处理函数时,以下条件成立。 438 + 439 + - 每个失败的scmd的eh_flags字段已正确设置。 440 + 441 + - 每个失败的scmd通过scmd->eh_entry链接到scmd->eh_cmd_q队列。 442 + 443 + - 已设置SHOST_RECOVERY标志。 444 + 445 + - `shost->host_failed == shost->host_busy`。 446 + 447 + 2.2.2 transportt->eh_strategy_handler()调用后的SCSI中间层状态 448 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 449 + 450 + 从该处理函数退出时,以下条件成立。 451 + 452 + - shost->host_failed为零。 453 + 454 + - shost->eh_cmd_q被清空。 455 + 456 + - 每个scmd->eh_entry被清空。 457 + 458 + - 对每个scmd必须调用scsi_queue_insert()或scsi_finish_command()。 459 + 注意,该处理程序可以使用scmd->retries(剩余重试次数)和 460 + scmd->allowed(允许重试次数)限制重试次数。 461 + 462 + 463 + 2.2.3 注意事项 464 + ^^^^^^^^^^^^^^ 465 + 466 + - 需明确已超时的scmd在底层仍处于活跃状态,因此在操作这些 467 + scmd前,必须确保底层已彻底不再维护。 468 + 469 + - 访问或修改shost数据结构时,必须持有shost->host_lock锁 470 + 以维持数据一致性。 471 + 472 + - 错误处理完成后,每个故障设备必须彻底清除所有活跃SCSI命 473 + 令(scmd)的关联状态。 474 + 475 + - 错误处理完成后,每个故障设备必须被设置为就绪(准备接收 476 + 新命令)或离线状态。 477 + 478 + 479 + Tejun Heo 480 + htejun@gmail.com 481 + 482 + 11th September 2005
+1174
Documentation/translations/zh_CN/scsi/scsi_mid_low_api.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + .. include:: ../disclaimer-zh_CN.rst 3 + 4 + :Original: Documentation/scsi/scsi_mid_low_api.rst 5 + 6 + :翻译: 7 + 8 + 郝栋栋 doubled <doubled@leap-io-kernel.com> 9 + 10 + :校译: 11 + 12 + 13 + 14 + ========================= 15 + SCSI中间层 — 底层驱动接口 16 + ========================= 17 + 18 + 简介 19 + ==== 20 + 本文档概述了Linux SCSI中间层与SCSI底层驱动之间的接口。底层 21 + 驱动(LLD)通常被称为主机总线适配器(HBA)驱动或主机驱动 22 + (HD)。在该上下文中,“主机”指的是计算机IO总线(例如:PCI总 23 + 线或ISA总线)与SCSI传输层中单个SCSI启动器端口之间的桥梁。 24 + “启动器”端口(SCSI术语,参考SAM-3:http://www.t10.org)向 25 + “目标”SCSI端口(例如:磁盘)发送SCSI命令。在一个运行的系统 26 + 中存在多种底层驱动(LLDs),但每种硬件类型仅对应一种底层驱动 27 + (LLD)。大多数底层驱动可以控制一个或多个SCSI HBA。部分HBA 28 + 内部集成多个主机控制器。 29 + 30 + 在某些情况下,SCSI传输层本身是已存在于Linux中的外部总线子系 31 + 统(例如:USB和ieee1394)。在此类场景下,SCSI子系统的底层驱 32 + 动将作为与其他驱动子系统的软件桥接层。典型示例包括 33 + usb-storage驱动(位于drivers/usb/storage目录)以 34 + 及ieee1394/sbp2驱动(位于 drivers/ieee1394 目录)。 35 + 36 + 例如,aic7xxx底层驱动负责控制基于Adaptec公司7xxx芯片系列的 37 + SCSI并行接口(SPI)控制器。aic7xxx底层驱动可以内建到内核中 38 + 或作为模块加载。一个Linux系统中只能运行一个aic7xxx底层驱动 39 + 程序,但他可能控制多个主机总线适配器(HBA)。这些HBA可能位于 40 + PCI扩展卡或内置于主板中(或两者兼有)。某些基于aic7xxx的HBA 41 + 采用双控制器设计,因此会呈现为两个SCSI主机适配器。与大多数现 42 + 代HBA相同,每个aic7xxx控制器都拥有其独立的PCI设备地址。[SCSI 43 + 主机与PCI设备之间一一对应虽然常见,但并非强制要求(例如ISA适 44 + 配器就不适用此规则)。] 45 + 46 + SCSI中间层将SCSI底层驱动(LLD)与其他层(例如SCSI上层驱动以 47 + 及块层)隔离开来。 48 + 49 + 本文档的版本大致与Linux内核2.6.8相匹配。 50 + 51 + 文档 52 + ==== 53 + 内核源码树中设有专用的SCSI文档目录,通常位于 54 + Documentation/scsi目录下。大多数文档采用 55 + reStructuredText格式。本文档名为 56 + scsi_mid_low_api.rst,可在该目录中找到。该文档的最新版本可 57 + 以访问 https://docs.kernel.org/scsi/scsi_mid_low_api.html 58 + 查阅。许多底层驱动(LLD)的文档也位于Documentation/scsi目录 59 + 下(例如aic7xxx.rst)。SCSI中间层的简要说明见scsi.rst文件, 60 + 该文档包含指向Linux Kernel 2.4系列SCSI子系统的文档链接。此 61 + 外还收录了两份SCSI上层驱动文档:st.rst(SCSI磁带驱动)与 62 + scsi-generic.rst(用通用SCSI(sg)驱动)。 63 + 64 + 部分底层驱动的文档(或相关URL)可能嵌在C源代码文件或与其 65 + 源码同位于同一目录下。例如,USB大容量存储驱动的文档链接可以在 66 + 目录/usr/src/linux/drivers/usb/storage下找到。 67 + 68 + 驱动程序结构 69 + ============ 70 + 传统上,SCSI子系统的底层驱动(LLD)至少包含drivers/scsi 71 + 目录下的两个文件。例如,一个名为“xyz”的驱动会包含一个头文件 72 + xyz.h和一个源文件xyz.c。[实际上所有代码完全可以合并为单个 73 + 文件,头文件并非必需的。] 部分需要跨操作系统移植的底层驱动会 74 + 采用更复杂的文件结构。例如,aic7xxx驱动,就为通用代码与操作 75 + 系统专用代码(如FreeBSD和Linux)分别创建了独立的文件。此类 76 + 驱动通常会在drivers/scsi目录下拥有自己单独的子目录。 77 + 78 + 当需要向Linux内核添加新的底层驱动(LLD)时,必须留意 79 + drivers/scsi目录下的两个文件:Makefile以及Kconfig。建议参 80 + 考现有底层驱动的代码组织方式。 81 + 82 + 随着Linux内核2.5开发内核逐步演进为2.6系列的生产版本,该接口 83 + 也引入了一些变化。以驱动初始化代码为例,现有两种模型可用。其 84 + 中旧模型与Linux内核2.4的实现相似,他基于在加载HBA驱动时检测 85 + 到的主机,被称为“被动(passive)”初始化模型。而新的模型允许 86 + 在底层驱动(LLD)的生命周期内动态拔插HBA,这种方式被称为“热 87 + 插拔(hotplug)”初始化模型。推荐使用新的模型,因为他既能处理 88 + 传统的永久连接SCSI设备,也能处理现代支持热插拔的类SCSI设备 89 + (例如通过USB或IEEE 1394连接的数码相机)。这两种初始化模型将 90 + 在后续的章节中分别讨论。 91 + 92 + SCSI底层驱动(LLD)通过以下3种方式与SCSI子系统进行交互: 93 + 94 + a) 直接调用由SCSI中间层提供的接口函数 95 + b) 将一组函数指针传递给中间层提供的注册函数,中间层将在 96 + 后续运行的某个时刻调用这些函数。这些函数由LLD实现。 97 + c) 直接访问中间层维护的核心数据结构 98 + 99 + a)组中所涉及的所有函数,均列于下文“中间层提供的函数”章节中。 100 + 101 + b)组中涉及的所有函数均列于下文名为“接口函数”的章节中。这些 102 + 函数指针位于结构体struct scsi_host_template中,该结构体实 103 + 例会被传递给scsi_host_alloc()。对于LLD未实现的接口函数,应 104 + 对struct scsi_host_template中的对应成员赋NULL。如果在文件 105 + 作用域定义一个struct scsi_host_template的实例,没有显式初 106 + 始化的函数指针成员将自动设置为NULL。 107 + 108 + c)组中提到的用法在“热插拔”环境中尤其需要谨慎处理。LLD必须 109 + 明确知晓这些与中间层及其他层级共享的数据结构的生命周期。 110 + 111 + LLD中定义的所有函数以及在文件作用域内定义的所有数据都应声明 112 + 为static。例如,在一个名为“xxx”的LLD中的sdev_init()函数定 113 + 义如下: 114 + ``static int xxx_sdev_init(struct scsi_device * sdev) { /* code */ }`` 115 + 116 + 热插拔初始化模型 117 + ================ 118 + 在该模型中,底层驱动(LLD)控制SCSI主机适配器在子系统中的注 119 + 册与注销时机。主机最早可以在驱动初始化阶段被注册,最晚可以在 120 + 驱动卸载时被移除。通常,驱动会响应来自sysfs probe()的回调, 121 + 表示已检测到一个主机总线适配器(HBA)。在确认该新设备是LLD的 122 + 目标设备后,LLD初始化HBA,并将一个新的SCSI主机适配器注册到 123 + SCSI中间层。 124 + 125 + 在LLD初始化过程中,驱动应当向其期望发现HBA的IO总线(例如PCI 126 + 总线)进行注册。该操作通常可以通过sysfs完成。任何驱动参数( 127 + 特别是那些在驱动加载后仍可修改的参数)也可以在此时通过sysfs 128 + 注册。当LLD注册其首个HBA时,SCSI中间层首次感受到该LLD的存在。 129 + 130 + 在稍后的某个时间点,当LLD检测到新的HBA时,接下来在LLD与SCSI 131 + 中间层之间会发生一系列典型的调用过程。该示例展示了中间层如何 132 + 扫描新引入的HBA,在该过程中发现了3个SCSI设备,其中只有前两个 133 + 设备有响应:: 134 + 135 + HBA探测:假设在扫描中发现2个SCSI设备 136 + 底层驱动 中间层 底层驱动 137 + =======---------------======---------------======= 138 + scsi_host_alloc() --> 139 + scsi_add_host() ----> 140 + scsi_scan_host() -------+ 141 + | 142 + sdev_init() 143 + sdev_configure() --> scsi_change_queue_depth() 144 + | 145 + sdev_init() 146 + sdev_configure() 147 + | 148 + sdev_init() *** 149 + sdev_destroy() *** 150 + 151 + 152 + *** 对于SCSI中间层尝试扫描但未响应的SCSI设备,系统调用 153 + sdev_init()和sdev_destroy()函数对。 154 + 155 + 如果LLD期望调整默认队列设置,可以在其sdev_configure()例程 156 + 中调用scsi_change_queue_depth()。 157 + 158 + 当移除一个HBA时,可能是由于卸载LLD模块相关的有序关闭(例如通 159 + 过rmmod命令),也可能是由于sysfs的remove()回调而触发的“热拔 160 + 插”事件。无论哪种情况,其执行顺序都是相同的:: 161 + 162 + HBA移除:假设连接了2个SCSI设备 163 + 底层驱动 中间层 底层驱动 164 + =======---------------------======-----------------======= 165 + scsi_remove_host() ---------+ 166 + | 167 + sdev_destroy() 168 + sdev_destroy() 169 + scsi_host_put() 170 + 171 + LLD用于跟踪struct Scsi_Host的实例可能会非常有用 172 + (scsi_host_alloc()返回的指针)。这些实例由中间层“拥有”。 173 + 当引用计数为零时,struct Scsi_Host实例会被 174 + scsi_host_put()释放。 175 + 176 + HBA的热插拔是一个特殊的场景,特别是当HBA下的磁盘正在处理已挂 177 + 载文件系统上的SCSI命令时。为了应对其中的诸多问题,中间层引入 178 + 了引用计数逻辑。具体内容参考下文关于引用计数的章节。 179 + 180 + 热插拔概念同样适用于SCSI设备。目前,当添加HBA时, 181 + scsi_scan_host() 函数会扫描该HBA所属SCSI传输通道上的设备。在 182 + 新型SCSI传输协议中,HBA可能在扫描完成后才检测到新的SCSI设备。 183 + LLD可通过以下步骤通知中间层新SCSI设备的存在:: 184 + 185 + SCSI设备热插拔 186 + 底层驱动 中间层 底层驱动 187 + =======-------------------======-----------------======= 188 + scsi_add_device() ------+ 189 + | 190 + sdev_init() 191 + sdev_configure() [--> scsi_change_queue_depth()] 192 + 193 + 类似的,LLD可能会感知到某个SCSI设备已经被移除(拔出)或与他的连 194 + 接已中断。某些现有的SCSI传输协议(例如SPI)可能直到后续SCSI命令 195 + 执行失败时才会发现设备已经被移除,中间层会将该设备设置为离线状态。 196 + 若LLD检测到SCSI设备已经被移除,可通过以下流程触发上层对该设备的 197 + 移除操作:: 198 + 199 + SCSI设备热拔插 200 + 底层驱动 中间层 底层驱动 201 + =======-------------------======-----------------======= 202 + scsi_remove_device() -------+ 203 + | 204 + sdev_destroy() 205 + 206 + 对于LLD而言,跟踪struct scsi_device实例可能会非常有用(该结构 207 + 的指针会作为参数传递给sdev_init()和sdev_configure()回调函数)。 208 + 这些实例的所有权归属于中间层(mid-level)。struct scsi_device 209 + 实例在sdev_destroy()执行后释放。 210 + 211 + 引用计数 212 + ======== 213 + Scsi_Host结构体已引入引用计数机制。该机制将struct Scsi_Host 214 + 实例的所有权分散到使用他的各SCSI层,而此前这类实例完全由中间 215 + 层独占管理。底层驱动(LLD)通常无需直接操作这些引用计数,仅在 216 + 某些特定场景下可能需要介入。 217 + 218 + 与struct Scsi_Host相关的引用计数函数主要有以下3种: 219 + 220 + - scsi_host_alloc(): 221 + 返回指向新实例的指针,该实例的引用计数被设置为1。 222 + 223 + - scsi_host_get(): 224 + 给定实例的引用计数加1。 225 + 226 + - scsi_host_put(): 227 + 给定实例的引用计数减1。如果引用计数减少到0,则释放该实例。 228 + 229 + scsi_device结构体现已引入引用计数机制。该机制将 230 + struct scsi_device实例的所有权分散到使用他的各SCSI层,而此 231 + 前这类实例完全由中间层独占管理。相关访问函数声明详见 232 + include/scsi/scsi_device.h文件末尾部分。若LLD需要保留 233 + scsi_device实例的指针副本,则应调用scsi_device_get()增加其 234 + 引用计数;不再需要该指针时,可通过scsi_device_put()递减引用 235 + 计数(该操作可能会导致该实例被释放)。 236 + 237 + .. Note:: 238 + 239 + struct Scsi_Host实际上包含两个并行维护的引用计数器,该引 240 + 用计数由这些函数共同操作。 241 + 242 + 编码规范 243 + ======== 244 + 245 + 首先,Linus Torvalds关于C语言编码风格的观点可以在 246 + Documentation/process/coding-style.rst文件中找到。 247 + 248 + 此外,在相关gcc编译器支持的前提下,鼓励使用大多数C99标准的增强 249 + 特性。因此,在适当的情况下鼓励使用C99风格的结构体和数组初始化 250 + 方式。但不要过度使用,目前对可变长度数组(VLA)的支持还待完善。 251 + 一个例外是 ``//`` 风格的注释;在Linux中倾向于使 252 + 用 ``/*...*/`` 注释格式。 253 + 254 + 对于编写良好、经过充分测试且有完整文档的代码不需要重新格式化 255 + 以符合上述规范。例如,aic7xxx驱动是从FreeBSD和Adaptec代码库 256 + 移植到Linux的。毫无疑问,FreeBSD和Adaptec遵循其原有的编码规 257 + 范。 258 + 259 + 260 + 中间层提供的函数 261 + ================ 262 + 这些函数由SCSI中间层提供,供底层驱动(LLD)调用。这些函数的名 263 + 称(即入口点)均已导出,因此作为模块加载的LLD可以访问他们。内 264 + 核会确保在任何LLD初始化之前,SCSI中间层已先行加载并完成初始化。 265 + 下文按字母顺序列出这些函数,其名称均以 ``scsi_`` 开头。 266 + 267 + 摘要: 268 + 269 + - scsi_add_device - 创建新的SCSI逻辑单元(LU)设备实例 270 + - scsi_add_host - 执行sysfs注册并设置传输类 271 + - scsi_change_queue_depth - 调整SCSI设备队列深度 272 + - scsi_bios_ptable - 返回块设备分区表的副本 273 + - scsi_block_requests - 阻止向指定主机提交新命令 274 + - scsi_host_alloc - 分配引用计数为1的新SCSI主机适配器实例scsi_host 275 + - scsi_host_get - 增加SCSI主机适配器实例的引用计数 276 + - scsi_host_put - 减少SCSI主机适配器的引用计数(归零时释放) 277 + - scsi_remove_device - 卸载并移除SCSI设备 278 + - scsi_remove_host - 卸载并移除主机控制器下的所有SCSI设备 279 + - scsi_report_bus_reset - 报告检测到的SCSI总线复位事件 280 + - scsi_scan_host - 执行SCSI总线扫描 281 + - scsi_track_queue_full - 跟踪连续出现的队列满事件 282 + - scsi_unblock_requests - 恢复向指定主机提交命令 283 + 284 + 详细信息:: 285 + 286 + /** 287 + * scsi_add_device - 创建新的SCSI逻辑单元(LU)设备实例 288 + * @shost: 指向SCSI主机适配器实例的指针 289 + * @channel: 通道号(通常为0) 290 + * @id: 目标ID号 291 + * @lun: 逻辑单元号(LUN) 292 + * 293 + * 返回指向新的struct scsi_device实例的指针, 294 + * 如果出现异常(例如在给定地址没有设备响应),则返 295 + * 回ERR_PTR(-ENODEV) 296 + * 297 + * 是否阻塞:是 298 + * 299 + * 注意事项:本函数通常在添加HBA的SCSI总线扫描过程 300 + * 中由系统内部调用(即scsi_scan_host()执行期间)。因此, 301 + * 仅应在以下情况调用:HBA在scsi_scan_host()完成扫描后, 302 + * 又检测到新的SCSI设备(逻辑单元)。若成功执行,本次调用 303 + * 可能会触发LLD的以下回调函数:sdev_init()以及 304 + * sdev_configure() 305 + * 306 + * 函数定义:drivers/scsi/scsi_scan.c 307 + **/ 308 + struct scsi_device * scsi_add_device(struct Scsi_Host *shost, 309 + unsigned int channel, 310 + unsigned int id, unsigned int lun) 311 + 312 + 313 + /** 314 + * scsi_add_host - 执行sysfs注册并设置传输类 315 + * @shost: 指向SCSI主机适配器实例的指针 316 + * @dev: 指向scsi类设备结构体(struct device)的指针 317 + * 318 + * 成功返回0,失败返回负的errno(例如:-ENOMEM) 319 + * 320 + * 是否阻塞:否 321 + * 322 + * 注意事项:仅在“热插拔初始化模型”中需要调用,且必须在 323 + * scsi_host_alloc()成功执行后调用。该函数不会扫描总线; 324 + * 总线扫描可通过调用scsi_scan_host()或其他传输层特定的 325 + * 方法完成。在调用该函数之前,LLD必须先设置好传输模板, 326 + * 并且只能在调用该函数之后才能访问传输类 327 + * (transport class)相关的数据结构。 328 + * 329 + * 函数定义:drivers/scsi/hosts.c 330 + **/ 331 + int scsi_add_host(struct Scsi_Host *shost, struct device * dev) 332 + 333 + 334 + /** 335 + * scsi_change_queue_depth - 调整SCSI设备队列深度 336 + * @sdev: 指向要更改队列深度的SCSI设备的指针 337 + * @tags 如果启用了标记队列,则表示允许的标记数, 338 + * 或者在非标记模式下,LLD可以排队的命令 339 + * 数(如 cmd_per_lun)。 340 + * 341 + * 无返回 342 + * 343 + * 是否阻塞:否 344 + * 345 + * 注意事项:可以在任何时刻调用该函数,只要该SCSI设备受该LLD控 346 + * 制。[具体来说,可以在sdev_configure()执行期间或之后,且在 347 + * sdev_destroy()执行之前调用。] 该函数可安全地在中断上下文中 348 + * 调用。 349 + * 350 + * 函数定义:drivers/scsi/scsi.c [更多注释请参考源代码] 351 + **/ 352 + int scsi_change_queue_depth(struct scsi_device *sdev, int tags) 353 + 354 + 355 + /** 356 + * scsi_bios_ptable - 返回块设备分区表的副本 357 + * @dev: 指向块设备的指针 358 + * 359 + * 返回指向分区表的指针,失败返回NULL 360 + * 361 + * 是否阻塞:是 362 + * 363 + * 注意事项:调用方负责释放返回的内存(通过 kfree() 释放) 364 + * 365 + * 函数定义:drivers/scsi/scsicam.c 366 + **/ 367 + unsigned char *scsi_bios_ptable(struct block_device *dev) 368 + 369 + 370 + /** 371 + * scsi_block_requests - 阻止向指定主机提交新命令 372 + * 373 + * @shost: 指向特定主机的指针,用于阻止命令的发送 374 + * 375 + * 无返回 376 + * 377 + * 是否阻塞:否 378 + * 379 + * 注意事项:没有定时器或其他任何机制可以解除阻塞,唯一的方式 380 + * 是由LLD调用scsi_unblock_requests()方可恢复。 381 + * 382 + * 函数定义:drivers/scsi/scsi_lib.c 383 + **/ 384 + void scsi_block_requests(struct Scsi_Host * shost) 385 + 386 + 387 + /** 388 + * scsi_host_alloc - 创建SCSI主机适配器实例并执行基础初始化 389 + * @sht: 指向SCSI主机模板的指针 390 + * @privsize: 在hostdata数组中分配的额外字节数(该数组是返 391 + * 回的Scsi_Host实例的最后一个成员) 392 + * 393 + * 返回指向新的Scsi_Host实例的指针,失败返回NULL 394 + * 395 + * 是否阻塞:是 396 + * 397 + * 注意事项:当此调用返回给LLD时,该主机适配器上的 398 + * SCSI总线扫描尚未进行。hostdata数组(默认长度为 399 + * 零)是LLD专属的每主机私有区域,供LLD独占使用。 400 + * 两个相关的引用计数都被设置为1。完整的注册(位于 401 + * sysfs)与总线扫描由scsi_add_host()和 402 + * scsi_scan_host()稍后执行。 403 + * 函数定义:drivers/scsi/hosts.c 404 + **/ 405 + struct Scsi_Host * scsi_host_alloc(const struct scsi_host_template * sht, 406 + int privsize) 407 + 408 + 409 + /** 410 + * scsi_host_get - 增加SCSI主机适配器实例的引用计数 411 + * @shost: 指向Scsi_Host实例的指针 412 + * 413 + * 无返回 414 + * 415 + * 是否阻塞:目前可能会阻塞,但可能迭代为不阻塞 416 + * 417 + * 注意事项:会同时增加struct Scsi_Host中两个子对 418 + * 象的引用计数 419 + * 420 + * 函数定义:drivers/scsi/hosts.c 421 + **/ 422 + void scsi_host_get(struct Scsi_Host *shost) 423 + 424 + 425 + /** 426 + * scsi_host_put - 减少SCSI主机适配器实例的引用计数 427 + * (归零时释放) 428 + * @shost: 指向Scsi_Host实例的指针 429 + * 430 + * 无返回 431 + * 432 + * 是否阻塞:当前可能会阻塞,但可能会改为不阻塞 433 + * 434 + * 注意事项:实际会递减两个子对象中的计数。当后一个引用 435 + * 计数归零时系统会自动释放Scsi_Host实例。 436 + * LLD 无需关注Scsi_Host实例的具体释放时机,只要在平衡 437 + * 引用计数使用后不再访问该实例即可。 438 + * 函数定义:drivers/scsi/hosts.c 439 + **/ 440 + void scsi_host_put(struct Scsi_Host *shost) 441 + 442 + 443 + /** 444 + * scsi_remove_device - 卸载并移除SCSI设备 445 + * @sdev: 指向SCSI设备实例的指针 446 + * 447 + * 返回值:成功返回0,若设备未连接,则返回-EINVAL 448 + * 449 + * 是否阻塞:是 450 + * 451 + * 如果LLD发现某个SCSI设备(逻辑单元,lu)已经被移除, 452 + * 但其主机适配器实例依旧存在,则可以请求移除该SCSI设备。 453 + * 如果该调用成功将触发sdev_destroy()回调函数的执行。调 454 + * 用完成后,sdev将变成一个无效的指针。 455 + * 456 + * 函数定义:drivers/scsi/scsi_sysfs.c 457 + **/ 458 + int scsi_remove_device(struct scsi_device *sdev) 459 + 460 + 461 + /** 462 + * scsi_remove_host - 卸载并移除主机控制器下的所有SCSI设备 463 + * @shost: 指向SCSI主机适配器实例的指针 464 + * 465 + * 返回值:成功返回0,失败返回1(例如:LLD正忙??) 466 + * 467 + * 是否阻塞:是 468 + * 469 + * 注意事项:仅在使用“热插拔初始化模型”时调用。应在调用 470 + * scsi_host_put()前调用。 471 + * 472 + * 函数定义:drivers/scsi/hosts.c 473 + **/ 474 + int scsi_remove_host(struct Scsi_Host *shost) 475 + 476 + 477 + /** 478 + * scsi_report_bus_reset - 报告检测到的SCSI总线复位事件 479 + * @shost: 指向关联的SCSI主机适配器的指针 480 + * @channel: 发生SCSI总线复位的通道号 481 + * 482 + * 返回值:无 483 + * 484 + * 是否阻塞:否 485 + * 486 + * 注意事项:仅当复位来自未知来源时才需调用此函数。 487 + * 由SCSI中间层发起的复位无需调用,但调用也不会导 488 + * 致副作用。此函数的主要作用是确保系统能正确处理 489 + * CHECK_CONDITION状态。 490 + * 491 + * 函数定义:drivers/scsi/scsi_error.c 492 + **/ 493 + void scsi_report_bus_reset(struct Scsi_Host * shost, int channel) 494 + 495 + 496 + /** 497 + * scsi_scan_host - 执行SCSI总线扫描 498 + * @shost: 指向SCSI主机适配器实例的指针 499 + * 500 + * 是否阻塞:是 501 + * 502 + * 注意事项:应在调用scsi_add_host()后调用 503 + * 504 + * 函数定义:drivers/scsi/scsi_scan.c 505 + **/ 506 + void scsi_scan_host(struct Scsi_Host *shost) 507 + 508 + 509 + /** 510 + * scsi_track_queue_full - 跟踪指定设备上连续的QUEUE_FULL 511 + * 事件,以判断是否需要及何时调整 512 + * 该设备的队列深度。 513 + * @sdev: 指向SCSI设备实例的指针 514 + * @depth: 当前该设备上未完成的SCSI命令数量(不包括返回 515 + * QUEUE_FULL的命令) 516 + * 517 + * 返回值:0 - 当前队列深度无需调整 518 + * >0 - 需要将队列深度调整为此返回值指定的新深度 519 + * -1 - 需要回退到非标记操作模式,并使用 520 + * host->cmd_per_lun作为非标记命令队列的 521 + * 深度限制 522 + * 523 + * 是否阻塞:否 524 + * 525 + * 注意事项:LLD可以在任意时刻调用该函数。系统将自动执行“正确 526 + * 的处理流程”;该函数支持在中断上下文中安全地调用 527 + * 528 + * 函数定义:drivers/scsi/scsi.c 529 + **/ 530 + int scsi_track_queue_full(struct scsi_device *sdev, int depth) 531 + 532 + 533 + /** 534 + * scsi_unblock_requests - 恢复向指定主机适配器提交命令 535 + * 536 + * @shost: 指向要解除阻塞的主机适配器的指针 537 + * 538 + * 返回值:无 539 + * 540 + * 是否阻塞:否 541 + * 542 + * 函数定义:drivers/scsi/scsi_lib.c 543 + **/ 544 + void scsi_unblock_requests(struct Scsi_Host * shost) 545 + 546 + 547 + 548 + 接口函数 549 + ======== 550 + 接口函数由底层驱动(LLD)定义实现,其函数指针保存在 551 + struct scsi_host_template实例中,并将该实例传递给 552 + scsi_host_alloc()。 553 + 部分接口函数为必选实现项。所有 554 + 接口函数都应声明为static,约定俗成的命名规则如下, 555 + 驱动“xyz”应将其sdev_configure()函数声明为:: 556 + 557 + static int xyz_sdev_configure(struct scsi_device * sdev); 558 + 559 + 其余接口函数的命名规范均依此类推。 560 + 561 + 需将该函数指针赋值给“struct scsi_host_template”实例 562 + 的‘sdev_configure’成员变量中,并将该结构体实例指针传 563 + 递到中间层的scsi_host_alloc()函数。 564 + 565 + 各个接口函数的详细说明可参考include/scsi/scsi_host.h 566 + 文件,具体描述位于“struct scsi_host_template”结构体 567 + 各个成员的上方。在某些情况下,scsi_host.h头文件中的描 568 + 述比本文提供的更为详尽。 569 + 570 + 以下按字母顺序列出所有接口函数及其说明。 571 + 572 + 摘要: 573 + 574 + - bios_param - 获取磁盘的磁头/扇区/柱面参数 575 + - eh_timed_out - SCSI命令超时回调 576 + - eh_abort_handler - 中止指定的SCSI命令 577 + - eh_bus_reset_handler - 触发SCSI总线复位 578 + - eh_device_reset_handler - 执行SCSI设备复位 579 + - eh_host_reset_handler - 复位主机(主机总线适配器) 580 + - info - 提供指定主机适配器的相关信息 581 + - ioctl - 驱动可响应ioctl控制命令 582 + - proc_info - 支持/proc/scsi/{驱动名}/{主机号}文件节点的读写操作 583 + - queuecommand - 将SCSI命令提交到主机控制器,命令执行完成后调用‘done’回调 584 + - sdev_init - 在向新设备发送SCSI命令前的初始化 585 + - sdev_configure - 设备挂载后的精细化微调 586 + - sdev_destroy - 设备即将被移除前的清理 587 + 588 + 589 + 详细信息:: 590 + 591 + /** 592 + * bios_param - 获取磁盘的磁头/扇区/柱面参数 593 + * @sdev: 指向SCSI设备实例的指针(定义于 594 + * include/scsi/scsi_device.h中) 595 + * @bdev: 指向块设备实例的指针(定义于fs.h中) 596 + * @capacity: 设备容量(以512字节扇区为单位) 597 + * @params: 三元数组用于保存输出结果: 598 + * params[0]:磁头数量(最大255) 599 + * params[1]:扇区数量(最大63) 600 + * params[2]:柱面数量 601 + * 602 + * 返回值:被忽略 603 + * 604 + * 并发安全声明: 无锁 605 + * 606 + * 调用上下文说明: 进程上下文(sd) 607 + * 608 + * 注意事项: 若未提供此函数,系统将基于READ CAPACITY 609 + * 使用默认几何参数。params数组已预初始化伪值,防止函 610 + * 数无输出。 611 + * 612 + * 可选实现说明:由LLD选择性定义 613 + **/ 614 + int bios_param(struct scsi_device * sdev, struct block_device *bdev, 615 + sector_t capacity, int params[3]) 616 + 617 + 618 + /** 619 + * eh_timed_out - SCSI命令超时回调 620 + * @scp: 标识超时的命令 621 + * 622 + * 返回值: 623 + * 624 + * EH_HANDLED: 我已修复该错误,请继续完成该命令 625 + * EH_RESET_TIMER: 我需要更多时间,请重置定时器并重新开始计时 626 + * EH_NOT_HANDLED 开始正常的错误恢复流程 627 + * 628 + * 并发安全声明: 无锁 629 + * 630 + * 调用上下文说明: 中断上下文 631 + * 632 + * 注意事项: 该回调函数为LLD提供一个机会进行本地 633 + * 错误恢复处理。此处的恢复仅限于判断该未完成的命 634 + * 令是否还有可能完成。此回调中不允许中止或重新启 635 + * 动该命令。 636 + * 637 + * 可选实现说明:由LLD选择性定义 638 + **/ 639 + int eh_timed_out(struct scsi_cmnd * scp) 640 + 641 + 642 + /** 643 + * eh_abort_handler - 中止指定的SCSI命令 644 + * @scp: 标识要中止的命令 645 + * 646 + * 返回值:如果命令成功中止,则返回SUCCESS,否则返回FAILED 647 + * 648 + * 并发安全声明: 无锁 649 + * 650 + * 调用上下文说明: 内核线程 651 + * 652 + * 注意事项: 该函数仅在命令超时时才被调用。 653 + * 654 + * 可选实现说明:由LLD选择性定义 655 + **/ 656 + int eh_abort_handler(struct scsi_cmnd * scp) 657 + 658 + 659 + /** 660 + * eh_bus_reset_handler - 发起SCSI总线复位 661 + * @scp: 包含该设备的SCSI总线应进行重置 662 + * 663 + * 返回值:重置成功返回SUCCESS;否则返回FAILED 664 + * 665 + * 并发安全声明: 无锁 666 + * 667 + * 调用上下文说明: 内核线程 668 + * 669 + * 注意事项: 由SCSI错误处理线程(scsi_eh)调用。 670 + * 在错误处理期间,当前主机适配器的所有IO请求均 671 + * 被阻塞。 672 + * 673 + * 可选实现说明:由LLD选择性定义 674 + **/ 675 + int eh_bus_reset_handler(struct scsi_cmnd * scp) 676 + 677 + 678 + /** 679 + * eh_device_reset_handler - 发起SCSI设备复位 680 + * @scp: 指定将被重置的SCSI设备 681 + * 682 + * 返回值:如果命令成功中止返回SUCCESS,否则返回FAILED 683 + * 684 + * 并发安全声明: 无锁 685 + * 686 + * 调用上下文说明: 内核线程 687 + * 688 + * 注意事项: 由SCSI错误处理线程(scsi_eh)调用。 689 + * 在错误处理期间,当前主机适配器的所有IO请求均 690 + * 被阻塞。 691 + * 692 + * 可选实现说明:由LLD选择性定义 693 + **/ 694 + int eh_device_reset_handler(struct scsi_cmnd * scp) 695 + 696 + 697 + /** 698 + * eh_host_reset_handler - 复位主机(主机总线适配器) 699 + * @scp: 管理该设备的SCSI主机适配器应该被重置 700 + * 701 + * 返回值:如果命令成功中止返回SUCCESS,否则返回FAILED 702 + * 703 + * 并发安全声明: 无锁 704 + * 705 + * 调用上下文说明: 内核线程 706 + * 707 + * 注意事项: 由SCSI错误处理线程(scsi_eh)调用。 708 + * 在错误处理期间,当前主机适配器的所有IO请求均 709 + * 被阻塞。当使用默认的eh_strategy策略时,如果 710 + * _abort_、_device_reset_、_bus_reset_和该处 711 + * 理函数均未定义(或全部返回FAILED),系统强制 712 + * 该故障设备处于离线状态 713 + * 714 + * 可选实现说明:由LLD选择性定义 715 + **/ 716 + int eh_host_reset_handler(struct scsi_cmnd * scp) 717 + 718 + 719 + /** 720 + * info - 提供给定主机适配器的详细信息:驱动程序名称 721 + * 以及用于区分不同主机适配器的数据结构 722 + * @shp: 指向目标主机的struct Scsi_Host实例 723 + * 724 + * 返回值:返回以NULL结尾的ASCII字符串。[驱动 725 + * 负责管理返回的字符串所在内存并确保其在整个 726 + * 主机适配器生命周期内有效。] 727 + * 728 + * 并发安全声明: 无锁 729 + * 730 + * 调用上下文说明: 进程上下文 731 + * 732 + * 注意事项: 通常提供诸如I/O地址或中断号 733 + * 等PCI或ISA信息。如果未实现该函数,则 734 + * 默认使用struct Scsi_Host::name 字段。 735 + * 返回的字符串应为单行(即不包含换行符)。 736 + * 通过SCSI_IOCTL_PROBE_HOST ioctl可获 737 + * 取该函数返回的字符串,如果该函数不可用, 738 + * 则ioctl返回struct Scsi_Host::name中 739 + * 的字符串。 740 + 741 + * 742 + * 可选实现说明:由LLD选择性定义 743 + **/ 744 + const char * info(struct Scsi_Host * shp) 745 + 746 + 747 + /** 748 + * ioctl - 驱动可响应ioctl控制命令 749 + * @sdp: ioctl操作针对的SCSI设备 750 + * @cmd: ioctl命令号 751 + * @arg: 指向用户空间读写数据的指针。由于他指向用 752 + * 户空间,必须使用适当的内核函数 753 + * (如 copy_from_user())。按照Unix的风 754 + * 格,该参数也可以视为unsigned long 类型。 755 + * 756 + * 返回值:如果出错则返回负的“errno”值。返回0或正值表 757 + * 示成功,并将返回值传递给用户空间。 758 + * 759 + * 并发安全声明:无锁 760 + * 761 + * 调用上下文说明:进程上下文 762 + * 763 + * 注意事项:SCSI子系统使用“逐层下传 764 + * (trickle down)”的ioctl模型。 765 + * 用户层会对上层驱动设备节点 766 + * (例如/dev/sdc)发起ioctl()调用, 767 + * 如果上层驱动无法识别该命令,则将其 768 + * 传递给SCSI中间层,若中间层也无法识 769 + * 别,则再传递给控制该设备的LLD。 770 + * 根据最新的Unix标准,对于不支持的 771 + * ioctl()命令,应返回-ENOTTY。 772 + * 773 + * 可选实现说明:由LLD选择性定义 774 + **/ 775 + int ioctl(struct scsi_device *sdp, int cmd, void *arg) 776 + 777 + 778 + /** 779 + * proc_info - 支持/proc/scsi/{驱动名}/{主机号}文件节点的读写操作 780 + * @buffer: 输入或出的缓冲区锚点(writeto1_read0==0表示向buffer写 781 + * 入,writeto1_read0==1表示由buffer读取) 782 + * @start: 当writeto1_read0==0时,用于指定驱动实际填充的起始位置; 783 + * 当writeto1_read0==1时被忽略。 784 + * @offset: 当writeto1_read0==0时,表示用户关注的数据在缓冲区中的 785 + * 偏移。当writeto1_read0==1时忽略。 786 + * @length: 缓冲区的最大(或实际使用)长度 787 + * @host_no: 目标SCSI Host的编号(struct Scsi_Host::host_no) 788 + * @writeto1_read0: 1 -> 表示数据从用户空间写入驱动 789 + * (例如,“echo some_string > /proc/scsi/xyz/2”) 790 + * 0 -> 表示用户从驱动读取数据 791 + * (例如,“cat /proc/scsi/xyz/2”) 792 + * 793 + * 返回值:当writeto1_read0==1时返回写入长度。否则, 794 + * 返回从offset偏移开始输出到buffer的字符数。 795 + * 796 + * 并发安全声明:无锁 797 + * 798 + * 调用上下文说明:进程上下文 799 + * 800 + * 注意事项:该函数由scsi_proc.c驱动,与proc_fs交互。 801 + * 当前SCSI子系统可移除对proc_fs的支持,相关配置选。 802 + * 803 + * 可选实现说明:由LLD选择性定义 804 + **/ 805 + int proc_info(char * buffer, char ** start, off_t offset, 806 + int length, int host_no, int writeto1_read0) 807 + 808 + 809 + /** 810 + * queuecommand - 将SCSI命令提交到主机控制器,命令执行完成后调用scp->scsi_done回调函数 811 + * @shost: 指向目标SCSI主机控制器 812 + * @scp: 指向待处理的SCSI命令 813 + * 814 + * 返回值:成功返回0。 815 + * 816 + * 如果发生错误,则返回: 817 + * 818 + * SCSI_MLQUEUE_DEVICE_BUSY表示设备队列满, 819 + * SCSI_MLQUEUE_HOST_BUSY表示整个主机队列满 820 + * 821 + * 在这两种情况下,中间层将自动重新提交该I/O请求 822 + * 823 + * - 若返回SCSI_MLQUEUE_DEVICE_BUSY,则仅暂停该 824 + * 特定设备的命令处理,当该设备的某个命令完成返回 825 + * 时(或在短暂延迟后如果没有其他未完成命令)将恢 826 + * 复其处理。其他设备的命令仍正常继续处理。 827 + * 828 + * - 若返回SCSI_MLQUEUE_HOST_BUSY,将暂停该主机 829 + * 的所有I/O操作,当任意命令从该主机返回时(或在 830 + * 短暂延迟后如果没有其他未完成命令)将恢复处理。 831 + * 832 + * 为了与早期的queuecommand兼容,任何其他返回值 833 + * 都被视作SCSI_MLQUEUE_HOST_BUSY。 834 + * 835 + * 对于其他可立即检测到的错误,可通过以下流程处 836 + * 理:设置scp->result为适当错误值,调用scp->scsi_done 837 + * 回调函数,然后该函数返回0。若该命令未立即执行(LLD 838 + * 正在启动或将要启动该命令),则应将scp->result置0并 839 + * 返回0。 840 + * 841 + * 命令所有权说明:若驱动返回0,则表示驱动获得该命令的 842 + * 所有权, 843 + * 并必须确保最终执行scp->scsi_done回调函数。注意:驱动 844 + * 可以在返回0之前调用scp->scsi_done,但一旦调用该回 845 + * 调函数后,就只能返回0。若驱动返回非零值,则禁止在任何时 846 + * 刻执行该命令的scsi_done回调函数。 847 + * 848 + * 并发安全声明:在2.6.36及更早的内核版本中,调用该函数时持有 849 + * struct Scsi_Host::host_lock锁(通过“irqsave”获取中断安全的自旋锁), 850 + * 并且返回时仍需保持该锁;从Linux 2.6.37开始,queuecommand 851 + * 将在无锁状态下被调用。 852 + * 853 + * 调用上下文说明:在中断(软中断)或进程上下文中 854 + * 855 + * 注意事项:该函数执行应当非常快速,通常不会等待I/O 856 + * 完成。因此scp->scsi_done回调函数通常会在该函数返 857 + * 回后的某个时刻被调用(经常直接从中断服务例程中调用)。 858 + * 某些情况下(如模拟SCSI INQUIRY响应的伪适配器驱动), 859 + * scp->scsi_done回调可能在该函数返回前就被调用。 860 + * 若scp->scsi_done回调函数未在指定时限内被调用,SCSI中 861 + * 间层将启动错误处理流程。当调用scp->scsi_done回调函数 862 + * 时,若“result”字段被设置为CHECK CONDITION, 863 + * 则LLD应执行自动感知并填充 864 + * struct scsi_cmnd::sense_buffer数组。在中间层将 865 + * 命令加入LLD队列之前前,scsi_cmnd::sense_buffer数组 866 + * 会被清零。 867 + * 868 + * 可选实现说明:LLD必须实现 869 + **/ 870 + int queuecommand(struct Scsi_Host *shost, struct scsi_cmnd * scp) 871 + 872 + 873 + /** 874 + * sdev_init - 在向新设备发送任何SCSI命令前(即开始扫描 875 + * 之前)调用该函数 876 + * @sdp: 指向即将被扫描的新设备的指针 877 + * 878 + * 返回值:返回0表示正常。返回其他值表示出错, 879 + * 该设备将被忽略。 880 + * 881 + * 并发安全声明:无锁 882 + * 883 + * 调用上下文说明:进程上下文 884 + * 885 + * 注意事项:该函数允许LLD在设备首次扫描前分配所需的资源。 886 + * 对应的SCSI设备可能尚未真正存在,但SCSI中间层即将对其进 887 + * 行扫描(例如发送INQUIRY命令等)。如果设备存在,将调用 888 + * sdev_configure()进行配置;如果设备不存在,则调用 889 + * sdev_destroy()销毁。更多细节请参考 890 + * include/scsi/scsi_host.h文件。 891 + * 892 + * 可选实现说明:由LLD选择性定义 893 + **/ 894 + int sdev_init(struct scsi_device *sdp) 895 + 896 + 897 + /** 898 + * sdev_configure - 在设备首次完成扫描(即已成功响应INQUIRY 899 + * 命令)之后,LDD可调用该函数对设备进行进一步配置 900 + * @sdp: 已连接的设备 901 + * 902 + * 返回值:返回0表示成功。任何其他返回值都被视为错误,此时 903 + * 设备将被标记为离线。[被标记离线的设备不会调用sdev_destroy(), 904 + * 因此需要LLD主动清理资源。] 905 + * 906 + * 并发安全声明:无锁 907 + * 908 + * 调用上下文说明:进程上下文 909 + * 910 + * 注意事项:该接口允许LLD查看设备扫描代码所发出的初始INQUIRY 911 + * 命令的响应,并采取对应操作。具体实现细节请参阅 912 + * include/scsi/scsi_host.h文件。 913 + * 914 + * 可选实现说明:由LLD选择性定义 915 + **/ 916 + int sdev_configure(struct scsi_device *sdp) 917 + 918 + 919 + /** 920 + * sdev_destroy - 当指定设备即将被关闭时调用。此时该设备 921 + * 上的所有I/O活动均已停止。 922 + * @sdp: 即将关闭的设备 923 + * 924 + * 返回值:无 925 + * 926 + * 并发安全声明:无锁 927 + * 928 + * 调用上下文说明:进程上下文 929 + * 930 + * 注意事项:该设备的中间层数据结构仍然存在 931 + * 但即将被销毁。驱动程序此时应当释放为该设 932 + * 备分配的所有专属资源。系统将不再向此sdp 933 + * 实例发送任何命令。[但该设备可能在未来被 934 + * 重新连接,届时将通过新的struct scsi_device 935 + * 实例,并触发后续的sdev_init()和 936 + * sdev_configure()调用过程。] 937 + * 938 + * 可选实现说明:由LLD选择性定义 939 + **/ 940 + void sdev_destroy(struct scsi_device *sdp) 941 + 942 + 943 + 944 + 数据结构 945 + ======== 946 + struct scsi_host_template 947 + ------------------------- 948 + 每个LLD对应一个“struct scsi_host_template” 949 + 实例 [#]_。该结构体通常被初始化为驱动头文件中的静 950 + 态全局变量,此方式可确保未显式初始化的成员自动置零 951 + (0或NULL)。关键成员变量说明如下: 952 + 953 + name 954 + - 驱动程序的名称(可以包含空格,请限制在80个字符以内) 955 + 956 + proc_name 957 + - 在“/proc/scsi/<proc_name>/<host_no>” 958 + 和sysfs的“drivers”目录中使用的名称。因此 959 + “proc_name”应仅包含Unix文件名中可接受 960 + 的字符。 961 + 962 + ``(*queuecommand)()`` 963 + - 中间层使用的主要回调函数,用于将SCSI命令 964 + 提交到LLD。 965 + 966 + vendor_id 967 + - 该字段是一个唯一标识值,用于确认提供 968 + Scsi_Host LLD的供应商,最常用于 969 + 验证供应商特定的消息请求。该值由标识符类型 970 + 和供应商特定值组成,有效格式描述请参阅 971 + scsi_netlink.h头文件。 972 + 973 + 该结构体的完整定义及详细注释请参阅 ``include/scsi/scsi_host.h``。 974 + 975 + .. [#] 在极端情况下,单个驱动需要控制多种不同类型的硬件时,驱动可 976 + 能包含多个实例,(例如某个LLD驱动同时处理ISA和PCI两种类型 977 + 的适配卡,并为每种硬件类型维护独立的 978 + struct scsi_host_template实例)。 979 + 980 + struct Scsi_Host 981 + ---------------- 982 + 每个由LLD控制的主机适配器对应一个struct Scsi_Host实例。 983 + 该结构体与struct scsi_host_template具有多个相同成员。 984 + 当创建struct Scsi_Host实例时(通过hosts.c中的 985 + scsi_host_alloc()函数),这些通用成员会从LLD的 986 + struct scsi_host_template实例初始化而来。关键成员说明 987 + 如下: 988 + 989 + host_no 990 + - 系统范围内唯一的主机标识号,按升序从0开始分配 991 + can_queue 992 + - 必须大于0,表示适配器可处理的最大并发命令数,禁 993 + 止向适配器发送超过此数值的命令数 994 + this_id 995 + - 主机适配器的SCSI ID(SCSI启动器标识),若未知则 996 + 设置为-1 997 + sg_tablesize 998 + - 主机适配器支持的最大散列表(scatter-gather)元素 999 + 数。设置为SG_ALL或更小的值可避免使用链式SG列表, 1000 + 且最小值必须为1 1001 + max_sectors 1002 + - 单个SCSI命令中允许的最大扇区数(通常为512字节/ 1003 + 扇区)。默认值为0,此时会使用 1004 + SCSI_DEFAULT_MAX_SECTORS(在scsi_host.h中定义), 1005 + 当前该值为1024。因此,如果未定义max_sectors,则磁盘的 1006 + 最大传输大小为512KB。注意:这个大小可能不足以支持 1007 + 磁盘固件上传。 1008 + cmd_per_lun 1009 + - 主机适配器的设备上,每个LUN可排队的最大命令数。 1010 + 此值可通过LLD调用scsi_change_queue_depth()进行 1011 + 调整。 1012 + hostt 1013 + - 指向LLD struct scsi_host_template实例的指针, 1014 + 当前struct Scsi_Host实例正是由此模板生成。 1015 + hostt->proc_name 1016 + - LLD的名称,sysfs使用的驱动名。 1017 + transportt 1018 + - 指向LLD struct scsi_transport_template实例的指 1019 + 针(如果存在)。当前支持FC与SPI传输协议。 1020 + hostdata[0] 1021 + - 为LLD在struct Scsi_Host结构体末尾预留的区域,大小由 1022 + scsi_host_alloc()的第二个参数(privsize)决定。 1023 + 1024 + scsi_host结构体的完整定义详见include/scsi/scsi_host.h。 1025 + 1026 + struct scsi_device 1027 + ------------------ 1028 + 通常而言,每个SCSI逻辑单元(Logical Unit)对应一个该结构 1029 + 的实例。连接到主机适配器的SCSI设备通过三个要素唯一标识:通 1030 + 道号(Channel Number)、目标ID(Target ID)和逻辑单元号 1031 + (LUN)。 1032 + 该结构体完整定义于include/scsi/scsi_device.h。 1033 + 1034 + struct scsi_cmnd 1035 + ---------------- 1036 + 该结构体实例用于在LLD与SCSI中间层之间传递SCSI命令 1037 + 及其响应。SCSI中间层会确保:提交到LLD的命令数不超过 1038 + scsi_change_queue_depth()(或struct Scsi_Host::cmd_per_lun) 1039 + 设定的上限,且每个SCSI设备至少分配一个struct scsi_cmnd实例。 1040 + 关键成员说明如下: 1041 + 1042 + cmnd 1043 + - 包含SCSI命令的数组 1044 + cmd_len 1045 + - SCSI命令的长度(字节为单位) 1046 + sc_data_direction 1047 + - 数据的传输方向。请参考 1048 + include/linux/dma-mapping.h中的 1049 + “enum dma_data_direction”。 1050 + result 1051 + - LLD在调用“done”之前设置该值。值为0表示命令成功 1052 + 完成(并且所有数据(如果有)已成功在主机与SCSI 1053 + 目标设备之间完成传输)。“result”是一个32位无符 1054 + 号整数,可以视为2个相关字节。SCSI状态值位于最 1055 + 低有效位。请参考include/scsi/scsi.h中的 1056 + status_byte()与host_byte()宏以及其相关常量。 1057 + sense_buffer 1058 + - 这是一个数组(最大长度为SCSI_SENSE_BUFFERSIZE 1059 + 字节),当SCSI状态(“result”的最低有效位)设为 1060 + CHECK_CONDITION(2)时,该数组由LLD填写。若 1061 + CHECK_CONDITION被置位,且sense_buffer[0]的高 1062 + 半字节值为7,则中间层会认为sense_buffer数组 1063 + 包含有效的SCSI感知数据;否则,中间层会发送 1064 + REQUEST_SENSE SCSI命令来获取感知数据。由于命令 1065 + 排队的存在,后一种方式容易出错,因此建议LLD始终 1066 + 支持“自动感知”。 1067 + device 1068 + - 指向与该命令关联的scsi_device对象的指针。 1069 + resid_len (通过调用scsi_set_resid() / scsi_get_resid()访问) 1070 + - LLD应将此无符号整数设置为请求的传输长度(即 1071 + “request_bufflen”)减去实际传输的字节数。“resid_len” 1072 + 默认设置为0,因此如果LLD无法检测到数据欠载(不能报告溢出), 1073 + 则可以忽略它。LLD应在调用“done”之前设置 1074 + “resid_len”。 1075 + underflow 1076 + - 如果实际传输的字节数小于该字段值,LLD应将 1077 + DID_ERROR << 16赋值给“result”。并非所有 1078 + LLD都实现此项检查,部分LLD仅将错误信息输出 1079 + 到日志,而未真正报告DID_ERROR。更推荐 1080 + 的做法是由LLD实现“resid_len”的支持。 1081 + 1082 + 建议LLD在从SCSI目标设备进行数据传输时设置“resid_len”字段 1083 + (例如READ操作)。当这些数据传输的感知码是MEDIUM ERROR或 1084 + HARDWARE ERROR(有时也包括RECOVERED ERROR)时设置 1085 + resid_len尤为重要。在这种情况下,如果LLD无法确定接收了多 1086 + 少数据,那么最安全的做法是表示没有接收到任何数据。例如: 1087 + 为了表明没有接收到任何有效数据,LLD可以使用如下辅助函数:: 1088 + 1089 + scsi_set_resid(SCpnt, scsi_bufflen(SCpnt)); 1090 + 1091 + 其中SCpnt是一个指向scsi_cmnd对象的指针。如果表示仅接收到 1092 + 三个512字节的数据块,可以这样设置resid_len:: 1093 + 1094 + scsi_set_resid(SCpnt, scsi_bufflen(SCpnt) - (3 * 512)); 1095 + 1096 + scsi_cmnd结构体定义在 include/scsi/scsi_cmnd.h文件中。 1097 + 1098 + 1099 + 1100 + === 1101 + 每个struct Scsi_Host实例都有一个名为default_lock 1102 + 的自旋锁(spin_lock),它在scsi_host_alloc()函数 1103 + 中初始化(该函数定义在hosts.c文件中)。在同一个函数 1104 + 中,struct Scsi_Host::host_lock指针会被初始化为指 1105 + 向default_lock。此后,SCSI中间层执行的加 1106 + 锁和解锁操作都会使用host_lock指针。过去,驱动程序可 1107 + 以重写host_lock指针,但现在不再允许这样做。 1108 + 1109 + 1110 + 自动感知 1111 + ======== 1112 + 自动感知(Autosense或auto-sense)在SAM-2规范中被定 1113 + 义为:当SCSI命令完成状态为CHECK CONDITION时,“自动 1114 + 将感知数据(sense data)返回给应用程序客户端”。底层 1115 + 驱动(LLD)应当执行自动感知。当LLD检测到 1116 + CHECK CONDITION状态时,可通过以下任一方式完成: 1117 + 1118 + a) 要求SCSI协议(例如SCSI并行接口(SPI))在此 1119 + 类响应中执行一次额外的数据传输 1120 + 1121 + b) 或由LLD主动发起REQUEST SENSE命令获取感知数据 1122 + 1123 + 无论采用哪种方式,当检测到CHECK CONDITION状态时,中 1124 + 间层通过检查结构体scsi_cmnd::sense_buffer[0]的值来 1125 + 判断LLD是否已执行自动感知。若该字节的高半字节为7 1126 + (或 0xf),则认为已执行自动感知;若该字节为其他值 1127 + (且此字节在每条命令执行前会被初始化为0),则中间层将 1128 + 主动发起REQUEST SENSE命令。 1129 + 1130 + 在存在命令队列的场景下,保存失败命令感知数据的“nexus” 1131 + 可能会在等待REQUEST SENSE命令期间变得不同步。因此, 1132 + 最佳实践是由LLD执行自动感知。 1133 + 1134 + 1135 + 自Linux内核2.4以来的变更 1136 + ======================== 1137 + io_request_lock已被多个细粒度锁替代。与底层驱动 1138 + (LLD)相关的锁是struct Scsi_Host::host_lock,且每 1139 + 个SCSI主机都独立拥有一个该锁。 1140 + 1141 + 旧的错误处理机制已经被移除。这意味着LLD的接口函数 1142 + abort()与reset()已经被删除。 1143 + struct scsi_host_template::use_new_eh_code标志 1144 + 也已经被移除。 1145 + 1146 + 在Linux内核2.4中,SCSI子系统的配置描述与其他Linux子系 1147 + 统的配置描述集中存放在Documentation/Configure.help 1148 + 文件中。在Linux内核2.6中,SCSI子系统拥有独立的配置文 1149 + 件drivers/scsi/Kconfig(体积更小),同时包含配置信息 1150 + 与帮助信息。 1151 + 1152 + struct SHT已重命名为struct scsi_host_template。 1153 + 1154 + 新增“热插拔初始化模型”以及许多用于支持该功能的额外函数。 1155 + 1156 + 1157 + 致谢 1158 + ==== 1159 + 以下人员对本文档做出了贡献: 1160 + 1161 + - Mike Anderson <andmike at us dot ibm dot com> 1162 + - James Bottomley <James dot Bottomley at hansenpartnership dot com> 1163 + - Patrick Mansfield <patmans at us dot ibm dot com> 1164 + - Christoph Hellwig <hch at infradead dot org> 1165 + - Doug Ledford <dledford at redhat dot com> 1166 + - Andries Brouwer <Andries dot Brouwer at cwi dot nl> 1167 + - Randy Dunlap <rdunlap at xenotime dot net> 1168 + - Alan Stern <stern at rowland dot harvard dot edu> 1169 + 1170 + 1171 + Douglas Gilbert 1172 + dgilbert at interlog dot com 1173 + 1174 + 2004年9月21日
+38
Documentation/translations/zh_CN/scsi/sd-parameters.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + .. include:: ../disclaimer-zh_CN.rst 3 + 4 + :Original: Documentation/scsi/sd-parameters.rst 5 + 6 + :翻译: 7 + 8 + 郝栋栋 doubled <doubled@leap-io-kernel.com> 9 + 10 + :校译: 11 + 12 + 13 + 14 + ============================ 15 + Linux SCSI磁盘驱动(sd)参数 16 + ============================ 17 + 18 + 缓存类型(读/写) 19 + ------------------ 20 + 启用/禁用驱动器读写缓存。 21 + 22 + =========================== ===== ===== ======= ======= 23 + 缓存类型字符串 WCE RCD 写缓存 读缓存 24 + =========================== ===== ===== ======= ======= 25 + write through 0 0 关闭 开启 26 + none 0 1 关闭 关闭 27 + write back 1 0 开启 开启 28 + write back, no read (daft) 1 1 开启 关闭 29 + =========================== ===== ===== ======= ======= 30 + 31 + 将缓存类型设置为“write back”并将该设置保存到驱动器:: 32 + 33 + # echo "write back" > cache_type 34 + 35 + 如果要修改缓存模式但不使更改持久化,可在缓存类型字符串前 36 + 添加“temporary ”。例如:: 37 + 38 + # echo "temporary write back" > cache_type
+317
Documentation/translations/zh_CN/security/SCTP.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + .. include:: ../disclaimer-zh_CN.rst 3 + 4 + :Original: Documentation/security/SCTP.rst 5 + 6 + :翻译: 7 + 赵硕 Shuo Zhao <zhaoshuo@cqsoftware.com.cn> 8 + 9 + ==== 10 + SCTP 11 + ==== 12 + 13 + SCTP的LSM支持 14 + ============= 15 + 16 + 安全钩子 17 + -------- 18 + 19 + 对于安全模块支持,已经实现了三个特定于SCTP的钩子:: 20 + 21 + security_sctp_assoc_request() 22 + security_sctp_bind_connect() 23 + security_sctp_sk_clone() 24 + security_sctp_assoc_established() 25 + 26 + 这些钩子的用法在下面的 `SCTP的SELinux支持`_ 一章中描述SELinux的实现。 27 + 28 + 29 + security_sctp_assoc_request() 30 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 31 + 将关联INIT数据包的 ``@asoc`` 和 ``@chunk->skb`` 传递给安全模块。 32 + 成功时返回 0,失败时返回错误。 33 + :: 34 + 35 + @asoc - 指向sctp关联结构的指针。 36 + @skb - 指向包含关联数据包skbuff的指针。 37 + 38 + 39 + security_sctp_bind_connect() 40 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 41 + 将一个或多个IPv4/IPv6地址传递给安全模块进行基于 ``@optname`` 的验证, 42 + 这将导致是绑定还是连接服务,如下面的权限检查表所示。成功时返回 0,失败 43 + 时返回错误。 44 + :: 45 + 46 + @sk - 指向sock结构的指针。 47 + @optname - 需要验证的选项名称。 48 + @address - 一个或多个IPv4 / IPv6地址。 49 + @addrlen - 地址的总长度。使用sizeof(struct sockaddr_in)或 50 + sizeof(struct sockaddr_in6)来计算每个ipv4或ipv6地址。 51 + 52 + ------------------------------------------------------------------ 53 + | BIND 类型检查 | 54 + | @optname | @address contains | 55 + |----------------------------|-----------------------------------| 56 + | SCTP_SOCKOPT_BINDX_ADD | 一个或多个 ipv4 / ipv6 地址 | 57 + | SCTP_PRIMARY_ADDR | 单个 ipv4 or ipv6 地址 | 58 + | SCTP_SET_PEER_PRIMARY_ADDR | 单个 ipv4 or ipv6 地址 | 59 + ------------------------------------------------------------------ 60 + 61 + ------------------------------------------------------------------ 62 + | CONNECT 类型检查 | 63 + | @optname | @address contains | 64 + |----------------------------|-----------------------------------| 65 + | SCTP_SOCKOPT_CONNECTX | 一个或多个 ipv4 / ipv6 地址 | 66 + | SCTP_PARAM_ADD_IP | 一个或多个 ipv4 / ipv6 地址 | 67 + | SCTP_SENDMSG_CONNECT | 单个 ipv4 or ipv6 地址 | 68 + | SCTP_PARAM_SET_PRIMARY | 单个 ipv4 or ipv6 地址 | 69 + ------------------------------------------------------------------ 70 + 71 + 条目 ``@optname`` 的摘要如下:: 72 + 73 + SCTP_SOCKOPT_BINDX_ADD - 允许在(可选地)调用 bind(3) 后,关联额外 74 + 的绑定地址。 75 + sctp_bindx(3) 用于在套接字上添加一组绑定地址。 76 + 77 + SCTP_SOCKOPT_CONNECTX - 允许分配多个地址以连接到对端(多宿主)。 78 + sctp_connectx(3) 使用多个目标地址在SCTP 79 + 套接字上发起连接。 80 + 81 + SCTP_SENDMSG_CONNECT - 通过sendmsg(2)或sctp_sendmsg(3)在新关联上 82 + 发起连接。 83 + 84 + SCTP_PRIMARY_ADDR - 设置本地主地址。 85 + 86 + SCTP_SET_PEER_PRIMARY_ADDR - 请求远程对端将某个地址设置为其主地址。 87 + 88 + SCTP_PARAM_ADD_IP - 在启用动态地址重配置时使用。 89 + SCTP_PARAM_SET_PRIMARY - 如下所述,启用重新配置功能。 90 + 91 + 92 + 为了支持动态地址重新配置,必须在两个端点上启用以下 93 + 参数(或使用适当的 **setsockopt**\(2)):: 94 + 95 + /proc/sys/net/sctp/addip_enable 96 + /proc/sys/net/sctp/addip_noauth_enable 97 + 98 + 当相应的 ``@optname`` 存在时,以下的 *_PARAM_* 参数会 99 + 通过ASCONF块发送到对端:: 100 + 101 + @optname ASCONF Parameter 102 + ---------- ------------------ 103 + SCTP_SOCKOPT_BINDX_ADD -> SCTP_PARAM_ADD_IP 104 + SCTP_SET_PEER_PRIMARY_ADDR -> SCTP_PARAM_SET_PRIMARY 105 + 106 + 107 + security_sctp_sk_clone() 108 + ~~~~~~~~~~~~~~~~~~~~~~~~ 109 + 每当通过 **accept**\(2)创建一个新的套接字(即TCP类型的套接字),或者当 110 + 一个套接字被‘剥离’时如用户空间调用 **sctp_peeloff**\(3),会调用此函数。 111 + :: 112 + 113 + @asoc - 指向当前sctp关联结构的指针。 114 + @sk - 指向当前套接字结构的指针。 115 + @newsk - 指向新的套接字结构的指针。 116 + 117 + 118 + security_sctp_assoc_established() 119 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 120 + 当收到COOKIE ACK时调用,对于客户端,对端的secid将被保存 121 + 到 ``@asoc->peer_secid`` 中:: 122 + 123 + @asoc - 指向sctp关联结构的指针。 124 + @skb - 指向COOKIE ACK数据包的skbuff指针。 125 + 126 + 127 + 用于关联建立的安全钩子 128 + ---------------------- 129 + 130 + 下图展示了在建立关联时 ``security_sctp_bind_connect()``、 ``security_sctp_assoc_request()`` 131 + 和 ``security_sctp_assoc_established()`` 的使用。 132 + :: 133 + 134 + SCTP 端点 "A" SCTP 端点 "Z" 135 + ============= ============= 136 + sctp_sf_do_prm_asoc() 137 + 关联的设置可以通过connect(2), 138 + sctp_connectx(3),sendmsg(2) 139 + or sctp_sendmsg(3)来发起。 140 + 这将导致调用security_sctp_bind_connect() 141 + 发起与SCTP对端端点"Z"的关联。 142 + INIT ---------------------------------------------> 143 + sctp_sf_do_5_1B_init() 144 + 响应一个INIT数据块。 145 + SCTP对端端点"A"正在请求一个临时关联。 146 + 如果是首次关联,调用security_sctp_assoc_request() 147 + 来设置对等方标签。 148 + 如果不是首次关联,检查是否被允许。 149 + 如果允许,则发送: 150 + <----------------------------------------------- INIT ACK 151 + | 152 + | 否则,生成审计事件并默默丢弃该数据包。 153 + | 154 + COOKIE ECHO ------------------------------------------> 155 + sctp_sf_do_5_1D_ce() 156 + 响应一个COOKIE ECHO数据块。 157 + 确认该cookie并创建一个永久关联。 158 + 调用security_sctp_assoc_request() 159 + 执行与INIT数据块响应相同的操作。 160 + <------------------------------------------- COOKIE ACK 161 + | | 162 + sctp_sf_do_5_1E_ca | 163 + 调用security_sctp_assoc_established() | 164 + 来设置对方标签 | 165 + | | 166 + | 如果是SCTP_SOCKET_TCP或是剥离的套接 167 + | 字,会调用 security_sctp_sk_clone() 168 + | 来克隆新的套接字。 169 + | | 170 + 建立 建立 171 + | | 172 + ------------------------------------------------------------------ 173 + | 关联建立 | 174 + ------------------------------------------------------------------ 175 + 176 + 177 + SCTP的SELinux支持 178 + ================= 179 + 180 + 安全钩子 181 + -------- 182 + 183 + 上面的 `SCTP的LSM支持`_ 章节描述了以下SCTP安全钩子,SELinux的细节 184 + 说明如下:: 185 + 186 + security_sctp_assoc_request() 187 + security_sctp_bind_connect() 188 + security_sctp_sk_clone() 189 + security_sctp_assoc_established() 190 + 191 + 192 + security_sctp_assoc_request() 193 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 194 + 将关联INIT数据包的 ``@asoc`` 和 ``@chunk->skb`` 传递给安全模块。 195 + 成功时返回 0,失败时返回错误。 196 + :: 197 + 198 + @asoc - 指向sctp关联结构的指针。 199 + @skb - 指向关联数据包skbuff的指针。 200 + 201 + 安全模块执行以下操作: 202 + 如果这是 ``@asoc->base.sk`` 上的首次关联,则将对端的sid设置 203 + 为 ``@skb`` 中的值。这将确保只有一个对端sid分配给可能支持多个 204 + 关联的 ``@asoc->base.sk``。 205 + 206 + 否则验证 ``@asoc->base.sk peer sid`` 是否与 ``@skb peer sid`` 207 + 匹配,以确定该关联是否应被允许或拒绝。 208 + 209 + 将sctp的 ``@asoc sid`` 设置为套接字的sid(来自 ``asoc->base.sk``) 210 + 并从 ``@skb peer sid`` 中提取MLS部分。这将在SCTP的TCP类型套接字及 211 + 剥离连接中使用,因为它们会导致生成一个新的套接字。 212 + 213 + 如果配置了IP安全选项(CIPSO/CALIPSO),则会在套接字上设置IP选项。 214 + 215 + 216 + security_sctp_bind_connect() 217 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 218 + 根据 ``@optname`` 检查ipv4/ipv6地址所需的权限,具体如下:: 219 + 220 + ------------------------------------------------------------------ 221 + | BIND 权限检查 | 222 + | @optname | @address contains | 223 + |----------------------------|-----------------------------------| 224 + | SCTP_SOCKOPT_BINDX_ADD | 一个或多个 ipv4 / ipv6 地址 | 225 + | SCTP_PRIMARY_ADDR | 单个 ipv4 or ipv6 地址 | 226 + | SCTP_SET_PEER_PRIMARY_ADDR | 单个 ipv4 or ipv6 地址 | 227 + ------------------------------------------------------------------ 228 + 229 + ------------------------------------------------------------------ 230 + | CONNECT 权限检查 | 231 + | @optname | @address contains | 232 + |----------------------------|-----------------------------------| 233 + | SCTP_SOCKOPT_CONNECTX | 一个或多个 ipv4 / ipv6 地址 | 234 + | SCTP_PARAM_ADD_IP | 一个或多个 ipv4 / ipv6 地址 | 235 + | SCTP_SENDMSG_CONNECT | 单个 ipv4 or ipv6 地址 | 236 + | SCTP_PARAM_SET_PRIMARY | 单个 ipv4 or ipv6 地址 | 237 + ------------------------------------------------------------------ 238 + 239 + 240 + `SCTP的LSM支持`_ 提供了 ``@optname`` 摘要,并且还描述了当启用动态地址重新 241 + 配置时,ASCONF块的处理过程。 242 + 243 + 244 + security_sctp_sk_clone() 245 + ~~~~~~~~~~~~~~~~~~~~~~~~ 246 + 每当通过 **accept**\(2)(即TCP类型的套接字)创建一个新的套接字,或者 247 + 当一个套接字被“剥离”如用户空间调用 **sctp_peeloff**\(3)时, 248 + ``security_sctp_sk_clone()`` 将会分别将新套接字的sid和对端sid设置为 249 + ``@asoc sid`` 和 ``@asoc peer sid`` 中包含的值。 250 + :: 251 + 252 + @asoc - 指向当前sctp关联结构的指针。 253 + @sk - 指向当前sock结构的指针。 254 + @newsk - 指向新sock结构的指针。 255 + 256 + 257 + security_sctp_assoc_established() 258 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 259 + 当接收到COOKIE ACK时调用,它将连接的对端sid设置为 ``@skb`` 中的值:: 260 + 261 + @asoc - 指向sctp关联结构的指针。 262 + @skb - 指向COOKIE ACK包skbuff的指针。 263 + 264 + 265 + 策略声明 266 + -------- 267 + 以下支持SCTP的类和权限在内核中是可用的:: 268 + 269 + class sctp_socket inherits socket { node_bind } 270 + 271 + 当启用以下策略功能时:: 272 + 273 + policycap extended_socket_class; 274 + 275 + SELinux对SCTP的支持添加了用于连接特定端口类型 ``name_connect`` 权限 276 + 以及在下面的章节中进行解释的 ``association`` 权限。 277 + 278 + 如果用户空间工具已更新,SCTP将支持如下所示的 ``portcon`` 声明:: 279 + 280 + portcon sctp 1024-1036 system_u:object_r:sctp_ports_t:s0 281 + 282 + 283 + SCTP对端标签 284 + ------------ 285 + 每个SCTP套接字仅分配一个对端标签。这个标签将在建立第一个关联时分配。 286 + 任何后续在该套接字上的关联都会将它们的数据包对端标签与套接字的对端标 287 + 签进行比较,只有在它们不同的情况下 ``association`` 权限才会被验证。 288 + 这是通过检查套接字的对端sid与接收到的数据包中的对端sid来验证的,以决 289 + 定是否允许或拒绝该关联。 290 + 291 + 注: 292 + 1) 如果对端标签未启用,则对端上下文将始终是 ``SECINITSID_UNLABELED`` 293 + (在策略声明中为 ``unlabeled_t`` )。 294 + 295 + 2) 由于SCTP可以在单个套接字上支持每个端点(多宿主)的多个传输地址,因此 296 + 可以配置策略和NetLabel为每个端点提供不同的对端标签。由于套接字的对端 297 + 标签是由第一个关联的传输地址决定的,因此建议所有的对端标签保持一致。 298 + 299 + 3) 用户空间可以使用 **getpeercon**\(3) 来检索套接字的对端上下文。 300 + 301 + 4) 虽然这不是SCTP特有的,但在使用NetLabel时要注意,如果标签分配给特定的接 302 + 口,而该接口‘goes down’,则NetLabel服务会移除该条目。因此,请确保网络启 303 + 动脚本调用 **netlabelctl**\(8) 来设置所需的标签(详细信息, 304 + 请参阅 **netlabel-config**\(8) 辅助脚本)。 305 + 306 + 5) NetLabel SCTP对端标签规则应用如下所述标签为“netlabel”的一组帖子: 307 + https://www.paul-moore.com/blog/t. 308 + 309 + 6) CIPSO仅支持IPv4地址: ``socket(AF_INET, ...)`` 310 + CALIPSO仅支持IPv6地址: ``socket(AF_INET6, ...)`` 311 + 312 + 测试CIPSO/CALIPSO时请注意以下事项: 313 + a) 如果SCTP数据包由于无效标签无法送达,CIPSO会发送一个ICMP包。 314 + b) CALIPSO不会发送ICMP包,只会默默丢弃数据包。 315 + 316 + 7) RFC 3554不支持IPSEC —— SCTP/IPSEC支持尚未在用户空间实现(**racoon**\(8) 317 + 或 **ipsec_pluto**\(8)),尽管内核支持 SCTP/IPSEC。
+2 -2
Documentation/translations/zh_CN/security/index.rst
··· 18 18 credentials 19 19 snp-tdx-threat-model 20 20 lsm 21 + lsm-development 21 22 sak 23 + SCTP 22 24 self-protection 23 25 siphash 24 26 tpm/index ··· 30 28 TODOLIST: 31 29 * IMA-templates 32 30 * keys/index 33 - * lsm-development 34 - * SCTP 35 31 * secrets/index 36 32 * ipe
+398
Documentation/translations/zh_CN/security/ipe.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + .. include:: ../disclaimer-zh_CN.rst 3 + 4 + :Original: Documentation/security/sak.rst 5 + 6 + :翻译: 7 + 赵硕 Shuo Zhao <zhaoshuo@cqsoftware.com.cn> 8 + 9 + 完整性策略执行(IPE)-内核文档 10 + ============================== 11 + 12 + .. NOTE:: 13 + 14 + 这是针对开发人员而不是管理员的文档。如果您正在 15 + 寻找有关IPE使用的文档,请参阅 :doc:`IPE admin 16 + guide </admin-guide/LSM/ipe>`。 17 + 18 + 历史背景 19 + -------- 20 + 21 + 最初促使IPE实施的原因,是需要创建一个锁定式系统。该系统将 22 + 从一开始就具备安全性,并且在可执行代码和系统功能关键的特定 23 + 数据文件上,提供强有力的完整性保障。只有当这些特定数据文件 24 + 符合完整性策略时,它们才可以被读取。系统中还将存在强制访问 25 + 控制机制,因此扩展属性(xattrs)也必须受到保护。这就引出了 26 + 需要选择能够提供完整性保证的机制。当时,有两种主要机制被考 27 + 虑,用以在满足这些要求的前提下保证系统完整性: 28 + 29 + 1. IMA + EVM Signatures 30 + 2. DM-Verity 31 + 32 + 这两个选项都经过了仔细考虑,然而在原始的IPE使用场景 33 + 中,最终选择DM-Verity而非IMA+EVM作为完整性机制,主 34 + 要有三个原因: 35 + 36 + 1. 防护额外的攻击途径 37 + 38 + * 使用IMA+EVM时,如果没有加密解决方案,系统很容易受到 39 + 离线攻击,特别是针对上述特定数据文件的攻击。 40 + 41 + 与可执行文件不同,读取操作(如对受保护数据文件的读 42 + 取操作)无法强制性进行全局完整性验证。这意味着必须 43 + 有一种选择机制来决定是否应对某个读取操作实施完整性 44 + 策略。 45 + 46 + 在当时,这是通过强制访问控制标签来实现的,IMA策略会 47 + 指定哪些标签需要进行完整性验证,这带来了一个问题: 48 + EVM虽然可以保护标签,但如果攻击者离线修改文件系统, 49 + 那么攻击者就可以清除所有的扩展属性(xattrs)——包括 50 + 用于确定文件是否应受完整性策略约束的SELinux标签。 51 + 52 + 使用DM-Verity,由于xattrs被保存为Merkel树的一部分, 53 + 如果对由dm-verity保护的文件系统进行了离线挂载,校验 54 + 和将不在匹配,文件将无法读取。 55 + 56 + * 由于用户空间的二进制文件在Linux中是分页加载的,dm- 57 + verity同样提供了对抗恶意块设备的额外保护。在这样的 58 + 攻击中,块设备最初报告适当的内容以供IMA哈希计算,通 59 + 过所需的完整性检查。然后,在访问真实数据时发生的页面 60 + 错误将报告攻击者的有效载荷。由于dm-verity会在页面错 61 + 误发生时检查数据(以及磁盘访问),因此这种攻击得到了 62 + 缓解。 63 + 64 + 2. 性能: 65 + 66 + * dm-verity在块被读取时按需提供完整性验证,而不需要将整 67 + 个文件读入内存进行验证。 68 + 69 + 3. 签名的简化性: 70 + 71 + * 不需要两个签名(IMA 然后是 EVM):一个签名可以覆盖整个 72 + 块设备。 73 + * 签名可以存储在文件系统元数据之外。 74 + * 该签名支持基于 x.509 的签名基础设施。 75 + 76 + 下一步是选择一个策略来执行完整性验证机制,该策略的最低 77 + 要求是: 78 + 79 + 1. 策略本身必须经过完整性验证(防止针对它的简单攻击)。 80 + 2. 策略本身必须抵抗回滚攻击。 81 + 3. 策略执行必须具有类似宽松模式的功能。 82 + 4. 策略必须能够在不重启的情况下,完整地进行更新。 83 + 5. 策略更新必须是原子性的。 84 + 6. 策略必须支持撤销先前创建的组件。 85 + 7. 策略必须在任何时间点都能进行审计。 86 + 87 + 当时,IMA作为唯一的完整性策略机制,被用来与这些要求进行对比, 88 + 但未能满足所有最低要求。尽管考虑过扩展IMA以涵盖这些要求,但 89 + 最终因两个原因被放弃: 90 + 91 + 1. 回归风险;这其中许多变更将导致对已经存在于内核的IMA进行 92 + 重大代码更改,因此可能会影响用户。 93 + 94 + 2. IMA在该系统中用于测量和证明;将测量策略与本地完整性策略 95 + 的执行分离被认为是有利的。 96 + 97 + 由于这些原因,决定创建一个新的LSM,其职责是仅限于本地完整性 98 + 策略的执行。 99 + 100 + 职责和范围 101 + ---------- 102 + 103 + IPE顾名思义,本质上是一种完整性策略执行解决方案;IPE并不强制规定 104 + 如何提供完整性保障,而是将这一决策权留给系统管理员,管理员根据自身 105 + 需求,选择符合的机制来设定安全标准。存在几种不同的完整性解决方案, 106 + 它们提供了不同程度的安全保障;而IPE允许系统管理员理论上为所有这些 107 + 解决方案制定策略。 108 + 109 + IPE自身没有内置确保完整性的固有机制。相反,在构建具备完整性保障能力 110 + 的系统时,存在更高效的分层方案可供使用。需要重点注意的是,用于证明完 111 + 整性的机制,与用于执行完整性声明的策略是相互独立的。 112 + 113 + 因此,IPE依据以下方面进行设计: 114 + 115 + 1. 便于与完整性提供机制集成。 116 + 2. 便于平台管理员/系统管理员使用。 117 + 118 + 设计理由: 119 + --------- 120 + 121 + IPE是在评估其他操作系统和环境中的现有完整性策略解决方案后设计的。 122 + 在对其他实现的调查中,发现了一些缺陷: 123 + 124 + 1. 策略不易为人们读取,通常需要二进制中间格式。 125 + 2. 默认情况下会隐式采取单一的、不可定制的操作。 126 + 3. 调试策略需要手动来确定违反了哪个规则。 127 + 4. 编写策略需要对更大系统或操作系统有深入的了解。 128 + 129 + IPE尝试避免所有这些缺陷。 130 + 131 + 策略 132 + ~~~~ 133 + 134 + 纯文本 135 + ^^^^^^ 136 + 137 + IPE的策略是纯文本格式的。相较于其他Linux安全模块(LSM), 138 + 策略文件体积略大,但能解决其他平台上部分完整性策略方案存在 139 + 的两个核心问题。 140 + 141 + 第一个问题是代码维护和冗余的问题。为了编写策略,策略必须是 142 + 以某种形式的字符串形式呈现(无论是 XML、JSON、YAML 等结构化 143 + 格式,还是其他形式),以便策略编写者能够理解所写内容。在假设 144 + 的二进制策略设计中,需要一个序列化器将策略将可读的形式转换为 145 + 二进制形式,同时还需要一个反序列化器来将二进制形式转换为内核 146 + 中的数据结构。 147 + 148 + 最终,还需要另一个反序列化器将是必要的,用于将二进制形式转换 149 + 为人类可读的形式,并尽可能保存所有信息,这是因为使用此访问控 150 + 制系统的用户必须维护一个校验表和原始文件,才能理解哪些策略已 151 + 经部署在该系统上,哪些没有。对于单个用户来说,这可能没问题, 152 + 因为旧的策略可以在更新生效后很快被丢弃。但对于管理成千上万、 153 + 甚至数十万台计算机的用户,且这些计算机有不同的操作系统和不同 154 + 的操作需求,这很快就成了一个问题,因为数年前的过时策略可能仍然 155 + 存在,从而导致需要快速恢复策略或投资大量基础设施来跟踪每个策略 156 + 的内容。 157 + 158 + 有了这三个独立的序列化器/反序列化器,维护成本非常昂贵。如果策略 159 + 避免使用二进制格式,则只需要一个序列化器;将人类可读的形式转换 160 + 为内核中的数据结构。从而节省了代码维护成本,并保持了可操作性。 161 + 162 + 第二个关于二进制格式的问题是透明性,由于IPE根据系统资源的可信度 163 + 来控制访问,因此其策略也必须可信,以便可以被更改。这是通过签名来 164 + 完成的,这就需要签名过程。签名过程通常具有很高的安全标准,因为 165 + 任何被签名的内容都可以被用来攻击完整性执行系统。签署时,签署者 166 + 必须知道他们在签署什么,二进制策略可能会导致这一点的模糊化;签署 167 + 者看到的只是一个不透明的二进制数据块。另一方面,对于纯文本策略中, 168 + 签署者看到的则是实际提交的策略。 169 + 170 + 启动策略 171 + ~~~~~~~~ 172 + 173 + 如果配置得当,IPE能够在内核启动并进入用户模式时立即执行策略。 174 + 这意味着需要在用户模式开始的那一刻就存储一定的策略。通常,这种 175 + 存储可以通过一下三种方式之一来处理: 176 + 177 + 1. 策略文件存储在磁盘上,内核在进入可能需要做出执行决策的代码 178 + 路径之前,先加载该策略。 179 + 2. 策略文件由引导加载程序传递给内核,内核解析这些策略。 180 + 3. 将一个策略文件编译到内核中,内核在初始化过程中对其进行解析并 181 + 执行。 182 + 183 + 第一种方式存在问题:内核从用户空间读取文件通常是不推荐的,并且在 184 + 内核中极为罕见。 185 + 186 + 第二种选项同样存在问题:Linux在其整个生态系统中支持多种引导加载程序, 187 + 所有引导加载程序都必须支持这种新方法,或者需要有一个独立的来源,这 188 + 可能会导致内核启动过程发生不必要的重大变化。 189 + 190 + 第三种选项是最佳选择,但需要注意的是,编译进内核的策略会占用磁盘空间。 191 + 重要的是要使这一策略足够通用,以便用户空间能够加载新的、更复杂的策略, 192 + 同时也要足够严格,以防止过度授权并避免引发安全问题。 193 + 194 + initramfs提供了一种建立此启动路径的方法。内核启动时以最小化的策略启动, 195 + 该策略仅信任initramfs。在initramfs内,当真实的根文件系统已挂载且尚未 196 + 切换时,它会部署并激活一个信任新根文件系统的策略。这种方法防止了在任何 197 + 步骤中出现过度授权,并保持内核策略的最小化。 198 + 199 + 启动 200 + ^^^^ 201 + 202 + 然而,并不是每个系统都以initramfs启动,因此编译进内核的启动策略需要具备 203 + 一定的灵活性,以明确如何为启动的下一个阶段建立信任。为此,如果我们将编译 204 + 进内核的策略设计为一个完整的IPE策略,这样系统构建者便能合理定义第一阶段启 205 + 动的需求。 206 + 207 + 可更新、无需重启的策略 208 + ~~~~~~~~~~~~~~~~~~~~~~ 209 + 210 + 随着时间的推移,系统需求发生变化(例如,之前信任的应用程序中发现漏洞、秘钥 211 + 轮换等)。更新内核以满足这些安全目标并非始终是一个合适的选择,因为内核更新并 212 + 非完全无风险的,而搁置安全更新会使系统处于脆弱状态。这意味着IPE需要一个可以 213 + 完全更新的策略(允许撤销现有的策略),并且这个更新来源必须是内核外部的(允许 214 + 再不更新内核的情况下更新策略)。 215 + 216 + 此外,由于内核在调用之间是无状态的,并且从内核空间读取磁盘上的策略文件不是一 217 + 个好主意,因此策略更新必须能够在不重启的情况下完成。 218 + 219 + 为了允许从外部来源进行更新,考虑到外部来源可能是恶意的,因此该策略需要具备可被 220 + 识别为可信的机制。这一机制通过签名链实现:策略的签名需与内核中的某个信任源相 221 + 关联。通常,这个信任源是 ``SYSTEM_TRUSTED_KEYRING`` ,这是一个在内核编译时就被 222 + 初始化填充的密钥环,因为这符合上述编译进来策略的制作者与能够部署策略更新的实体 223 + 相同的预期。 224 + 225 + 防回滚 / 防重放 226 + ~~~~~~~~~~~~~~~ 227 + 228 + 随着时间的推移,系统可能会发现漏洞,曾经受信任的资源可能不再可信,IPE的 229 + 策略也不例外。可能会出现的情况是,策略制作者误部署了一个不安全的策略, 230 + 随后再用一个安全的策略进行修正。 231 + 232 + 假设一旦不安全的策略被部署,攻击者获取了这个不安全的策略,IPE需要有一种 233 + 方式来防止从安全的策略更新回滚到不安全的策略。 234 + 235 + 最初,IPE的策略可以包含一个policy_version字段,声明系统上所有可激活策略 236 + 所需的最低版本号。这将在系统运行期间防止回滚。 237 + 238 + .. WARNING:: 239 + 240 + 然而,由于内核每次启动都是无状态的,因此该策略版本将在下次 241 + 启动时被重置为0.0.0。系统构建者需要意识到这一点,并确保在启 242 + 动后尽快部署新的安全策略,以确保攻击者部署不安全的策略的几 243 + 率最小化。 244 + 245 + 隐式操作: 246 + ~~~~~~~~~ 247 + 248 + 隐式操作的问题只有在考虑系统中多个操作具有不同级别时才会显现出来。 249 + 例如,考虑一个系统,该系统对可执行代码和系统中对其功能至关重要的 250 + 特定数据提供强大的完整性保障。在这个系统中,可能存在三种类型的 251 + 策略: 252 + 253 + 1. 一种策略,在这种策略中,如果操作未能匹配到任何规则,则该操 254 + 作将被拒绝。 255 + 2. 一种策略,在这种策略中,如果操作未能匹配到任何规则,则该操 256 + 作将被允许。 257 + 3. 一种策略,在这种策略中,如果操作未能匹配到任何规则,则执行 258 + 操作由策略作者指定。 259 + 260 + 第一种类型的策略示例如下:: 261 + 262 + op=EXECUTE integrity_verified=YES action=ALLOW 263 + 264 + 在示例系统中,这对于可执行文件来说效果很好,因为所有可执行文件 265 + 都应该拥有完整性保障。但问题出现在第二个要求上,即关于特定数据 266 + 文件的要求。这将导致如下策略(假设策略按行依次执行):: 267 + 268 + op=EXECUTE integrity_verified=YES action=ALLOW 269 + 270 + op=READ integrity_verified=NO label=critical_t action=DENY 271 + op=READ action=ALLOW 272 + 273 + 若阅读过文档,了解策略按顺序执行且默认动作是拒绝,那么这个策略的 274 + 逻辑还算清晰;但最后一行规则实际上将读取操作的默认动作改成了允许。 275 + 这种设计是必要的,因为在实际系统中,存在一些无需验证的读取操作(例 276 + 如向日志文件追加内容时的读取操作)。 277 + 278 + 第二种策略类型(未匹配任何规则时默认允许)在管控特定数据文件时逻辑 279 + 更清晰,其策略可简化为:: 280 + 281 + op=READ integrity_verified=NO label=critical_t action=DENY 282 + 283 + 但与第一种策略类似,这种默认允许的策略在管控执行操作时会存在缺陷, 284 + 因此仍需显式覆盖默认动作:: 285 + 286 + op=EXECUTE integrity_verified=YES action=ALLOW 287 + op=EXECUTE action=DENY 288 + 289 + op=READ integrity_verified=NO label=critical_t action=DENY 290 + 291 + 这就引出了第三种策略类型(自定义默认动作)。该类型无需让用户绞尽脑汁 292 + 通过空规则覆盖默认动作,而是强制用户根据自身场景思考合适的默认动作是 293 + 什么,并显式声明:: 294 + 295 + DEFAULT op=EXECUTE action=DENY 296 + op=EXECUTE integrity_verified=YES action=ALLOW 297 + 298 + DEFAULT op=READ action=ALLOW 299 + op=READ integrity_verified=NO label=critical_t action=DENY 300 + 301 + 策略调试: 302 + ~~~~~~~~~ 303 + 304 + 在开发策略时,知道策略违反了哪一行有助于减少调试成本;可以 305 + 将调查的范围缩小到导致该行为的确切行。有些完整性策略系统并 306 + 不提供这一信息,而是提供评估过程中使用的信息。这随后需要将 307 + 这些信息和策略进行关联,以分析哪里了问题。 308 + 309 + 相反,IPE只会输出匹配到的规则。这将调查范围限制到确切到策略行 310 + (在特定规则的情况下)或部分(在DEFAULT规则的情况下)。当在 311 + 评估策略时观察到策略失败时,这可以减少迭代和调查的时间。 312 + 313 + IPE的策略引擎还被设计成让人类容易理解如何调查策略失败。每一 314 + 行都会按编写顺序进行评估,因此算法非常简单,便于人类重现步 315 + 骤并找出可能导致失败的原因。而在调查其他的系统中,加载策略 316 + 时会进行优化(例如对规则排序)。在这些系统中,调试需要多个 317 + 步骤,而且没有先阅读代码的情况下,终端用户可能无法完全理解 318 + 该算法的原理。 319 + 320 + 简化策略: 321 + ~~~~~~~~~ 322 + 323 + 最后,IPE的策略是为系统管理员设计的,而不是内核开发人员。 324 + IPE不涉及单独的LSM钩子(或系统调用),而是涵盖操作。这 325 + 意味着,系统管理员不需要知道像 ``mmap`` 、 ``mprotect`` 、 326 + ``execve`` 和 ``uselib`` 这些系统调用必须有规则进行保护, 327 + 而只需要知道他们想要限制代码执行。这减少了由于缺乏对底层 328 + 系统的了解而可能导致的绕过情况;而IPE的维护者作为内核开发 329 + 人员,可以做出正确的选择,确定某些操作是否与这些操作匹配, 330 + 以及在什么条件下匹配。 331 + 332 + 实现说明 333 + -------- 334 + 335 + 匿名内存 336 + ~~~~~~~~ 337 + 338 + 在IPE中,匿名内存的处理方式与其他任何类型的访问没有区别。当匿 339 + 名内存使用 ``+X`` 映射时,它仍然会进入 ``file_mmp`` 或 340 + ``file_mprotect`` 钩子,但此时会带有一个 ``NULL`` 文件对象 341 + 这会像其他文件一样提交进行评估。然而,所有当前的信任属性都会 342 + 评估为假,因为它们都是基于文件的,而此次操作并不与任何文件相关联。 343 + 344 + .. WARNING:: 345 + 346 + 这也适用于 ``kernel_load_data`` 钩子,当内核从一个没有文件 347 + 支持的用户空间缓冲区加载数据时。在这种情况下,所有当前的信任 348 + 属性也将评估为false。 349 + 350 + Securityfs接口 351 + ~~~~~~~~~~~~~~ 352 + 353 + 每个策略的对应的securityfs树是有些独特的。例如,对于一个标准的 354 + securityfs策略树:: 355 + 356 + MyPolicy 357 + |- active 358 + |- delete 359 + |- name 360 + |- pkcs7 361 + |- policy 362 + |- update 363 + |- version 364 + 365 + 策略存储在MyPolicy对应节点的 ``->i_private`` 数据中。 366 + 367 + 测试 368 + ---- 369 + 370 + IPE为策略解析器提供了KUnit测试。推荐kunitconfig:: 371 + 372 + CONFIG_KUNIT=y 373 + CONFIG_SECURITY=y 374 + CONFIG_SECURITYFS=y 375 + CONFIG_PKCS7_MESSAGE_PARSER=y 376 + CONFIG_SYSTEM_DATA_VERIFICATION=y 377 + CONFIG_FS_VERITY=y 378 + CONFIG_FS_VERITY_BUILTIN_SIGNATURES=y 379 + CONFIG_BLOCK=y 380 + CONFIG_MD=y 381 + CONFIG_BLK_DEV_DM=y 382 + CONFIG_DM_VERITY=y 383 + CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG=y 384 + CONFIG_NET=y 385 + CONFIG_AUDIT=y 386 + CONFIG_AUDITSYSCALL=y 387 + CONFIG_BLK_DEV_INITRD=y 388 + 389 + CONFIG_SECURITY_IPE=y 390 + CONFIG_IPE_PROP_DM_VERITY=y 391 + CONFIG_IPE_PROP_DM_VERITY_SIGNATURE=y 392 + CONFIG_IPE_PROP_FS_VERITY=y 393 + CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG=y 394 + CONFIG_SECURITY_IPE_KUNIT_TEST=y 395 + 396 + 此外,IPE 具有一个基于 Python 的集成 397 + `测试套件 <https://github.com/microsoft/ipe/tree/test-suite>`_ 398 + 可以测试用户界面和强制执行功能。
+19
Documentation/translations/zh_CN/security/lsm-development.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + .. include:: ../disclaimer-zh_CN.rst 3 + 4 + :Original: Documentation/security/lsm-development.rst 5 + 6 + :翻译: 7 + 赵硕 Shuo Zhao <zhaoshuo@cqsoftware.com.cn> 8 + 9 + ================= 10 + Linux安全模块开发 11 + ================= 12 + 13 + 基于https://lore.kernel.org/r/20071026073721.618b4778@laptopd505.fenrus.org, 14 + 当一种新的LSM的意图(它试图防范什么,以及在哪些情况下人们会期望使用它)在 15 + ``Documentation/admin-guide/LSM/`` 中适当记录下来后,就会被接受进入内核。 16 + 这使得LSM的代码可以很轻松的与其目标进行对比,从而让最终用户和发行版可以更 17 + 明智地决定那些LSM适合他们的需求。 18 + 19 + 有关可用的 LSM 钩子接口的详细文档,请参阅 ``security/security.c`` 及相关结构。
+96
Documentation/translations/zh_CN/security/secrets/coco.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + .. include:: ../../disclaimer-zh_CN.rst 3 + 4 + :Original: Documentation/security/secrets/coco.rst 5 + 6 + :翻译: 7 + 8 + 赵硕 Shuo Zhao <zhaoshuo@cqsoftware.com.cn> 9 + 10 + ============ 11 + 机密计算密钥 12 + ============ 13 + 14 + 本文档介绍了在EFI驱动程序和efi_secret内核模块中,机密计算密钥从固件 15 + 到操作系统的注入处理流程。 16 + 17 + 简介 18 + ==== 19 + 20 + 机密计算硬件(如AMD SEV,Secure Encrypted Virtualization)允许虚拟机 21 + 所有者将密钥注入虚拟机(VM)内存,且主机/虚拟机监控程序无法读取这些密 22 + 钥。在SEV中,密钥注入需在虚拟机启动流程的早期阶段(客户机开始运行前) 23 + 执行。 24 + 25 + efi_secret内核模块允许用户空间应用程序通过securityfs(安全文件系统)访 26 + 问这些密钥。 27 + 28 + 密钥数据流 29 + ========== 30 + 31 + 客户机固件可能会为密钥注入预留一块指定的内存区域,并将该区域的位置(基准 32 + 客户机物理地址GPA和长度)在EFI配置表中,通过 ``LINUX_EFI_COCO_SECRET_AREA_GUID`` 33 + 条目(对应的GUID值为 ``adf956ad-e98c-484c-ae11-b51c7d336447`` )的形式发布。 34 + 固件应将此内存区域标记为 ``EFI_RESERVED_TYPE`` ,因此内核不应将其用于自身用途。 35 + 36 + 虚拟机启动过程中,虚拟机管理器可向该区域注入密钥。在AMD SEV和SEV-ES中,此 37 + 操作通过 ``KVM_SEV_LAUNCH_SECRET`` 命令执行(参见 [sev_CN]_ )。注入的“客户机 38 + 所有者密钥数据”应采用带GUID的密钥值表结构,其二进制格式在 ``drivers/virt/ 39 + coco/efi_secret/efi_secret.c`` 文件的EFI密钥区域结构部分中有详细描述。 40 + 41 + 内核启动时,内核的EFI驱动程序将保存密钥区域位置(来自EFI配置表)到 ``efi.coco_secret`` 42 + 字段。随后,它会检查密钥区域是否已填充:映射该区域并检查其内容是否以 43 + ``EFI_SECRET_TABLE_HEADER_GUID`` (对应的GUID为 ``1e74f542-71dd-4d66-963e-ef4287ff173b`` ) 44 + 开头。如果密钥区域已填充,EFI驱动程序将自动加载efi_secret内核模块,并通过securityfs将密钥 45 + 暴露给用户空间应用程序。efi_secret文件系统接口的详细信息请参考 [secrets-coco-abi_CN]_ 。 46 + 47 + 48 + 应用使用示例 49 + ============ 50 + 51 + 假设客户机需要对加密文件进行计算处理。客户机所有者通过密钥注入机制提供解密密钥 52 + (即密钥)。客户机应用程序从efi_secret文件系统读取该密钥,然后将文件解密到内存中, 53 + 接着对内容进行需要的计算。 54 + 55 + 在此示例中,主机无法从磁盘镜像中读取文件,因为文件是加密的;主机无法读取解密密钥, 56 + 因为它是通过密钥注入机制(即安全通道)传递的;主机也无法读取内存中的解密内容,因为 57 + 这是一个机密型(内存加密)客户机。 58 + 59 + 以下是一个简单的示例,展示了在客户机中使用efi_secret模块的过程,在启动时注入了 60 + 一个包含4个密钥的EFI密钥区域:: 61 + 62 + # ls -la /sys/kernel/security/secrets/coco 63 + total 0 64 + drwxr-xr-x 2 root root 0 Jun 28 11:54 . 65 + drwxr-xr-x 3 root root 0 Jun 28 11:54 .. 66 + -r--r----- 1 root root 0 Jun 28 11:54 736870e5-84f0-4973-92ec-06879ce3da0b 67 + -r--r----- 1 root root 0 Jun 28 11:54 83c83f7f-1356-4975-8b7e-d3a0b54312c6 68 + -r--r----- 1 root root 0 Jun 28 11:54 9553f55d-3da2-43ee-ab5d-ff17f78864d2 69 + -r--r----- 1 root root 0 Jun 28 11:54 e6f5a162-d67f-4750-a67c-5d065f2a9910 70 + 71 + # hd /sys/kernel/security/secrets/coco/e6f5a162-d67f-4750-a67c-5d065f2a9910 72 + 00000000 74 68 65 73 65 2d 61 72 65 2d 74 68 65 2d 6b 61 |these-are-the-ka| 73 + 00000010 74 61 2d 73 65 63 72 65 74 73 00 01 02 03 04 05 |ta-secrets......| 74 + 00000020 06 07 |..| 75 + 00000022 76 + 77 + # rm /sys/kernel/security/secrets/coco/e6f5a162-d67f-4750-a67c-5d065f2a9910 78 + 79 + # ls -la /sys/kernel/security/secrets/coco 80 + total 0 81 + drwxr-xr-x 2 root root 0 Jun 28 11:55 . 82 + drwxr-xr-x 3 root root 0 Jun 28 11:54 .. 83 + -r--r----- 1 root root 0 Jun 28 11:54 736870e5-84f0-4973-92ec-06879ce3da0b 84 + -r--r----- 1 root root 0 Jun 28 11:54 83c83f7f-1356-4975-8b7e-d3a0b54312c6 85 + -r--r----- 1 root root 0 Jun 28 11:54 9553f55d-3da2-43ee-ab5d-ff17f78864d2 86 + 87 + 88 + 参考文献 89 + ======== 90 + 91 + 请参见 [sev-api-spec_CN]_ 以获取有关SEV ``LAUNCH_SECRET`` 操作的更多信息。 92 + 93 + .. [sev_CN] Documentation/virt/kvm/x86/amd-memory-encryption.rst 94 + .. [secrets-coco-abi_CN] Documentation/ABI/testing/securityfs-secrets-coco 95 + .. [sev-api-spec_CN] https://www.amd.com/system/files/TechDocs/55766_SEV-KM_API_Specification.pdf 96 +
+3 -6
Documentation/translations/zh_CN/security/secrets/index.rst
··· 5 5 6 6 :翻译: 7 7 8 - ===================== 8 + ======== 9 9 密钥文档 10 - ===================== 10 + ======== 11 11 12 12 .. toctree:: 13 13 14 - 15 - TODOLIST: 16 - 17 - * coco 14 + coco
+1 -1
Documentation/translations/zh_CN/subsystem-apis.rst
··· 71 71 :maxdepth: 1 72 72 73 73 filesystems/index 74 + scsi/index 74 75 75 76 TODOList: 76 77 77 78 * block/index 78 79 * cdrom/index 79 - * scsi/index 80 80 * target/index 81 81 82 82 **Fixme**: 这里还需要更多的分类组织工作。