diff options
Diffstat (limited to 'Documentation/translations')
48 files changed, 5947 insertions, 43 deletions
diff --git a/Documentation/translations/it_IT/doc-guide/parse-headers.rst b/Documentation/translations/it_IT/doc-guide/parse-headers.rst index 026a23e49767..b0caa40fe1e9 100644 --- a/Documentation/translations/it_IT/doc-guide/parse-headers.rst +++ b/Documentation/translations/it_IT/doc-guide/parse-headers.rst @@ -13,28 +13,28 @@ dello spazio utente ha ulteriori vantaggi: Sphinx genererà dei messaggi d'avviso se un simbolo non viene trovato nella documentazione. Questo permette di mantenere allineate la documentazione della uAPI (API spazio utente) con le modifiche del kernel. -Il programma :ref:`parse_headers.pl <it_parse_headers>` genera questi riferimenti. +Il programma :ref:`parse_headers.py <it_parse_headers>` genera questi riferimenti. Esso dev'essere invocato attraverso un Makefile, mentre si genera la documentazione. Per avere un esempio su come utilizzarlo all'interno del kernel consultate ``Documentation/userspace-api/media/Makefile``. .. _it_parse_headers: -parse_headers.pl +parse_headers.py ^^^^^^^^^^^^^^^^ NOME **** -parse_headers.pl - analizza i file C al fine di identificare funzioni, +parse_headers.py - analizza i file C al fine di identificare funzioni, strutture, enumerati e definizioni, e creare riferimenti per Sphinx SINTASSI ******** -\ **parse_headers.pl**\ [<options>] <C_FILE> <OUT_FILE> [<EXCEPTIONS_FILE>] +\ **parse_headers.py**\ [<options>] <C_FILE> <OUT_FILE> [<EXCEPTIONS_FILE>] Dove <options> può essere: --debug, --usage o --help. diff --git a/Documentation/translations/it_IT/doc-guide/sphinx.rst b/Documentation/translations/it_IT/doc-guide/sphinx.rst index 1f513bc33618..a5c5d935febf 100644 --- a/Documentation/translations/it_IT/doc-guide/sphinx.rst +++ b/Documentation/translations/it_IT/doc-guide/sphinx.rst @@ -109,7 +109,7 @@ Sphinx. Se lo script riesce a riconoscere la vostra distribuzione, allora sarà in grado di darvi dei suggerimenti su come procedere per completare l'installazione:: - $ ./scripts/sphinx-pre-install + $ ./tools/docs/sphinx-pre-install Checking if the needed tools for Fedora release 26 (Twenty Six) are available Warning: better to also install "texlive-luatex85". You should run: @@ -119,7 +119,7 @@ l'installazione:: . sphinx_2.4.4/bin/activate pip install -r Documentation/sphinx/requirements.txt - Can't build as 1 mandatory dependency is missing at ./scripts/sphinx-pre-install line 468. + Can't build as 1 mandatory dependency is missing at ./tools/docs/sphinx-pre-install line 468. L'impostazione predefinita prevede il controllo dei requisiti per la generazione di documenti html e PDF, includendo anche il supporto per le immagini, le diff --git a/Documentation/translations/ja_JP/SubmittingPatches b/Documentation/translations/ja_JP/SubmittingPatches index 5334db471744..b950347b5993 100644 --- a/Documentation/translations/ja_JP/SubmittingPatches +++ b/Documentation/translations/ja_JP/SubmittingPatches @@ -132,6 +132,25 @@ http://savannah.nongnu.org/projects/quilt platform_set_drvdata(), but left the variable "dev" unused, delete it. +特定のコミットで導入された不具合を修正する場合(例えば ``git bisect`` で原因となった +コミットを特定したときなど)は、コミットの SHA-1 の先頭12文字と1行の要約を添えた +「Fixes:」タグを付けてください。この行は75文字を超えても構いませんが、途中で +改行せず、必ず1行で記述してください。 +例: + Fixes: 54a4f0239f2e ("KVM: MMU: make kvm_mmu_zap_page() return the number of pages it actually freed") + +以下の git の設定を使うと、git log や git show で上記形式を出力するための +専用の出力形式を追加できます:: + + [core] + abbrev = 12 + [pretty] + fixes = Fixes: %h (\"%s\") + +使用例:: + + $ git log -1 --pretty=fixes 54a4f0239f2e + Fixes: 54a4f0239f2e ("KVM: MMU: make kvm_mmu_zap_page() return the number of pages it actually freed") 3) パッチの分割 @@ -409,7 +428,7 @@ Acked-by: が必ずしもパッチ全体の承認を示しているわけでは このタグはパッチに関心があると思われる人達がそのパッチの議論に含まれていたこと を明文化します。 -14) Reported-by:, Tested-by:, Reviewed-by: および Suggested-by: の利用 +14) Reported-by:, Tested-by:, Reviewed-by:, Suggested-by: および Fixes: の利用 他の誰かによって報告された問題を修正するパッチであれば、問題報告者という寄与を クレジットするために、Reported-by: タグを追加することを検討してください。 @@ -465,6 +484,13 @@ Suggested-by: タグは、パッチのアイデアがその人からの提案に クレジットしていけば、望むらくはその人たちが将来別の機会に再度力を貸す気に なってくれるかもしれません。 +Fixes: タグは、そのパッチが以前のコミットにあった問題を修正することを示します。 +これは、バグがどこで発生したかを特定しやすくし、バグ修正のレビューに役立ちます。 +また、このタグはstableカーネルチームが、あなたの修正をどのstableカーネル +バージョンに適用すべきか判断する手助けにもなります。パッチによって修正された +バグを示すには、この方法が推奨されます。前述の、「2) パッチに対する説明」の +セクションを参照してください。 + 15) 標準的なパッチのフォーマット 標準的なパッチのサブジェクトは以下のとおりです。 diff --git a/Documentation/translations/zh_CN/admin-guide/README.rst b/Documentation/translations/zh_CN/admin-guide/README.rst index 82e628b77efd..7c2ffe7e87c7 100644 --- a/Documentation/translations/zh_CN/admin-guide/README.rst +++ b/Documentation/translations/zh_CN/admin-guide/README.rst @@ -288,4 +288,4 @@ Documentation/translations/zh_CN/admin-guide/bug-hunting.rst 。 更多用GDB调试内核的信息,请参阅: Documentation/translations/zh_CN/dev-tools/gdb-kernel-debugging.rst -和 Documentation/dev-tools/kgdb.rst 。 +和 Documentation/process/debugging/kgdb.rst 。 diff --git a/Documentation/translations/zh_CN/block/blk-mq.rst b/Documentation/translations/zh_CN/block/blk-mq.rst new file mode 100644 index 000000000000..ccc08f76ff97 --- /dev/null +++ b/Documentation/translations/zh_CN/block/blk-mq.rst @@ -0,0 +1,130 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/block/blk-mq.rst + +:翻译: + + 柯子杰 kezijie <kezijie@leap-io-kernel.com> + +:校译: + + + +================================================ +多队列块设备 I/O 排队机制 (blk-mq) +================================================ + +多队列块设备 I/O 排队机制提供了一组 API,使高速存储设备能够同时在多个队列中 +处理并发的 I/O 请求并将其提交到块设备,从而实现极高的每秒输入/输出操作次数 +(IOPS),充分发挥现代存储设备的并行能力。 + +介绍 +==== + +背景 +---- + +磁盘从 Linux 内核开发初期就已成为事实上的标准。块 I/O 子系统的目标是尽可能 +为此类设备提供最佳性能,因为它们在进行随机访问时代价极高,性能瓶颈主要在机械 +运动部件上,其速度远低于存储栈中其他任何层。其中一个软件优化例子是根据硬盘磁 +头当前的位置重新排序读/写请求。 + +然而,随着固态硬盘和非易失性存储的发展,它们没有机械部件,也不存在随机访问代 +码,并能够进行高速并行访问,存储栈的瓶颈从存储设备转移到了操作系统。为了充分 +利用这些设备设计中的并行性,引入了多队列机制。 + +原来的设计只有一个队列来存储块设备 I/O 请求,并且只使用一个锁。由于缓存中的 +脏数据和多处理器共享单锁的瓶颈,这种设计在 SMP 系统中扩展性不佳。当不同进程 +(或同一进程在不同 CPU 上)同时执行块设备 I/O 时,该单队列模型还会出现严重 +的拥塞问题。为了解决这些问题,blk-mq API 引入了多个队列,每个队列在本地 CPU +上拥有独立的入口点,从而消除了对全局锁的需求。关于其具体工作机制的更深入说明, +请参见下一节( `工作原理`_ )。 + +工作原理 +-------- + +当用户空间执行对块设备的 I/O(例如读写文件)时,blk-mq 便会介入:它将存储和 +管理发送到块设备的 I/O 请求,充当用户空间(文件系统,如果存在的话)与块设备驱 +动之间的中间层。 + +blk-mq 由两组队列组成:软件暂存队列和硬件派发队列。当请求到达块层时,它会尝 +试最短路径:直接发送到硬件队列。然而,有两种情况下可能不会这样做:如果该层有 +IO 调度器或者是希望合并请求。在这两种情况下,请求将被发送到软件队列。 + +随后,在软件队列中的请求被处理后,请求会被放置到硬件队列。硬件队列是第二阶段 +的队列,硬件可以直接访问并处理这些请求。然而,如果硬件没有足够的资源来接受更 +多请求,blk-mq 会将请求放置在临时队列中,待硬件资源充足时再发送。 + +软件暂存队列 +~~~~~~~~~~~~ + +在这些请求未直接发送到驱动时,块设备 I/O 子系统会将请求添加到软件暂存队列中 +(由 struct blk_mq_ctx 表示)。一个请求可能包含一个或多个 BIO。它们通过 struct bio +数据结构到达块层。块层随后会基于这些 BIO 构建新的结构体 struct request,用于 +与设备驱动通信。每个队列都有自己的锁,队列数量由每个 CPU 和每个 node 为基础 +来决定。 + +暂存队列可用于合并相邻扇区的请求。例如,对扇区3-6、6-7、7-9的请求可以合并 +为对扇区3-9的一个请求。即便 SSD 或 NVM 的随机访问和顺序访问响应时间相同, +合并顺序访问的请求仍可减少单独请求的数量。这种合并请求的技术称为 plugging。 + +此外,I/O 调度器还可以对请求进行重新排序以确保系统资源的公平性(例如防止某 +个应用出现“饥饿”现象)或是提高 I/O 性能。 + +I/O 调度器 +^^^^^^^^^^ + +块层实现了多种调度器,每种调度器都遵循一定启发式规则以提高 I/O 性能。它们是 +“可插拔”的(plug and play),可在运行时通过 sysfs 选择。你可以在这里阅读更 +多关于 Linux IO 调度器知识 `here +<https://www.kernel.org/doc/html/latest/block/index.html>`_。调度只发 +生在同一队列内的请求之间,因此无法合并不同队列的请求,否则会造成缓存冲突并需 +要为每个队列加锁。调度后,请求即可发送到硬件。可能选择的调度器之一是 NONE 调 +度器,这是最直接的调度器:它只将请求放到进程所在的软件队列,不进行重新排序。 +当设备开始处理硬件队列中的请求时(运行硬件队列),映射到该硬件队列的软件队列 +会按映射顺序依次清空。 + +硬件派发队列 +~~~~~~~~~~~~~ + +硬件队列(由 struct blk_mq_hw_ctx 表示)是设备驱动用来映射设备提交队列 +(或设备 DMA 环缓存)的结构体,它是块层提交路径在底层设备驱动接管请求之前的 +最后一个阶段。运行此队列时,块层会从相关软件队列中取出请求,并尝试派发到硬件。 + +如果请求无法直接发送到硬件,它们会被加入到请求的链表(``hctx->dispatch``) 中。 +随后,当块层下次运行该队列时,会优先发送位于 ``dispatch`` 链表中的请求, +以确保那些最早准备好发送的请求能够得到公平调度。硬件队列的数量取决于硬件及 +其设备驱动所支持的硬件上下文数,但不会超过系统的CPU核心数。在这个阶段不 +会发生重新排序,每个软件队列都有一组硬件队列来用于提交请求。 + +.. note:: + + 块层和设备协议都不保证请求完成顺序。此问题需由更高层处理,例如文件系统。 + +基于标识的完成机制 +~~~~~~~~~~~~~~~~~~~ + +为了指示哪一个请求已经完成,每个请求都会被分配一个整数标识,该标识的取值范围 +是从0到分发队列的大小。这个标识由块层生成,并在之后由设备驱动使用,从而避 +免了为每个请求再单独创建冗余的标识符。当请求在驱动中完成时,驱动会将该标识返 +回给块层,以通知该请求已完成。这样,块层就无需再进行线性搜索来确定是哪一个 +I/O 请求完成了。 + +更多阅读 +-------- + +- `Linux 块 I/O:多队列 SSD 并发访问简介 <http://kernel.dk/blk-mq.pdf>`_ + +- `NOOP 调度器 <https://en.wikipedia.org/wiki/Noop_scheduler>`_ + +- `Null 块设备驱动程序 <https://www.kernel.org/doc/html/latest/block/null_blk.html>`_ + +源代码 +====== + +该API在以下内核代码中: + +include/linux/blk-mq.h + +block/blk-mq.c
\ No newline at end of file diff --git a/Documentation/translations/zh_CN/block/data-integrity.rst b/Documentation/translations/zh_CN/block/data-integrity.rst new file mode 100644 index 000000000000..b31aa9ef8954 --- /dev/null +++ b/Documentation/translations/zh_CN/block/data-integrity.rst @@ -0,0 +1,192 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/block/data-integrity.rst + +:翻译: + + 柯子杰 kezijie <kezijie@leap-io-kernel.com> + +:校译: + +========== +数据完整性 +========== + +1. 引言 +======= + +现代文件系统对数据和元数据都进行了校验和保护以防止数据损坏。然而,这种损坏的 +检测是在读取时才进行,这可能发生在数据写入数月之后。到那时,应用程序尝试写入 +的原始数据很可能已经丢失。 + +解决方案是确保磁盘实际存储的内容就是应用程序想存储的。SCSI 协议族(如 SBC +数据完整性字段、SCC 保护提案)以及 SATA/T13(外部路径保护)最近新增的功能, +通过在 I/O 中附加完整性元数据的方式,试图解决这一问题。完整性元数据(在 +SCSI 术语中称为保护信息)包括每个扇区的校验和,以及一个递增计数器,用于确保 +各扇区按正确顺序被写入盘。在某些保护方案中,还能保证 I/O 写入磁盘的正确位置。 + +当前的存储控制器和设备实现了多种保护措施,例如校验和和数据清理。但这些技术通 +常只在各自的独立域内工作,或最多仅在 I/O 路径的相邻节点之间发挥作用。DIF 及 +其它数据完整性拓展有意思的点在于保护格式定义明确,I/O 路径上的每个节点都可以 +验证 I/O 的完整性,如检测到损坏可直接拒绝。这不仅可以防止数据损坏,还能够隔 +离故障点。 + +2. 数据完整性拓展 +================= + +如上所述,这些协议扩展只保护控制器与存储设备之间的路径。然而,许多控制器实际 +上允许操作系统与完整性元数据(IMD)交互。我们一直与多家 FC/SAS HBA 厂商合作, +使保护信息能够在其控制器与操作系统之间传输。 + +SCSI 数据完整性字段通过在每个扇区后附加8字节的保护信息来实现。数据 + 完整 +性元数据存储在磁盘的520字节扇区中。数据 + IMD 在控制器与目标设备之间传输 +时是交错组合在一起的。T13 提案的方式类似。 + +由于操作系统处理520字节(甚至 4104 字节)扇区非常不便,我们联系了多家 HBA +厂商,并鼓励它们分离数据与完整性元数据的 scatter-gather lists。 + +控制器在写入时会将数据缓冲区和完整性元数据缓冲区的数据交错在一起,并在读取时 +会拆分它们。这样,Linux 就能直接通过 DMA 将数据缓冲区传输到主机内存或从主机 +内存读取,而无需修改页缓存。 + +此外,SCSI 与 SATA 规范要求的16位 CRC 校验在软件中计算代价较高。基准测试发 +现,计算此校验在高负载情形下显著影响系统性能。一些控制器允许在操作系统接口处 +使用轻量级校验。例如 Emulex 支持 TCP/IP 校验。操作系统提供的 IP 校验在写入 +时会转换为16位 CRC,读取时则相反。这允许 Linux 或应用程序以极低的开销生成 +完整性元数据(与软件 RAID5 相当)。 + +IP 校验在检测位错误方面比 CRC 弱,但关键在于数据缓冲区与完整性元数据缓冲区 +的分离。只有这两个不同的缓冲区匹配,I/O 才能完成。 + +数据与完整性元数据缓冲区的分离以及校验选择被称为数据完整性扩展。由于这些扩展 +超出了协议主体(T10、T13)的范围,Oracle 及其合作伙伴正尝试在存储网络行业协 +会内对其进行标准化。 + +3. 内核变更 +=========== + +Linux 中的数据完整性框架允许将保护信息固定到 I/O 上,并在支持该功能的控制器 +之间发送和接收。 + +SCSI 和 SATA 中完整性扩展的优势在于,它们能够保护从应用程序到存储设备的整个 +路径。然而,这同时也是最大的劣势。这意味着保护信息必须采用磁盘可以理解的格式。 + +通常,Linux/POSIX 应用程序并不关心所访问存储设备的具体细节。虚拟文件系统层 +和块层会让硬件扇区大小和传输协议对应用程序完全透明。 + +然而,在准备发送到磁盘的保护信息时,就需要这种细节。因此,端到端保护方案的概 +念实际上违反了层次结构。应用程序完全不应该知道它访问的是 SCSI 还是 SATA 磁盘。 + +Linux 中实现的数据完整性支持尝试将这些细节对应用程序隐藏。就应用程序(以及在 +某种程度上内核)而言,完整性元数据是附加在 I/O 上的不透明信息。 + +当前实现允许块层自动为任何 I/O 生成保护信息。最终目标是将用户数据的完整性元 +数据计算移至用户空间。内核中产生的元数据和其他 I/O 仍将使用自动生成接口。 + +一些存储设备允许为每个硬件扇区附加一个16位的标识值。这个标识空间的所有者是 +块设备的所有者,也就是在多数情况下由文件系统掌控。文件系统可以利用这额外空间 +按需为扇区附加标识。由于标识空间有限,块接口允许通过交错方式对更大的数据块标 +识。这样,8*16位的信息可以附加到典型的 4KB 文件系统块上。 + +这也意味着诸如 fsck 和 mkfs 等应用程序需要能够从用户空间访问并操作这些标记。 +为此,正在开发一个透传接口。 + +4. 块层实现细节 +=============== + +4.1 Bio +-------- + +当启用 CONFIG_BLK_DEV_INTEGRITY 时,数据完整性补丁会在 struct bio 中添加 +一个新字段。调用 bio_integrity(bio) 会返回一个指向 struct bip 的指针,该 +结构体包含了该 bio 的完整性负载。本质上,bip 是一个精简版的 struct bio,其 +中包含一个 bio_vec,用于保存完整性元数据以及所需的维护信息(bvec 池、向量计 +数等)。 + +内核子系统可以通过调用 bio_integrity_alloc(bio) 来为某个 bio 启用数据完整 +性保护。该函数会分配并附加一个 bip 到该 bio 上。 + +随后使用 bio_integrity_add_page() 将包含完整性元数据的单独页面附加到该 bio。 + +调用 bio_free() 会自动释放bip。 + +4.2 块设备 +----------- + +块设备可以在 queue_limits 结构中的 integrity 子结构中设置完整性信息。 + +对于分层块设备,需要选择一个适用于所有子设备的完整性配置文件。可以使用 +queue_limits_stack_integrity() 来协助完成该操作。目前,DM 和 MD linear、 +RAID0 和 RAID1 已受支持。而RAID4/5/6因涉及应用标签仍需额外的开发工作。 + +5.0 块层完整性API +================== + +5.1 普通文件系统 +----------------- + + 普通文件系统并不知道其下层块设备具备发送或接收完整性元数据的能力。 + 在执行写操作时,块层会在调用 submit_bio() 时自动生成完整性元数据。 + 在执行读操作时,I/O 完成后会触发完整性验证。 + + IMD 的生成与验证行为可以通过以下开关控制:: + + /sys/block/<bdev>/integrity/write_generate + + and:: + + /sys/block/<bdev>/integrity/read_verify + + flags. + +5.2 具备完整性感知的文件系统 +---------------------------- + + 具备完整性感知能力的文件系统可以在准备 I/O 时附加完整性元数据, + 并且如果底层块设备支持应用标签空间,也可以加以利用。 + + + `bool bio_integrity_prep(bio);` + + 要为写操作生成完整性元数据或为读操作设置缓冲区,文件系统必须调用 + bio_integrity_prep(bio)。 + + 在调用此函数之前,必须先设置好 bio 的数据方向和起始扇区,并确 + 保该 bio 已经添加完所有的数据页。调用者需要自行保证,在 I/O 进行 + 期间 bio 不会被修改。如果由于某种原因准备失败,则应当以错误状态 + 完成该 bio。 + +5.3 传递已有的完整性元数据 +-------------------------- + + 能够自行生成完整性元数据或可以从用户空间传输完整性元数据的文件系统, + 可以使用如下接口: + + + `struct bip * bio_integrity_alloc(bio, gfp_mask, nr_pages);` + + 为 bio 分配完整性负载并挂载到 bio 上。nr_pages 表示需要在 + integrity bio_vec list 中存储多少页保护数据(类似 bio_alloc)。 + + 完整性负载将在 bio_free() 被调用时释放。 + + + `int bio_integrity_add_page(bio, page, len, offset);` + + 将包含完整性元数据的一页附加到已有的 bio 上。该 bio 必须已有 bip, + 即必须先调用 bio_integrity_alloc()。对于写操作,页中的完整 + 性元数据必须采用目标设备可识别的格式,但有一个例外,当请求在 I/O 栈 + 中传递时,扇区号会被重新映射。这意味着通过此接口添加的页在 I/O 过程 + 中可能会被修改!完整性元数据中的第一个引用标签必须等于 bip->bip_sector。 + + 只要 bip bio_vec array(nr_pages)有空间,就可以继续通过 + bio_integrity_add_page()添加页。 + + 当读操作完成后,附加的页将包含从存储设备接收到的完整性元数据。 + 接收方需要处理这些元数据,并在操作完成时验证数据完整性 + + +---------------------------------------------------------------------- + +2007-12-24 Martin K. Petersen <martin.petersen@oracle.com>
\ No newline at end of file diff --git a/Documentation/translations/zh_CN/block/index.rst b/Documentation/translations/zh_CN/block/index.rst new file mode 100644 index 000000000000..f2ae5096ed68 --- /dev/null +++ b/Documentation/translations/zh_CN/block/index.rst @@ -0,0 +1,35 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/block/index.rst + +:翻译: + + 柯子杰 ke zijie <kezijie@leap-io-kernel.com> + +:校译: + +===== +Block +===== + +.. toctree:: + :maxdepth: 1 + + blk-mq + data-integrity + +TODOList: +* bfq-iosched +* biovecs +* cmdline-partition +* deadline-iosched +* inline-encryption +* ioprio +* kyber-iosched +* null_blk +* pr +* stat +* switching-sched +* writeback_cache_control +* ublk diff --git a/Documentation/translations/zh_CN/dev-tools/gdb-kernel-debugging.rst b/Documentation/translations/zh_CN/dev-tools/gdb-kernel-debugging.rst index 282aacd33442..0b382a32b3fe 100644 --- a/Documentation/translations/zh_CN/dev-tools/gdb-kernel-debugging.rst +++ b/Documentation/translations/zh_CN/dev-tools/gdb-kernel-debugging.rst @@ -2,7 +2,7 @@ .. include:: ../disclaimer-zh_CN.rst -:Original: Documentation/dev-tools/gdb-kernel-debugging.rst +:Original: Documentation/process/debugging/gdb-kernel-debugging.rst :Translator: 高超 gao chao <gaochao49@huawei.com> 通过gdb调试内核和模块 diff --git a/Documentation/translations/zh_CN/doc-guide/checktransupdate.rst b/Documentation/translations/zh_CN/doc-guide/checktransupdate.rst index d20b4ce66b9f..dbfd65398077 100644 --- a/Documentation/translations/zh_CN/doc-guide/checktransupdate.rst +++ b/Documentation/translations/zh_CN/doc-guide/checktransupdate.rst @@ -28,15 +28,15 @@ :: - ./scripts/checktransupdate.py --help + tools/docs/checktransupdate.py --help 具体用法请参考参数解析器的输出 示例 -- ``./scripts/checktransupdate.py -l zh_CN`` +- ``tools/docs/checktransupdate.py -l zh_CN`` 这将打印 zh_CN 语言中需要更新的所有文件。 -- ``./scripts/checktransupdate.py Documentation/translations/zh_CN/dev-tools/testing-overview.rst`` +- ``tools/docs/checktransupdate.py Documentation/translations/zh_CN/dev-tools/testing-overview.rst`` 这将只打印指定文件的状态。 然后输出类似如下的内容: diff --git a/Documentation/translations/zh_CN/doc-guide/contributing.rst b/Documentation/translations/zh_CN/doc-guide/contributing.rst index 394a13b438b0..b0c8ba782b16 100644 --- a/Documentation/translations/zh_CN/doc-guide/contributing.rst +++ b/Documentation/translations/zh_CN/doc-guide/contributing.rst @@ -124,7 +124,7 @@ C代码编译器发出的警告常常会被视为误报,从而导致出现了 这使得这些信息更难找到,例如使Sphinx无法生成指向该文档的链接。将 ``kernel-doc`` 指令添加到文档中以引入这些注释可以帮助社区获得为编写注释所做工作的全部价值。 -``scripts/find-unused-docs.sh`` 工具可以用来找到这些被忽略的评论。 +``tools/docs/find-unused-docs.sh`` 工具可以用来找到这些被忽略的评论。 请注意,将导出的函数和数据结构引入文档是最有价值的。许多子系统还具有供内部 使用的kernel-doc注释;除非这些注释放在专门针对相关子系统开发人员的文档中, diff --git a/Documentation/translations/zh_CN/doc-guide/parse-headers.rst b/Documentation/translations/zh_CN/doc-guide/parse-headers.rst index a08819e904ed..65d9dc5143ff 100644 --- a/Documentation/translations/zh_CN/doc-guide/parse-headers.rst +++ b/Documentation/translations/zh_CN/doc-guide/parse-headers.rst @@ -13,20 +13,20 @@ 有时,为了描述用户空间API并在代码和文档之间生成交叉引用,需要包含头文件和示例 C代码。为用户空间API文件添加交叉引用还有一个好处:如果在文档中找不到相应符号, Sphinx将生成警告。这有助于保持用户空间API文档与内核更改同步。 -:ref:`parse_headers.pl <parse_headers_zh>` 提供了生成此类交叉引用的一种方法。 +:ref:`parse_headers.py <parse_headers_zh>` 提供了生成此类交叉引用的一种方法。 在构建文档时,必须通过Makefile调用它。有关如何在内核树中使用它的示例,请参阅 ``Documentation/userspace-api/media/Makefile`` 。 .. _parse_headers_zh: -parse_headers.pl +parse_headers.py ---------------- 脚本名称 ~~~~~~~~ -parse_headers.pl——解析一个C文件,识别函数、结构体、枚举、定义并对Sphinx文档 +parse_headers.py——解析一个C文件,识别函数、结构体、枚举、定义并对Sphinx文档 创建交叉引用。 @@ -34,7 +34,7 @@ parse_headers.pl——解析一个C文件,识别函数、结构体、枚举、 ~~~~~~~~ -\ **parse_headers.pl**\ [<选项>] <C文件> <输出文件> [<例外文件>] +\ **parse_headers.py**\ [<选项>] <C文件> <输出文件> [<例外文件>] <选项> 可以是: --debug, --help 或 --usage 。 diff --git a/Documentation/translations/zh_CN/doc-guide/sphinx.rst b/Documentation/translations/zh_CN/doc-guide/sphinx.rst index 23eac67fbc30..3375c6f3a811 100644 --- a/Documentation/translations/zh_CN/doc-guide/sphinx.rst +++ b/Documentation/translations/zh_CN/doc-guide/sphinx.rst @@ -84,7 +84,7 @@ PDF和LaTeX构建 这有一个脚本可以自动检查Sphinx依赖项。如果它认得您的发行版,还会提示您所用发行 版的安装命令:: - $ ./scripts/sphinx-pre-install + $ ./tools/docs/sphinx-pre-install Checking if the needed tools for Fedora release 26 (Twenty Six) are available Warning: better to also install "texlive-luatex85". You should run: @@ -94,7 +94,7 @@ PDF和LaTeX构建 . sphinx_2.4.4/bin/activate pip install -r Documentation/sphinx/requirements.txt - Can't build as 1 mandatory dependency is missing at ./scripts/sphinx-pre-install line 468. + Can't build as 1 mandatory dependency is missing at ./tools/docs/sphinx-pre-install line 468. 默认情况下,它会检查html和PDF的所有依赖项,包括图像、数学表达式和LaTeX构建的 需求,并假设将使用虚拟Python环境。html构建所需的依赖项被认为是必需的,其他依 diff --git a/Documentation/translations/zh_CN/filesystems/dnotify.rst b/Documentation/translations/zh_CN/filesystems/dnotify.rst new file mode 100644 index 000000000000..5ab109b9424c --- /dev/null +++ b/Documentation/translations/zh_CN/filesystems/dnotify.rst @@ -0,0 +1,67 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/filesystems/dnotify.rst + +:翻译: + + 王龙杰 Wang Longjie <wang.longjie1@zte.com.cn> + +============== +Linux 目录通知 +============== + + Stephen Rothwell <sfr@canb.auug.org.au> + +目录通知的目的是使用户应用程序能够在目录或目录中的任何文件发生变更时收到通知。基本机制包括应用程序 +通过 fcntl(2) 调用在目录上注册通知,通知本身则通过信号传递。 + +应用程序可以决定希望收到哪些 “事件” 的通知。当前已定义的事件如下: + + ========= ===================================== + DN_ACCESS 目录中的文件被访问(read) + DN_MODIFY 目录中的文件被修改(write,truncate) + DN_CREATE 目录中创建了文件 + DN_DELETE 目录中的文件被取消链接 + DN_RENAME 目录中的文件被重命名 + DN_ATTRIB 目录中的文件属性被更改(chmod,chown) + ========= ===================================== + +通常,应用程序必须在每次通知后重新注册,但如果将 DN_MULTISHOT 与事件掩码进行或运算,则注册 +将一直保持有效,直到被显式移除(通过注册为不接收任何事件)。 + +默认情况下,SIGIO 信号将被传递给进程,且不附带其他有用的信息。但是,如果使用 F_SETSIG fcntl(2) +调用让内核知道要传递哪个信号,一个 siginfo 结构体将被传递给信号处理程序,该结构体的 si_fd 成员将 +包含与发生事件的目录相关联的文件描述符。 + +应用程序最好选择一个实时信号(SIGRTMIN + <n>),以便通知可以被排队。如果指定了 DN_MULTISHOT, +这一点尤为重要。注意,SIGRTMIN 通常是被阻塞的,因此最好使用(至少)SIGRTMIN + 1。 + +实现预期(特性与缺陷 :-)) +-------------------------- + +对于文件的任何本地访问,通知都应能正常工作,即使实际文件系统位于远程服务器上。这意味着,对本地用户 +模式服务器提供的文件的远程访问应能触发通知。同样的,对本地内核 NFS 服务器提供的文件的远程访问 +也应能触发通知。 + +为了尽可能减小对文件系统代码的影响,文件硬链接的问题已被忽略。因此,如果一个文件(x)存在于两个 +目录(a 和 b)中,通过名称”a/x”对该文件进行的更改应通知给期望接收目录“a”通知的程序,但不会 +通知给期望接收目录“b”通知的程序。 + +此外,取消链接的文件仍会在它们链接到的最后一个目录中触发通知。 + +配置 +---- + +Dnotify 由 CONFIG_DNOTIFY 配置选项控制。禁用该选项时,fcntl(fd, F_NOTIFY, ...) 将返 +回 -EINVAL。 + +示例 +---- +具体示例可参见 tools/testing/selftests/filesystems/dnotify_test.c。 + +注意 +---- +从 Linux 2.6.13 开始,dnotify 已被 inotify 取代。有关 inotify 的更多信息,请参见 +Documentation/filesystems/inotify.rst。 diff --git a/Documentation/translations/zh_CN/filesystems/gfs2-glocks.rst b/Documentation/translations/zh_CN/filesystems/gfs2-glocks.rst new file mode 100644 index 000000000000..abfd2f2f94e9 --- /dev/null +++ b/Documentation/translations/zh_CN/filesystems/gfs2-glocks.rst @@ -0,0 +1,211 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/filesystems/gfs2-glocks.rst + +:翻译: + + 邵明寅 Shao Mingyin <shao.mingyin@zte.com.cn> + +:校译: + + 杨涛 yang tao <yang.tao172@zte.com.cn> + +================== +Glock 内部加锁规则 +================== + +本文档阐述 glock 状态机内部运作的基本原理。每个 glock(即 +fs/gfs2/incore.h 中的 struct gfs2_glock)包含两把主要的内部锁: + + 1. 自旋锁(gl_lockref.lock):用于保护内部状态(如 + gl_state、gl_target)和持有者列表(gl_holders) + 2. 非阻塞的位锁(GLF_LOCK):用于防止其他线程同时调用 + DLM 等操作。若某线程获取此锁,则在释放时必须调用 + run_queue(通常通过工作队列),以确保所有待处理任务 + 得以完成。 + +gl_holders 列表包含与该 glock 关联的所有排队锁请求(不 +仅是持有者)。若存在已持有的锁,它们将位于列表开头的连 +续条目中。锁的授予严格遵循排队顺序。 + +glock 层用户可请求三种锁状态:共享(SH)、延迟(DF)和 +排他(EX)。它们对应以下 DLM 锁模式: + +========== ====== ===================================================== +Glock 模式 DLM 锁模式 +========== ====== ===================================================== + UN IV/NL 未加锁(无关联的 DLM 锁)或 NL + SH PR 受保护读(Protected read) + DF CW 并发写(Concurrent write) + EX EX 排他(Exclusive) +========== ====== ===================================================== + +因此,DF 本质上是一种与“常规”共享锁模式(SH)互斥的共 +享模式。在 GFS2 中,DF 模式专用于直接 I/O 操作。Glock +本质上是锁加缓存管理例程的组合,其缓存规则如下: + +========== ============== ========== ========== ============== +Glock 模式 缓存元数据 缓存数据 脏数据 脏元数据 +========== ============== ========== ========== ============== + UN 否 否 否 否 + DF 是 否 否 否 + SH 是 是 否 否 + EX 是 是 是 是 +========== ============== ========== ========== ============== + +这些规则通过为每种 glock 定义的操作函数实现。并非所有 +glock 类型都使用全部的模式,例如仅 inode glock 使用 DF 模 +式。 + +glock 操作函数及类型常量说明表: + +============== ======================================================== +字段 用途 +============== ======================================================== +go_sync 远程状态变更前调用(如同步脏数据) +go_xmote_bh 远程状态变更后调用(如刷新缓存) +go_inval 远程状态变更需使缓存失效时调用 +go_instantiate 获取 glock 时调用 +go_held 每次获取 glock 持有者时调用 +go_dump 为 debugfs 文件打印对象内容,或出错时将 glock 转储至日志 +go_callback 若 DLM 发送回调以释放此锁时调用 +go_unlocked 当 glock 解锁时调用(dlm_unlock()) +go_type glock 类型,``LM_TYPE_*`` +go_flags 若 glock 关联地址空间,则设置GLOF_ASPACE 标志 +============== ======================================================== + +每种锁的最短持有时间是指在远程锁授予后忽略远程降级请求 +的时间段。此举旨在防止锁在集群节点间持续弹跳而无实质进 +展的情况,此现象常见于多节点写入的共享内存映射文件。通 +过延迟响应远程回调的降级操作,为用户空间程序争取页面取 +消映射前的处理时间。 + +未来计划将 glock 的 "EX" 模式设为本地共享,使本地锁通 +过 i_mutex 实现而非 glock。 + +glock 操作函数的加锁规则: + +============== ====================== ============================= +操作 GLF_LOCK 位锁持有 gl_lockref.lock 自旋锁持有 +============== ====================== ============================= +go_sync 是 否 +go_xmote_bh 是 否 +go_inval 是 否 +go_instantiate 否 否 +go_held 否 否 +go_dump 有时 是 +go_callback 有时(N/A) 是 +go_unlocked 是 否 +============== ====================== ============================= + +.. Note:: + + 若入口处持有锁则操作期间不得释放位锁或自旋锁。 + go_dump 和 do_demote_ok 严禁阻塞。 + 仅当 glock 状态指示其缓存最新数据时才会调用 go_dump。 + +GFS2 内部的 glock 加锁顺序: + + 1. i_rwsem(如需要) + 2. 重命名 glock(仅用于重命名) + 3. Inode glock + (父级优先于子级,同级 inode 按锁编号排序) + 4. Rgrp glock(用于(反)分配操作) + 5. 事务 glock(通过 gfs2_trans_begin,非读操作) + 6. i_rw_mutex(如需要) + 7. 页锁(始终最后,至关重要!) + +每个 inode 对应两把 glock:一把管理 inode 本身(加锁顺 +序如上),另一把(称为 iopen glock)结合 inode 的 +i_nlink 字段决定 inode 生命周期。inode 加锁基于单个 +inode,rgrp 加锁基于单个 rgrp。通常优先获取本地锁再获 +取集群锁。 + +Glock 统计 +---------- + +统计分为两类:超级块相关统计和单个 glock 相关统计。超级 +块统计按每 CPU 执行以减少收集开销,并进一步按 glock 类 +型细分。所有时间单位为纳秒。 + +超级块和 glock 统计收集相同信息。超级块时序统计为 glock +时序统计提供默认值,使新建 glock 具有合理的初始值。每个 +glock 的计数器在创建时初始化为零,当 glock 从内存移除时 +统计丢失。 + +统计包含三组均值/方差对及两个计数器。均值/方差对为平滑 +指数估计,算法与网络代码中的往返时间计算类似(参见《 +TCP/IP详解 卷1》第21.3节及《卷2》第25.10节)。与 TCP/IP +案例不同,此处均值/方差未缩放且单位为整数纳秒。 + +三组均值/方差对测量以下内容: + + 1. DLM 锁时间(非阻塞请求) + 2. DLM 锁时间(阻塞请求) + 3. 请求间隔时间(指向 DLM) + +非阻塞请求指无论目标 DLM 锁处于何种状态均能立即完成的请求。 +当前满足条件的请求包括:(a)锁当前状态为互斥(如锁降级)、 +(b)请求状态为空置或解锁(同样如锁降级)、或(c)设置"try lock" +标志的请求。其余锁请求均属阻塞请求。 + +两个计数器分别统计: + 1. 锁请求总数(决定均值/方差计算的数据量) + 2. glock 代码顶层的持有者排队数(通常远大于 DLM 锁请求数) + +为什么收集这些统计数据?我们需深入分析时序参数的动因如下: + +1. 更精准设置 glock "最短持有时间" +2. 快速识别性能问题 +3. 改进资源组分配算法(基于锁等待时间而非盲目 "try lock") + +因平滑更新的特性,采样量的阶跃变化需经 8 次采样(方差需 +4 次)才能完全体现,解析结果时需审慎考虑。 + +通过锁请求完成时间和 glock 平均锁请求间隔时间,可计算节 +点使用 glock 时长与集群共享时长的占比,对设置锁最短持有 +时间至关重要。 + +我们已采取严谨措施,力求精准测量目标量值。任何测量系统均 +存在误差,但我期望当前方案已达到合理精度极限。 + +超级块状态统计路径:: + + /sys/kernel/debug/gfs2/<fsname>/sbstats + +Glock 状态统计路径:: + + /sys/kernel/debug/gfs2/<fsname>/glstats + +(假设 debugfs 挂载于 /sys/kernel/debug,且 <fsname> 替 +换为对应 GFS2 文件系统名) + +输出缩写说明: + +========= ============================================ +srtt 非阻塞 DLM 请求的平滑往返时间 +srttvar srtt 的方差估计 +srttb (潜在)阻塞 DLM 请求的平滑往返时间 +srttvarb srttb 的方差估计 +sirt DLM 请求的平滑请求间隔时间 +sirtvar sirt 的方差估计 +dlm DLM 请求数(glstats 文件中的 dcnt) +queue 排队的 glock 请求数(glstats 文件中的 qcnt) +========= ============================================ + +sbstats文件按glock类型(每种类型8行)和CPU核心(每CPU一列) +记录统计数据集。glstats文件则为每个glock提供统计集,其格式 +与glocks文件类似,但所有时序统计量均采用均值/方差格式存储。 + +gfs2_glock_lock_time 跟踪点实时输出目标 glock 的当前统计 +值,并附带每次接收到的dlm响应附加信息: + +====== ============ +status DLM 请求状态 +flags DLM 请求标志 +tdiff 该请求的耗时 +====== ============ + +(其余字段同上表) diff --git a/Documentation/translations/zh_CN/filesystems/gfs2-uevents.rst b/Documentation/translations/zh_CN/filesystems/gfs2-uevents.rst new file mode 100644 index 000000000000..3975c4544118 --- /dev/null +++ b/Documentation/translations/zh_CN/filesystems/gfs2-uevents.rst @@ -0,0 +1,97 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/filesystems/gfs2-uevents.rst + +:翻译: + + 邵明寅 Shao Mingyin <shao.mingyin@zte.com.cn> + +:校译: + + 杨涛 yang tao <yang.tao172@zte.com.cn> + +=============== +uevents 与 GFS2 +=============== + +在 GFS2 文件系统的挂载生命周期内,会生成多个 uevent。 +本文档解释了这些事件的含义及其用途(被 gfs2-utils 中的 gfs_controld 使用)。 + +GFS2 uevents 列表 +================= + +1. ADD +------ + +ADD 事件发生在挂载时。它始终是新建文件系统生成的第一个 uevent。如果挂载成 +功,随后会生成 ONLINE uevent。如果挂载失败,则随后会生成 REMOVE uevent。 + +ADD uevent 包含两个环境变量:SPECTATOR=[0|1] 和 RDONLY=[0|1],分别用 +于指定文件系统的观察者状态(一种未分配日志的只读挂载)和只读状态(已分配日志)。 + +2. ONLINE +--------- + +ONLINE uevent 在成功挂载或重新挂载后生成。它具有与 ADD uevent 相同的环 +境变量。ONLINE uevent 及其用于标识观察者和 RDONLY 状态的两个环境变量是较 +新版本内核引入的功能(2.6.32-rc+ 及以上),旧版本内核不会生成此事件。 + +3. CHANGE +--------- + +CHANGE uevent 在两种场景下使用。一是报告第一个节点成功挂载文件系统时 +(FIRSTMOUNT=Done)。这作为信号告知 gfs_controld,此时集群中其他节点可以 +安全挂载该文件系统。 + +另一个 CHANGE uevent 用于通知文件系统某个日志的日志恢复已完成。它包含两个 +环境变量:JID= 指定刚恢复的日志 ID,RECOVERY=[Done|Failed] 表示操作成 +功与否。这些 uevent 会在每次日志恢复时生成,无论是在初始挂载过程中,还是 +gfs_controld 通过 /sys/fs/gfs2/<fsname>/lock_module/recovery 文件 +请求特定日志恢复的结果。 + +由于早期版本的 gfs_controld 使用 CHANGE uevent 时未检查环境变量以确定状 +态,若为其添加新功能,存在用户工具版本过旧导致集群故障的风险。因此,在新增用 +于标识成功挂载或重新挂载的 uevent 时,选择了使用 ONLINE uevent。 + +4. OFFLINE +---------- + +OFFLINE uevent 仅在文件系统发生错误时生成,是 "withdraw" 机制的一部分。 +当前该事件未提供具体错误信息,此问题有待修复。 + +5. REMOVE +--------- + +REMOVE uevent 在挂载失败结束或卸载文件系统时生成。所有 REMOVE uevent +之前都至少存在同一文件系统的 ADD uevent。与其他 uevent 不同,它由内核的 +kobject 子系统自动生成。 + + +所有 GFS2 uevents 的通用信息(uevent 环境变量) +=============================================== + +1. LOCKTABLE= +-------------- + +LOCKTABLE 是一个字符串,其值来源于挂载命令行(locktable=)或 fstab 文件。 +它用作文件系统标签,并为 lock_dlm 类型的挂载提供加入集群所需的信息。 + +2. LOCKPROTO= +------------- + +LOCKPROTO 是一个字符串,其值取决于挂载命令行或 fstab 中的设置。其值将是 +lock_nolock 或 lock_dlm。未来可能支持其他锁管理器。 + +3. JOURNALID= +------------- + +如果文件系统正在使用日志(观察者挂载不分配日志),则所有 GFS2 uevent 中都 +会包含此变量,其值为数字形式的日志 ID。 + +4. UUID= +-------- + +在较新版本的 gfs2-utils 中,mkfs.gfs2 会向文件系统超级块写入 UUID。若存 +在 UUID,所有与该文件系统相关的 uevent 中均会包含此信息。 diff --git a/Documentation/translations/zh_CN/filesystems/gfs2.rst b/Documentation/translations/zh_CN/filesystems/gfs2.rst new file mode 100644 index 000000000000..ffa62b12b019 --- /dev/null +++ b/Documentation/translations/zh_CN/filesystems/gfs2.rst @@ -0,0 +1,57 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/filesystems/gfs2.rst + +:翻译: + + 邵明寅 Shao Mingyin <shao.mingyin@zte.com.cn> + +:校译: + + 杨涛 yang tao <yang.tao172@zte.com.cn> + +===================================== +全局文件系统 2 (Global File System 2) +===================================== + +GFS2 是一个集群文件系统。它允许一组计算机同时使用在它们之间共享的块设备(通 +过 FC、iSCSI、NBD 等)。GFS2 像本地文件系统一样读写块设备,但也使用一个锁 +模块来让计算机协调它们的 I/O 操作,从而维护文件系统的一致性。GFS2 的出色特 +性之一是完美一致性——在一台机器上对文件系统所做的更改会立即显示在集群中的所 +有其他机器上。 + +GFS2 使用可互换的节点间锁定机制,当前支持的机制有: + + lock_nolock + - 允许将 GFS2 用作本地文件系统 + + lock_dlm + - 使用分布式锁管理器 (dlm) 进行节点间锁定。 + 该 dlm 位于 linux/fs/dlm/ + +lock_dlm 依赖于在上述 URL 中找到的用户空间集群管理系统。 + +若要将 GFS2 用作本地文件系统,则不需要外部集群系统,只需:: + + $ mkfs -t gfs2 -p lock_nolock -j 1 /dev/block_device + $ mount -t gfs2 /dev/block_device /dir + +在所有集群节点上都需要安装 gfs2-utils 软件包;对于 lock_dlm,您还需要按 +照文档配置 dlm 和 corosync 用户空间工具。 + +gfs2-utils 可在 https://pagure.io/gfs2-utils 找到。 + +GFS2 在磁盘格式上与早期版本的 GFS 不兼容,但它已相当接近。 + +以下手册页 (man pages) 可在 gfs2-utils 中找到: + + ============ ============================================= + fsck.gfs2 用于修复文件系统 + gfs2_grow 用于在线扩展文件系统 + gfs2_jadd 用于在线向文件系统添加日志 + tunegfs2 用于操作、检查和调优文件系统 + gfs2_convert 用于将 gfs 文件系统原地转换为 GFS2 + mkfs.gfs2 用于创建文件系统 + ============ ============================================= diff --git a/Documentation/translations/zh_CN/filesystems/index.rst b/Documentation/translations/zh_CN/filesystems/index.rst index 9f2a8b003778..fcc79ff9fdad 100644 --- a/Documentation/translations/zh_CN/filesystems/index.rst +++ b/Documentation/translations/zh_CN/filesystems/index.rst @@ -15,6 +15,16 @@ Linux Kernel中的文件系统 文件系统(VFS)层以及基于其上的各种文件系统如何工作呈现给大家。当前\ 可以看到下面的内容。 +核心 VFS 文档 +============= + +有关 VFS 层本身以及其算法工作方式的文档,请参阅这些手册。 + +.. toctree:: + :maxdepth: 1 + + dnotify + 文件系统 ======== @@ -26,4 +36,9 @@ Linux Kernel中的文件系统 virtiofs debugfs tmpfs - + ubifs + ubifs-authentication + gfs2 + gfs2-uevents + gfs2-glocks + inotify diff --git a/Documentation/translations/zh_CN/filesystems/inotify.rst b/Documentation/translations/zh_CN/filesystems/inotify.rst new file mode 100644 index 000000000000..b4d740aca946 --- /dev/null +++ b/Documentation/translations/zh_CN/filesystems/inotify.rst @@ -0,0 +1,80 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/filesystems/inotify.rst + +:翻译: + + 王龙杰 Wang Longjie <wang.longjie1@zte.com.cn> + +========================================== +Inotify - 一个强大且简单的文件变更通知系统 +========================================== + + + +文档由 Robert Love <rml@novell.com> 于 2005 年 3 月 15 日开始撰写 + +文档由 Zhang Zhen <zhenzhang.zhang@huawei.com> 于 2015 年 1 月 4 日更新 + + - 删除了已废弃的接口,关于用户接口请参考手册页。 + +(i) 基本原理 + +问: + 不将监控项与被监控对象打开的文件描述符(fd)绑定,这背后的设计决策是什么? + +答: + 监控项会与打开的 inotify 设备相关联,而非与打开的文件相关联。这解决了 dnotify 的主要问题: + 保持文件打开会锁定文件,更糟的是,还会锁定挂载点。因此,dnotify 在带有可移动介质的桌面系统 + 上难以使用,因为介质将无法被卸载。监控文件不应要求文件处于打开状态。 + +问: + 与每个监控项一个文件描述符的方式相比,采用每个实例一个文件描述符的设计决策是出于什么 + 考虑? + +答: + 每个监控项一个文件描述符会很快的消耗掉超出允许数量的文件描述符,其数量会超出实际可管理的范 + 围,也会超出 select() 能高效处理的范围。诚然,root 用户可以提高每个进程的文件描述符限制, + 用户也可以使用 epoll,但同时要求这两者是不合理且多余的。一个监控项所消耗的内存比一个打开的文 + 件要少,因此将这两个数量空间分开是合理的。当前的设计正是用户空间开发者所期望的:用户只需初始 + 化一次 inotify,然后添加 n 个监控项,而这只需要一个文件描述符,无需调整文件描述符限制。初 + 始化 inotify 实例初始化两千次是很荒谬的。如果我们能够简洁地实现用户空间的偏好——而且我们 + 确实可以,idr 层让这类事情变得轻而易举——那么我们就应该这么做。 + + 还有其他合理的理由。如果只有一个文件描述符,那就只需要在该描述符上阻塞,它对应着一个事件队列。 + 这个单一文件描述符会返回所有的监控事件以及任何可能的带外数据。而如果每个文件描述符都是一个独 + 立的监控项, + + - 将无法知晓事件的顺序。文件 foo 和文件 bar 上的事件会触发两个文件描述符上的 poll(), + 但无法判断哪个事件先发生。而用单个队列就可以很容易的提供事件的顺序。这种顺序对现有的应用程 + 序(如 Beagle)至关重要。想象一下,如果“mv a b ; mv b a”这样的事件没有顺序会是什么 + 情况。 + + - 我们将不得不维护 n 个文件描述符和 n 个带有状态的内部队列,而不是仅仅一个。这在 kernel 中 + 会混乱得多。单个线性队列是合理的数据结构。 + + - 用户空间开发者更青睐当前的 API。例如,Beagle 的开发者们就很喜欢它。相信我,我问过他们。 + 这并不奇怪:谁会想通过 select 来管理以及阻塞在 1000 个文件描述符上呢? + + - 无法获取带外数据。 + + - 1024 这个数量仍然太少。 ;-) + + 当要设计一个可扩展到数千个目录的文件变更通知系统时,处理数千个文件描述符似乎并不是合适的接口。 + 这太繁琐了。 + + 此外,创建多个实例、处理多个队列以及相应的多个文件描述符是可行的。不必是每个进程对应一个文件描 + 述符;而是每个队列对应一个文件描述符,一个进程完全可能需要多个队列。 + +问: + 为什么采用系统调用的方式? + +答: + 糟糕的用户空间接口是 dnotify 的第二大问题。信号对于文件通知来说是一种非常糟糕的接口。其实对 + 于其他任何事情,信号也都不是好的接口。从各个角度来看,理想的解决方案是基于文件描述符的,它允许 + 基本的文件 I/O 操作以及 poll/select 操作。获取文件描述符和管理监控项既可以通过设备文件来 + 实现,也可以通过一系列新的系统调用来实现。我们决定采用一系列系统调用,因为这是提供新的内核接口 + 的首选方法。两者之间唯一真正的区别在于,我们是想使用 open(2) 和 ioctl(2),还是想使用几 + 个新的系统调用。系统调用比 ioctl 更有优势。 diff --git a/Documentation/translations/zh_CN/filesystems/ubifs-authentication.rst b/Documentation/translations/zh_CN/filesystems/ubifs-authentication.rst new file mode 100644 index 000000000000..0e7cf7707e26 --- /dev/null +++ b/Documentation/translations/zh_CN/filesystems/ubifs-authentication.rst @@ -0,0 +1,354 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/filesystems/ubifs-authentication.rst + +:翻译: + + 邵明寅 Shao Mingyin <shao.mingyin@zte.com.cn> + +:校译: + + 杨涛 yang tao <yang.tao172@zte.com.cn> + +============= +UBIFS认证支持 +============= + +引言 +==== +UBIFS 利用 fscrypt 框架为文件内容及文件名提供保密性。这能防止攻击者在单一 +时间点读取文件系统内容的攻击行为。典型案例是智能手机丢失时,攻击者若没有文件 +系统解密密钥则无法读取设备上的个人数据。 + +在现阶段,UBIFS 加密尚不能防止攻击者篡改文件系统内容后用户继续使用设备的攻 +击场景。这种情况下,攻击者可任意修改文件系统内容而不被用户察觉。例如修改二 +进制文件使其执行时触发恶意行为 [DMC-CBC-ATTACK]。由于 UBIFS 大部分文件 +系统元数据以明文存储,使得文件替换和内容篡改变得相当容易。 + +其他全盘加密系统(如 dm-crypt)可以覆盖所有文件系统元数据,这类系统虽然能 +增加这种攻击的难度,但特别是当攻击者能多次访问设备时,也有可能实现攻击。对于 +基于 Linux 块 IO 层的 dm-crypt 等文件系统,可通过 dm-integrity 或 +dm-verity 子系统[DM-INTEGRITY, DM-VERITY]在块层实现完整数据认证,这些 +功能也可与 dm-crypt 结合使用[CRYPTSETUP2]。 + +本文描述一种为 UBIFS 实现文件内容认证和完整元数据认证的方法。由于 UBIFS +使用 fscrypt 进行文件内容和文件名加密,认证系统可与 fscrypt 集成以利用密 +钥派生等现有功能。但系统同时也应支持在不启用加密的情况下使用 UBIFS 认证。 + + +MTD, UBI & UBIFS +---------------- +在 Linux 中,MTD(内存技术设备)子系统提供访问裸闪存设备的统一接口。运行于 +MTD 之上的重要子系统是 UBI(无序块映像),它为闪存设备提供卷管理功能,类似 +于块设备的 LVM。此外,UBI 还处理闪存特有的磨损均衡和透明 I/O 错误处理。 +UBI 向上层提供逻辑擦除块(LEB),并透明地映射到闪存的物理擦除块(PEB)。 + +UBIFS 是运行于 UBI 之上的裸闪存文件系统。因此 UBI 处理磨损均衡和部分闪存 +特性,而 UBIFS专注于可扩展性、性能和可恢复性。 + +:: + + +------------+ +*******+ +-----------+ +-----+ + | | * UBIFS * | UBI-BLOCK | | ... | + | JFFS/JFFS2 | +*******+ +-----------+ +-----+ + | | +-----------------------------+ +-----------+ +-----+ + | | | UBI | | MTD-BLOCK | | ... | + +------------+ +-----------------------------+ +-----------+ +-----+ + +------------------------------------------------------------------+ + | MEMORY TECHNOLOGY DEVICES (MTD) | + +------------------------------------------------------------------+ + +-----------------------------+ +--------------------------+ +-----+ + | NAND DRIVERS | | NOR DRIVERS | | ... | + +-----------------------------+ +--------------------------+ +-----+ + + 图1:处理裸闪存的 Linux 内核子系统 + + + +UBIFS 内部维护多个持久化在闪存上的数据结构: + +- *索引*:存储在闪存上的 B+ 树,叶节点包含文件系统数据 +- *日志*:在更新闪存索引前收集文件系统变更的辅助数据结构,可减少闪存磨损 +- *树节点缓存(TNC)*:反映当前文件系统状态的内存 B+ 树,避免频繁读取闪存。 + 本质上是索引的内存表示,但包含额外属性 +- *LEB属性树(LPT)*:用于统计每个 UBI LEB 空闲空间的闪存B+树 + +本节后续将详细讨论UBIFS的闪存数据结构。因为 TNC 不直接持久化到闪存,其在此 +处的重要性较低。更多 UBIFS 细节详见[UBIFS-WP]。 + + +UBIFS 索引与树节点缓存 +~~~~~~~~~~~~~~~~~~~~~~ + +UBIFS 在闪存上的基础实体称为 *节点* ,包含多种类型。如存储文件内容块的数据 +节点 +( ``struct ubifs_data_node`` ),或表示 VFS 索引节点的 inode 节点 +( ``struct ubifs_ino_node`` )。几乎所有节点共享包含节点类型、长度、序列 +号等基础信息的通用头 +( ``ubifs_ch`` )(见内核源码 ``fs/ubifs/ubifs-media.h`` )。LPT条目 +和填充节点(用于填充 LEB +尾部不可用空间)等次要节点类型除外。 + +为避免每次变更重写整个 B+ 树,UBIFS 采用 *wandering tree* 实现:仅重写 +变更节点,旧版本被标记废弃而非立即擦除。因此索引不固定存储于闪存某处,而是在 +闪存上 *wanders* ,在 LEB 被 UBIFS 重用前,闪存上会存在废弃部分。为定位 +最新索引,UBIFS 在 UBI LEB 1 存储称为 *主节点* 的特殊节点,始终指向最新 +UBIFS 索引根节点。为增强可恢复性,主节点还备份到 LEB 2。因此挂载 UBIFS 只 +需读取 LEB 1 和 2 获取当前主节点,进而定位最新闪存索引。 + +TNC 是闪存索引的内存表示,包含未持久化的运行时属性(如脏标记)。TNC 作为回 +写式缓存,所有闪存索引修改都通过 TNC 完成。与其他缓存类似,TNC 无需将完整 +索引全部加载到内存中,需要时从闪存读取部分内容。 *提交* 是更新闪存文件系统 +结构(如索引)的 UBIFS 操作。每次提交时,标记为脏的 TNC 节点被写入闪存以更 +新持久化索引。 + + +日志 +~~~~ + +为避免闪存磨损,索引仅在满足特定条件(如 ``fsync(2)`` )时才持久化(提交)。 +日志用于记录索引提交之间的所有变更(以 inode 节点、数据节点等形式)。挂载时 +从闪存读取日志并重放到 TNC(此时 TNC 按需从闪存索引创建)。 + +UBIFS 保留一组专用于日志的 LEB(称为 *日志区* )。日志区 LEB 数量在文件系 +统创建时配置(使用 ``mkfs.ubifs`` )并存储于超级块节点。日志区仅含两类节 +点: *引用节点* 和 *提交起始节点* 。执行索引提交时写入提交起始节点,每次日 +志更新时写入引用节点。每个引用节点指向构成日志条目的其他节点( inode 节点、 +数据节点等)在闪存上的位置,这些节点称为 *bud* ,描述包含数据的实际文件系 +统变更。 + +日志区以环形缓冲区维护。当日志将满时触发提交操作,同时写入提交起始节点。因此 +挂载时 UBIFS 查找最新提交起始节点,仅重放其后的引用节点。提交起始节点前的引 +用节点将被忽略(因其已属于闪存索引)。 + +写入日志条目时,UBIFS 首先确保有足够空间写入引用节点和该条目的 bud。然后先 +写引用节点,再写描述文件变更的 bud。在日志重放阶段,UBIFS 会记录每个参考节 +点,并检查其引用的 LEB位置以定位 buds。若这些数据损坏或丢失,UBIFS 会尝试 +通过重新读取 LEB 来恢复,但仅针对日志中最后引用的 LEB,因为只有它可能因断 +电而损坏。若恢复失败,UBIFS 将拒绝挂载。对于其他 LEB 的错误,UBIFS 会直接 +终止挂载操作。 + +:: + + | ---- LOG AREA ---- | ---------- MAIN AREA ------------ | + + -----+------+-----+--------+---- ------+-----+-----+--------------- + \ | | | | / / | | | \ + / CS | REF | REF | | \ \ DENT | INO | INO | / + \ | | | | / / | | | \ + ----+------+-----+--------+--- -------+-----+-----+---------------- + | | ^ ^ + | | | | + +------------------------+ | + | | + +-------------------------------+ + + + 图2:包含提交起始节点(CS)和引用节点(REF)的日志区闪存布局,引用节点指向含 + bud 的主区 + + +LEB属性树/表 +~~~~~~~~~~~~ + +LEB 属性树用于存储每个 LEB 的信息,包括 LEB 类型、LEB 上的空闲空间和 +*脏空间* (旧空间,废弃内容) [1]_ 的数量。因为 UBIFS 从不在单个 LEB 混 +合存储索引节点和数据节点,所以 LEB 的类型至关重要,每个 LEB 都有特定用途, +这对空闲空间计算非常有帮助。详见[UBIFS-WP]。 + +LEB 属性树也是 B+ 树,但远小于索引。因为其体积小,所以每次提交时都整块写入, +保存 LPT 是原子操作。 + + +.. [1] 由于LEB只能追加写入不能覆盖,空闲空间(即 LEB 剩余可写空间)与废弃 + 内容(先前写入但未擦除前不能覆盖)存在区别。 + + +UBIFS认证 +========= + +本章介绍UBIFS认证,使UBIFS能验证闪存上元数据和文件内容的真实性与完整性。 + + +威胁模型 +-------- + +UBIFS 认证可检测离线数据篡改。虽然不能防止篡改,但是能让(可信)代码检查闪 +存文件内容和文件系统元数据的完整性与真实性,也能检查文件内容被替换的攻击。 + +UBIFS 认证不防护全闪存内容回滚(攻击者可转储闪存内容并在后期还原)。也不防护 +单个索引提交的部分回滚(攻击者能部分撤销变更)。这是因为 UBIFS 不立即覆盖索 +引树或日志的旧版本,而是标记为废弃,稍后由垃圾回收擦除。攻击者可擦除当前树部 +分内容并还原闪存上尚未擦除的旧版本。因每次提交总会写入索引根节点和主节点的新 +版本而不覆盖旧版本,UBI 的磨损均衡操作(将内容从物理擦除块复制到另一擦除块 +且非原子擦除原块)进一步助长此问题。 + +UBIFS 认证不覆盖认证密钥提供后攻击者在设备执行代码的攻击,需结合安全启动和 +可信启动等措施确保设备仅执行可信代码。 + + +认证 +---- + +为完全信任从闪存读取的数据,所有存储在闪存的 UBIFS 数据结构均需认证: +- 包含文件内容、扩展属性、文件长度等元数据的索引 +- 通过记录文件系统变更来包含文件内容和元数据的日志 +- 存储 UBIFS 用于空闲空间统计的 UBI LEB 元数据的 LPT + + +索引认证 +~~~~~~~~ + +借助 *wandering tree* 概念,UBIFS 仅更新和持久化从叶节点到根节点的变更 +部分。这允许用子节点哈希增强索引树节点。最终索引基本成为 Merkle 树:因索引 +叶节点含实际文件系统数据,其父索引节点的哈希覆盖所有文件内容和元数据。文件 +变更时,UBIFS 索引从叶节点到根节点(含主节点)相应更新,此过程可挂钩以同步 +重新计算各变更节点的哈希。读取文件时,UBIFS 可从叶节点到根节点逐级验证哈希 +确保节点完整性。 + +为确保整个索引真实性,UBIFS 主节点存储基于密钥的哈希(HMAC),覆盖自身内容及 +索引树根节点哈希。如前所述,主节点在索引持久化时(即索引提交时)总会写入闪存。 + +此方法仅修改 UBIFS 索引节点和主节点以包含哈希,其他类型节点保持不变,减少了 +对 UBIFS 用户(如嵌入式设备)宝贵的存储开销。 + +:: + + +---------------+ + | Master Node | + | (hash) | + +---------------+ + | + v + +-------------------+ + | Index Node #1 | + | | + | branch0 branchn | + | (hash) (hash) | + +-------------------+ + | ... | (fanout: 8) + | | + +-------+ +------+ + | | + v v + +-------------------+ +-------------------+ + | Index Node #2 | | Index Node #3 | + | | | | + | branch0 branchn | | branch0 branchn | + | (hash) (hash) | | (hash) (hash) | + +-------------------+ +-------------------+ + | ... | ... | + v v v + +-----------+ +----------+ +-----------+ + | Data Node | | INO Node | | DENT Node | + +-----------+ +----------+ +-----------+ + + + 图3:索引节点哈希与主节点 HMAC 的覆盖范围 + + + +健壮性性和断电安全性的关键在于以原子操作持久化哈希值与文件内容。UBIFS 现有 +的变更节点持久化机制专为此设计,能够确保断电时安全恢复。为索引节点添加哈希值 +不会改变该机制,因为每个哈希值都与其对应节点以原子操作同步持久化。 + + +日志认证 +~~~~~~~~ + +日志也需要认证。因为日志持续写入,必须频繁地添加认证信息以确保断电时未认证数 +据量可控。方法是从提交起始节点开始,对先前引用节点、当前引用节点和 bud 节点 +创建连续哈希链。适时地在bud节点间插入认证节点,这种新节点类型包含哈希链当前 +状态的 HMAC。因此日志可认证至最后一个认证节点。日志尾部无认证节点的部分无法 +认证,在日志重放时跳过。 + +日志认证示意图如下:: + + ,,,,,,,, + ,......,........................................... + ,. CS , hash1.----. hash2.----. + ,. | , . |hmac . |hmac + ,. v , . v . v + ,.REF#0,-> bud -> bud -> bud.-> auth -> bud -> bud.-> auth ... + ,..|...,........................................... + , | , + , | ,,,,,,,,,,,,,,, + . | hash3,----. + , | , |hmac + , v , v + , REF#1 -> bud -> bud,-> auth ... + ,,,|,,,,,,,,,,,,,,,,,, + v + REF#2 -> ... + | + V + ... + +因为哈希值包含引用节点,攻击者无法重排或跳过日志头重放,仅能移除日志尾部的 +bud 节点或引用节点,最大限度将文件系统回退至上次提交。 + +日志区位置存储于主节点。因为主节点通过 HMAC 认证,所以未经检测无法篡改。日 +志区大小在文件系统创建时由 `mkfs.ubifs` 指定并存储于超级块节点。为避免篡 +改此值及其他参数,超级块结构添加 HMAC。超级块节点存储在 LEB 0,仅在功能标 +志等变更时修改,文件变更时不修改。 + + +LPT认证 +~~~~~~~ + +LPT 根节点在闪存上的位置存储于 UBIFS 主节点。因为 LPT 每次提交时都以原子 +操作写入和读取,无需单独认证树节点。通过主节点存储的简单哈希保护完整 LPT +即可。因为主节点自身已认证,通过验证主节点真实性并比对存储的 LTP 哈希与读 +取的闪存 LPT 计算哈希值,即可验证 LPT 真实性。 + + +密钥管理 +-------- + +为了简化实现,UBIFS 认证使用单一密钥计算超级块、主节点、提交起始节点和引用 +节点的 HMAC。创建文件系统(`mkfs.ubifs`) 时需提供此密钥以认证超级块节点。 +挂载文件系统时也需此密钥验证认证节点并为变更生成新 HMAC。 + +UBIFS 认证旨在与 UBIFS 加密(fscrypt)协同工作以提供保密性和真实性。因为 +UBIFS 加密采用基于目录的差异化加密策略,可能存在多个 fscrypt 主密钥甚至未 +加密目录。而 UBIFS 认证采用全有或全无方式,要么认证整个文件系统要么完全不 +认证。基于此特性,且为确保认证机制可独立于加密功能使用,UBIFS 认证不与 +fscrypt 共享主密钥,而是维护独立的认证专用密钥。 + +提供认证密钥的API尚未定义,但可通过类似 fscrypt 的用户空间密钥环提供。需注 +意当前 fscrypt 方案存在缺陷,用户空间 API 终将变更[FSCRYPT-POLICY2]。 + +用户仍可通过用户空间提供单一口令或密钥覆盖 UBIFS 认证与加密。相应用户空间工 +具可解决此问题:除派生的 fscrypt 加密主密钥外,额外派生认证密钥。 + +为检查挂载时密钥可用性,UBIFS 超级块节点将额外存储认证密钥的哈希。此方法类 +似 fscrypt 加密策略 v2 提出的方法[FSCRYPT-POLICY2]。 + + +未来扩展 +======== + +特定场景下,若供应商需要向客户提供认证文件系统镜像,应该能在不共享 UBIFS 认 +证密钥的前提下实现。方法是在每个 HMAC 外额外存储数字签名,供应商随文件系统 +镜像分发公钥。若该文件系统后续需要修改,若后续需修改该文件系统,UBIFS 可在 +首次挂载时将全部数字签名替换为 HMAC,其处理逻辑与 IMA/EVM 子系统应对此类情 +况的方式类似。此时,HMAC 密钥需按常规方式预先提供。 + + +参考 +==== + +[CRYPTSETUP2] https://www.saout.de/pipermail/dm-crypt/2017-November/005745.html + +[DMC-CBC-ATTACK] https://www.jakoblell.com/blog/2013/12/22/practical-malleability-attack-against-cbc-en +crypted-luks-partitions/ + +[DM-INTEGRITY] https://www.kernel.org/doc/Documentation/device-mapper/dm-integrity.rst + +[DM-VERITY] https://www.kernel.org/doc/Documentation/device-mapper/verity.rst + +[FSCRYPT-POLICY2] https://www.spinics.net/lists/linux-ext4/msg58710.html + +[UBIFS-WP] http://www.linux-mtd.infradead.org/doc/ubifs_whitepaper.pdf diff --git a/Documentation/translations/zh_CN/filesystems/ubifs.rst b/Documentation/translations/zh_CN/filesystems/ubifs.rst new file mode 100644 index 000000000000..d1873fc6a67c --- /dev/null +++ b/Documentation/translations/zh_CN/filesystems/ubifs.rst @@ -0,0 +1,114 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/filesystems/ubifs.rst + +:翻译: + + 邵明寅 Shao Mingyin <shao.mingyin@zte.com.cn> + +:校译: + + 杨涛 yang tao <yang.tao172@zte.com.cn> + +============ +UBI 文件系统 +============ + +简介 +==== + +UBIFS 文件系统全称为 UBI 文件系统(UBI File System)。UBI 代表无序块镜 +像(Unsorted Block Images)。UBIFS 是一种闪存文件系统,这意味着它专为闪 +存设备设计。需要理解的是,UBIFS与 Linux 中任何传统文件系统(如 Ext2、 +XFS、JFS 等)完全不同。UBIFS 代表一类特殊的文件系统,它们工作在 MTD 设备 +而非块设备上。该类别的另一个 Linux 文件系统是 JFFS2。 + +为更清晰说明,以下是 MTD 设备与块设备的简要比较: + +1. MTD 设备代表闪存设备,由较大尺寸的擦除块组成,通常约 128KiB。块设备由 + 小块组成,通常 512 字节。 +2. MTD 设备支持 3 种主要操作:在擦除块内偏移位置读取、在擦除块内偏移位置写 + 入、以及擦除整个擦除块。块设备支持 2 种主要操作:读取整个块和写入整个块。 +3. 整个擦除块必须先擦除才能重写内容。块可直接重写。 +4. 擦除块在经历一定次数的擦写周期后会磨损,通常 SLC NAND 和 NOR 闪存为 + 100K-1G 次,MLC NAND 闪存为 1K-10K 次。块设备不具备磨损特性。 +5. 擦除块可能损坏(仅限 NAND 闪存),软件需处理此问题。硬盘上的块通常不会损 + 坏,因为硬件有坏块替换机制(至少现代 LBA 硬盘如此)。 + +这充分说明了 UBIFS 与传统文件系统的本质差异。 + +UBIFS 工作在 UBI 层之上。UBI 是一个独立的软件层(位于 drivers/mtd/ubi), +本质上是卷管理和磨损均衡层。它提供称为 UBI 卷的高级抽象,比 MTD 设备更上层。 +UBI 设备的编程模型与 MTD 设备非常相似,仍由大容量擦除块组成,支持读/写/擦 +除操作,但 UBI 设备消除了磨损和坏块限制(上述列表的第 4 和第 5 项)。 + +某种意义上,UBIFS 是 JFFS2 文件系统的下一代产品,但它与 JFFS2 差异巨大且 +不兼容。主要区别如下: + +* JFFS2 工作在 MTD 设备之上,UBIFS 依赖于 UBI 并工作在 UBI 卷之上。 +* JFFS2 没有介质索引,需在挂载时构建索引,这要求全介质扫描。UBIFS 在闪存 + 介质上维护文件系统索引信息,无需全介质扫描,因此挂载速度远快于 JFFS2。 +* JFFS2 是直写(write-through)文件系统,而 UBIFS 支持回写 + (write-back),这使得 UBIFS 写入速度快得多。 + +与 JFFS2 类似,UBIFS 支持实时压缩,可将大量数据存入闪存。 + +与 JFFS2 类似,UBIFS 能容忍异常重启和断电。它不需要类似 fsck.ext2 的工 +具。UBIFS 会自动重放日志并从崩溃中恢复,确保闪存数据结构的一致性。 + +UBIFS 具有对数级扩展性(其使用的数据结构多为树形),因此挂载时间和内存消耗不 +像 JFFS2 那样线性依赖于闪存容量。这是因为 UBIFS 在闪存介质上维护文件系统 +索引。但 UBIFS 依赖于线性扩展的 UBI 层,因此整体 UBI/UBIFS 栈仍是线性扩 +展。尽管如此,UBIFS/UBI 的扩展性仍显著优于 JFFS2。 + +UBIFS 开发者认为,未来可开发同样具备对数级扩展性的 UBI2。UBI2 将支持与 +UBI 相同的 API,但二进制不兼容。因此 UBIFS 无需修改即可使用 UBI2。 + +挂载选项 +======== + +(*) 表示默认选项。 + +==================== ======================================================= +bulk_read 批量读取以利用闪存介质的顺序读取加速特性 +no_bulk_read (*) 禁用批量读取 +no_chk_data_crc (*) 跳过数据节点的 CRC 校验以提高读取性能。 仅在闪存 + 介质高度可靠时使用此选项。 此选项可能导致文件内容损坏无法被 + 察觉。 +chk_data_crc 强制校验数据节点的 CRC +compr=none 覆盖默认压缩器,设置为"none" +compr=lzo 覆盖默认压缩器,设置为"LZO" +compr=zlib 覆盖默认压缩器,设置为"zlib" +auth_key= 指定用于文件系统身份验证的密钥。 + 使用此选项将强制启用身份验证。 + 传入的密钥必须存在于内核密钥环中, 且类型必须是'logon' +auth_hash_name= 用于身份验证的哈希算法。同时用于哈希计算和 HMAC + 生成。典型值包括"sha256"或"sha512" +==================== ======================================================= + +快速使用指南 +============ + +挂载的 UBI 卷通过 "ubiX_Y" 或 "ubiX:NAME" 语法指定,其中 "X" 是 UBI +设备编号,"Y" 是 UBI 卷编号,"NAME" 是 UBI 卷名称。 + +将 UBI 设备 0 的卷 0 挂载到 /mnt/ubifs:: + + $ mount -t ubifs ubi0_0 /mnt/ubifs + +将 UBI 设备 0 的 "rootfs" 卷挂载到 /mnt/ubifs("rootfs" 是卷名):: + + $ mount -t ubifs ubi0:rootfs /mnt/ubifs + +以下是内核启动参数的示例,用于将 mtd0 附加到 UBI 并挂载 "rootfs" 卷: +ubi.mtd=0 root=ubi0:rootfs rootfstype=ubifs + +参考资料 +======== + +UBIFS 文档及常见问题解答/操作指南请访问 MTD 官网: + +- http://www.linux-mtd.infradead.org/doc/ubifs.html +- http://www.linux-mtd.infradead.org/faq/ubifs.html diff --git a/Documentation/translations/zh_CN/how-to.rst b/Documentation/translations/zh_CN/how-to.rst index ddd99c0f9b4d..7ae5d8765888 100644 --- a/Documentation/translations/zh_CN/how-to.rst +++ b/Documentation/translations/zh_CN/how-to.rst @@ -64,7 +64,7 @@ Linux 发行版和简单地使用 Linux 命令行,那么可以迅速开始了 :: cd linux - ./scripts/sphinx-pre-install + ./tools/docs/sphinx-pre-install 以 Fedora 为例,它的输出是这样的:: @@ -437,7 +437,7 @@ git email 默认会抄送给您一份,所以您可以切换为审阅者的角 对于首次参与 Linux 内核中文文档翻译的新手,建议您在 linux 目录中运行以下命令: :: - ./script/checktransupdate.py -l zh_CN`` + tools/docs/checktransupdate.py -l zh_CN`` 该命令会列出需要翻译或更新的英文文档,结果同时保存在 checktransupdate.log 中。 diff --git a/Documentation/translations/zh_CN/kbuild/kbuild.rst b/Documentation/translations/zh_CN/kbuild/kbuild.rst index e5e2aebe1ebc..57f5cf5b2cdd 100644 --- a/Documentation/translations/zh_CN/kbuild/kbuild.rst +++ b/Documentation/translations/zh_CN/kbuild/kbuild.rst @@ -93,6 +93,16 @@ HOSTRUSTFLAGS ------------- 在构建主机程序时传递给 $(HOSTRUSTC) 的额外标志。 +PROCMACROLDFLAGS +---------------- +用于链接 Rust 过程宏的标志。由于过程宏是由 rustc 在构建时加载的, +因此必须以与当前使用的 rustc 工具链兼容的方式进行链接。 + +例如,当 rustc 使用的 C 库与用户希望用于主机程序的 C 库不同时, +此设置会非常有用。 + +如果未设置,则默认使用链接主机程序时传递的标志。 + HOSTLDFLAGS ----------- 链接主机程序时传递的额外选项。 @@ -135,12 +145,18 @@ KBUILD_OUTPUT 指定内核构建的输出目录。 在单独的构建目录中为预构建内核构建外部模块时,这个变量也可以指向内核输出目录。请注意, -这并不指定外部模块本身的输出目录。 +这并不指定外部模块本身的输出目录(使用 KBUILD_EXTMOD_OUTPUT 来达到这个目的)。 输出目录也可以使用 "O=..." 指定。 设置 "O=..." 优先于 KBUILD_OUTPUT。 +KBUILD_EXTMOD_OUTPUT +-------------------- +指定外部模块的输出目录 + +设置 "MO=..." 优先于 KBUILD_EXTMOD_OUTPUT. + KBUILD_EXTRA_WARN ----------------- 指定额外的构建检查。也可以通过在命令行传递 "W=..." 来设置相同的值。 @@ -290,8 +306,13 @@ IGNORE_DIRS KBUILD_BUILD_TIMESTAMP ---------------------- 将该环境变量设置为日期字符串,可以覆盖在 UTS_VERSION 定义中使用的时间戳 -(运行内核时的 uname -v)。该值必须是一个可以传递给 date -d 的字符串。默认值是 -内核构建某个时刻的 date 命令输出。 +(运行内核时的 uname -v) 。该值必须是一个可以传递给 date -d 的字符串。例如:: + + $ KBUILD_BUILD_TIMESTAMP="Mon Oct 13 00:00:00 UTC 2025" make + +默认值是内核构建某个时刻的 date 命令输出。如果提供该时戳,它还用于任何 initramfs 归 +档文件中的 mtime 字段。 Initramfs mtimes 是 32 位的,因此早于 Unix 纪元 1970 年,或 +晚于协调世界时 (UTC) 2106 年 2 月 7 日 6 时 28 分 15 秒的日期是无效的。 KBUILD_BUILD_USER, KBUILD_BUILD_HOST ------------------------------------ diff --git a/Documentation/translations/zh_CN/mm/active_mm.rst b/Documentation/translations/zh_CN/mm/active_mm.rst index b3352668c4c8..9496a0bb7d07 100644 --- a/Documentation/translations/zh_CN/mm/active_mm.rst +++ b/Documentation/translations/zh_CN/mm/active_mm.rst @@ -87,4 +87,4 @@ Active MM 最丑陋的之一--不像其他架构的MM和寄存器状态是分开的,alpha的PALcode将两者 连接起来,你需要同时切换两者)。 - (文档来源 http://marc.info/?l=linux-kernel&m=93337278602211&w=2) + (文档来源 https://lore.kernel.org/lkml/Pine.LNX.4.10.9907301410280.752-100000@penguin.transmeta.com/) diff --git a/Documentation/translations/zh_CN/networking/generic-hdlc.rst b/Documentation/translations/zh_CN/networking/generic-hdlc.rst new file mode 100644 index 000000000000..9e493dc9721e --- /dev/null +++ b/Documentation/translations/zh_CN/networking/generic-hdlc.rst @@ -0,0 +1,176 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/networking/generic-hdlc.rst + +:翻译: + + 孙渔喜 Sun yuxi <sun.yuxi@zte.com.cn> + +========== +通用HDLC层 +========== + +Krzysztof Halasa <khc@pm.waw.pl> + + +通用HDLC层当前支持以下协议: + +1. 帧中继(支持ANSI、CCITT、Cisco及无LMI模式) + + - 常规(路由)接口和以太网桥接(以太网设备仿真)接口 + 可共享同一条PVC。 + - 支持ARP(内核暂不支持InARP,但可通过实验性用户空间守护程序实现, + 下载地址:http://www.kernel.org/pub/linux/utils/net/hdlc/)。 + +2. 原始HDLC —— 支持IP(IPv4)接口或以太网设备仿真 +3. Cisco HDLC +4. PPP +5. X.25(使用X.25协议栈) + +通用HDLC仅作为协议驱动 - 必须配合具体硬件的底层驱动 +才能运行。 + +以太网设备仿真(使用HDLC或帧中继PVC)兼容IEEE 802.1Q(VLAN)和 +802.1D(以太网桥接)。 + + +请确保已加载 hdlc.o 和硬件驱动程序。系统将为每个WAN端口创建一个 +"hdlc"网络设备(如hdlc0等)。您需要使用"sethdlc"工具,可从以下 +地址获取: + + http://www.kernel.org/pub/linux/utils/net/hdlc/ + +编译 sethdlc.c 工具:: + + gcc -O2 -Wall -o sethdlc sethdlc.c + +请确保使用与您内核版本匹配的 sethdlc 工具。 + +使用 sethdlc 工具设置物理接口、时钟频率、HDLC 模式, +若使用帧中继还需添加所需的 PVC。 +通常您需要执行类似以下命令:: + + sethdlc hdlc0 clock int rate 128000 + sethdlc hdlc0 cisco interval 10 timeout 25 + +或:: + + sethdlc hdlc0 rs232 clock ext + sethdlc hdlc0 fr lmi ansi + sethdlc hdlc0 create 99 + ifconfig hdlc0 up + ifconfig pvc0 localIP pointopoint remoteIP + +在帧中继模式下,请先启用主hdlc设备(不分配IP地址),再 +使用pvc设备。 + + +接口设置选项: + +* v35 | rs232 | x21 | t1 | e1 + - 当网卡支持软件可选接口时,可为指定端口设置物理接口 + loopback + - 启用硬件环回(仅用于测试) +* clock ext + - RX与TX时钟均使用外部时钟源 +* clock int + - RX与TX时钟均使用内部时钟源 +* clock txint + - RX时钟使用外部时钟源,TX时钟使用内部时钟源 +* clock txfromrx + - RX时钟使用外部时钟源,TX时钟从RX时钟派生 +* rate + - 设置时钟速率(仅适用于"int"或"txint"时钟模式) + + +设置协议选项: + +* hdlc - 设置原始HDLC模式(仅支持IP协议) + + nrz / nrzi / fm-mark / fm-space / manchester - 传输编码选项 + + no-parity / crc16 / crc16-pr0 (预设零值的CRC16) / crc32-itu + + crc16-itu (使用ITU-T多项式的CRC16) / crc16-itu-pr0 - 校验方式选项 + +* hdlc-eth - 使用HDLC进行以太网设备仿真. 校验和编码方式同上 + as above. + +* cisco - 设置Cisco HDLC模式(支持IP、IPv6和IPX协议) + + interval - 保活数据包发送间隔(秒) + + timeout - 未收到保活数据包的超时时间(秒),超过此时长将判定 + 链路断开 + +* ppp - 设置同步PPP模式 + +* x25 - 设置X.25模式 + +* fr - 帧中继模式 + + lmi ansi / ccitt / cisco / none - LMI(链路管理)类型 + + dce - 将帧中继设置为DCE(网络侧)LMI模式(默认为DTE用户侧)。 + + 此设置与时钟无关! + + - t391 - 链路完整性验证轮询定时器(秒)- 用户侧 + - t392 - 轮询验证定时器(秒)- 网络侧 + - n391 - 全状态轮询计数器 - 用户侧 + - n392 - 错误阈值 - 用户侧和网络侧共用 + - n393 - 监控事件计数 - 用户侧和网络侧共用 + +帧中继专用命令: + +* create n | delete n - 添加/删除DLCI编号为n的PVC接口。 + 新创建的接口将命名为pvc0、pvc1等。 + +* create ether n | delete ether n - 添加/删除用于以太网 + 桥接帧的设备设备将命名为pvceth0、pvceth1等。 + + + + +板卡特定问题 +------------ + +n2.o 和 c101.o 驱动模块需要参数才能工作:: + + insmod n2 hw=io,irq,ram,ports[:io,irq,...] + +示例:: + + insmod n2 hw=0x300,10,0xD0000,01 + +或:: + + insmod c101 hw=irq,ram[:irq,...] + +示例:: + + insmod c101 hw=9,0xdc000 + +若直接编译进内核,这些驱动需要通过内核(命令行)参数配置:: + + n2.hw=io,irq,ram,ports:... + +或:: + + c101.hw=irq,ram:... + + + +若您的N2、C101或PLX200SYN板卡出现问题,可通过"private" +命令查看端口数据包描述符环(显示在内核日志中) + + sethdlc hdlc0 private + +硬件驱动需使用#define DEBUG_RINGS编译选项构建。 +在提交错误报告时附上这些信息将很有帮助。如在使用过程中遇 +到任何问题,请随时告知。 + +获取补丁和其他信息,请访问: +<http://www.kernel.org/pub/linux/utils/net/hdlc/>.
\ No newline at end of file diff --git a/Documentation/translations/zh_CN/networking/index.rst b/Documentation/translations/zh_CN/networking/index.rst index bb0edcffd144..c276c0993c51 100644 --- a/Documentation/translations/zh_CN/networking/index.rst +++ b/Documentation/translations/zh_CN/networking/index.rst @@ -27,6 +27,9 @@ xfrm_proc netmem alias + mptcp-sysctl + generic-hdlc + timestamping Todolist: @@ -76,7 +79,6 @@ Todolist: * eql * fib_trie * filter -* generic-hdlc * generic_netlink * netlink_spec/index * gen_stats @@ -96,7 +98,6 @@ Todolist: * mctp * mpls-sysctl * mptcp -* mptcp-sysctl * multiqueue * multi-pf-netdev * net_cachelines/index @@ -126,7 +127,6 @@ Todolist: * sctp * secid * seg6-sysctl -* skbuff * smc-sysctl * sriov * statistics @@ -138,7 +138,6 @@ Todolist: * tcp_ao * tcp-thin * team -* timestamping * tipc * tproxy * tuntap diff --git a/Documentation/translations/zh_CN/networking/mptcp-sysctl.rst b/Documentation/translations/zh_CN/networking/mptcp-sysctl.rst new file mode 100644 index 000000000000..0b1b9ed7c647 --- /dev/null +++ b/Documentation/translations/zh_CN/networking/mptcp-sysctl.rst @@ -0,0 +1,139 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/networking/mptcp-sysctl.rst + +:翻译: + + 孙渔喜 Sun yuxi <sun.yuxi@zte.com.cn> + +================ +MPTCP Sysfs 变量 +================ + +/proc/sys/net/mptcp/* Variables +=============================== + +add_addr_timeout - INTEGER (秒) + 设置ADD_ADDR控制消息的重传超时时间。当MPTCP对端未确认 + 先前的ADD_ADDR消息时,将在该超时时间后重新发送。 + + 默认值与TCP_RTO_MAX相同。此为每个命名空间的sysctl参数。 + + 默认值:120 + +allow_join_initial_addr_port - BOOLEAN + 控制是否允许对端向初始子流使用的IP地址和端口号发送加入 + 请求(1表示允许)。此参数会设置连接时发送给对端的标志位, + 并决定是否接受此类加入请求。 + + 通过ADD_ADDR通告的地址不受此参数影响。 + + 此为每个命名空间的sysctl参数。 + + 默认值:1 + +available_path_managers - STRING + 显示已注册的可用路径管理器选项。可能有更多路径管理器可用 + 但尚未加载。 + +available_schedulers - STRING + 显示已注册的可用调度器选项。可能有更多数据包调度器可用 + 但尚未加载。 + +blackhole_timeout - INTEGER (秒) + 当发生MPTCP防火墙黑洞问题时,初始禁用活跃MPTCP套接字上MPTCP + 功能的时间(秒)。如果在重新启用MPTCP后立即检测到更多黑洞问题, + 此时间段将呈指数增长;当黑洞问题消失时,将重置为初始值。 + + 设置为0可禁用黑洞检测功能。此为每个命名空间的sysctl参数。 + + 默认值:3600 + +checksum_enabled - BOOLEAN + 控制是否启用DSS校验和功能。 + + 当值为非零时可启用DSS校验和。此为每个命名空间的sysctl参数。 + + 默认值:0 + +close_timeout - INTEGER (seconds) + 设置"先断后连"超时时间:在未调用close或shutdown系统调用时, + MPTCP套接字将在最后一个子流移除后保持当前状态达到该时长,才 + 会转为TCP_CLOSE状态。 + + 默认值与TCP_TIMEWAIT_LEN相同。此为每个命名空间的sysctl参数。 + + 默认值:60 + +enabled - BOOLEAN + 控制是否允许创建MPTCP套接字。 + + 当值为1时允许创建MPTCP套接字。此为每个命名空间的sysctl参数。 + + 默认值:1(启用) + +path_manager - STRING + 设置用于每个新MPTCP套接字的默认路径管理器名称。内核路径管理将 + 根据通过MPTCP netlink API配置的每个命名空间值来控制子流连接 + 和地址通告。用户空间路径管理将每个MPTCP连接的子流连接决策和地 + 址通告交由特权用户空间程序控制,代价是需要更多netlink流量来 + 传播所有相关事件和命令。 + + 此为每个命名空间的sysctl参数。 + + * "kernel" - 内核路径管理器 + * "userspace" - 用户空间路径管理器 + + 默认值:"kernel" + +pm_type - INTEGER + 设置用于每个新MPTCP套接字的默认路径管理器类型。内核路径管理将 + 根据通过MPTCP netlink API配置的每个命名空间值来控制子流连接 + 和地址通告。用户空间路径管理将每个MPTCP连接的子流连接决策和地 + 址通告交由特权用户空间程序控制,代价是需要更多netlink流量来 + 传播所有相关事件和命令。 + + 此为每个命名空间的sysctl参数。 + + 自v6.15起已弃用,请改用path_manager参数。 + + * 0 - 内核路径管理器 + * 1 - 用户空间路径管理器 + + 默认值:0 + +scheduler - STRING + 选择所需的调度器类型。 + + 支持选择不同的数据包调度器。此为每个命名空间的sysctl参数。 + + 默认值:"default" + +stale_loss_cnt - INTEGER + 用于判定子流失效(stale)的MPTCP层重传间隔次数阈值。当指定 + 子流在连续多个重传间隔内既无数据传输又有待处理数据时,将被标 + 记为失效状态。失效子流将被数据包调度器忽略。 + 设置较低的stale_loss_cnt值可实现快速主备切换,较高的值则能 + 最大化边缘场景(如高误码率链路或对端暂停数据处理等异常情况) + 的链路利用率。 + + 此为每个命名空间的sysctl参数。 + + 默认值:4 + +syn_retrans_before_tcp_fallback - INTEGER + 在回退到 TCP(即丢弃 MPTCP 选项)之前,SYN + MP_CAPABLE + 报文的重传次数。换句话说,如果所有报文在传输过程中都被丢弃, + 那么将会: + + * 首次SYN携带MPTCP支持选项 + * 按本参数值重传携带MPTCP选项的SYN包 + * 后续重传将不再携带MPTCP支持选项 + + 0 表示首次重传即丢弃MPTCP选项。 + >=128 表示所有SYN重传均保留MPTCP选项设置过低的值可能增加 + MPTCP黑洞误判几率。此为每个命名空间的sysctl参数。 + + 默认值:2 diff --git a/Documentation/translations/zh_CN/networking/timestamping.rst b/Documentation/translations/zh_CN/networking/timestamping.rst new file mode 100644 index 000000000000..4593f53ad09a --- /dev/null +++ b/Documentation/translations/zh_CN/networking/timestamping.rst @@ -0,0 +1,674 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/networking/timestamping.rst + +:翻译: + + 王亚鑫 Wang Yaxin <wang.yaxin@zte.com.cn> + +====== +时间戳 +====== + + +1. 控制接口 +=========== + +接收网络数据包时间戳的接口包括: + +SO_TIMESTAMP + 为每个传入数据包生成(不一定是单调的)系统时间时间戳。通过 recvmsg() + 在控制消息中以微秒分辨率报告时间戳。 + SO_TIMESTAMP 根据架构类型和 libc 的 lib 中的 time_t 表示方式定义为 + SO_TIMESTAMP_NEW 或 SO_TIMESTAMP_OLD。 + SO_TIMESTAMP_OLD 和 SO_TIMESTAMP_NEW 的控制消息格式分别为 + struct __kernel_old_timeval 和 struct __kernel_sock_timeval。 + +SO_TIMESTAMPNS + 与 SO_TIMESTAMP 相同的时间戳机制,但以 struct timespec 格式报告时间戳, + 纳秒分辨率。 + SO_TIMESTAMPNS 根据架构类型和 libc 的 time_t 表示方式定义为 + SO_TIMESTAMPNS_NEW 或 SO_TIMESTAMPNS_OLD。 + 控制消息格式对于 SO_TIMESTAMPNS_OLD 为 struct timespec, + 对于 SO_TIMESTAMPNS_NEW 为 struct __kernel_timespec。 + +IP_MULTICAST_LOOP + SO_TIMESTAMP[NS] + 仅用于多播:通过读取回环数据包接收时间戳,获得近似的传输时间戳。 + +SO_TIMESTAMPING + 在接收、传输或两者时生成时间戳。支持多个时间戳源,包括硬件。 + 支持为流套接字生成时间戳。 + + +1.1 SO_TIMESTAMP(也包括 SO_TIMESTAMP_OLD 和 SO_TIMESTAMP_NEW) +--------------------------------------------------------------- + +此套接字选项在接收路径上启用数据报的时间戳。由于目标套接字(如果有) +在网络栈早期未知,因此必须为所有数据包启用此功能。所有早期接收的时间 +戳选项也是如此。 + +有关接口详细信息,请参阅 `man 7 socket`。 + +始终使用 SO_TIMESTAMP_NEW 时间戳以获得 struct __kernel_sock_timeval +格式的时间戳。 + +如果时间在 2038 年后,SO_TIMESTAMP_OLD 在 32 位机器上将返回错误的时间戳。 + +1.2 SO_TIMESTAMPNS(也包括 SO_TIMESTAMPNS_OLD 和 SO_TIMESTAMPNS_NEW) +--------------------------------------------------------------------- + +此选项与 SO_TIMESTAMP 相同,但返回数据类型有所不同。其 struct timespec +能达到比 SO_TIMESTAMP 的 timeval(毫秒)更高的分辨率(纳秒)时间戳。 + +始终使用 SO_TIMESTAMPNS_NEW 时间戳获得 struct __kernel_timespec 格式 +的时间戳。 + +如果时间在 2038 年后,SO_TIMESTAMPNS_OLD 在 32 位机器上将返回错误的时间戳。 + +1.3 SO_TIMESTAMPING(也包括 SO_TIMESTAMPING_OLD 和 SO_TIMESTAMPING_NEW) +------------------------------------------------------------------------ + +支持多种类型的时间戳请求。因此,此套接字选项接受标志位图,而不是布尔值。在:: + + err = setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &val, sizeof(val)); + +val 是一个整数,设置了以下任何位。设置其他位将返回 EINVAL 且不更改当前状态。 + +这个套接字选项配置以下几个方面的时间戳生成: +为单个 sk_buff 结构体生成时间戳(1.3.1); +将时间戳报告到套接字的错误队列(1.3.2); +配置相关选项(1.3.3); +也可以通过 cmsg 为单个 sendmsg 调用启用时间戳生成(1.3.4)。 + +1.3.1 时间戳生成 +^^^^^^^^^^^^^^^^ + +某些位是向协议栈请求尝试生成时间戳。它们的任何组合都是有效的。对这些位的更改适 +用于新创建的数据包,而不是已经在协议栈中的数据包。因此,可以通过在两个 setsockopt +调用之间嵌入 send() 调用来选择性地为数据包子集请求时间戳(例如,用于采样), +一个用于启用时间戳生成,一个用于禁用它。时间戳也可能由于特定套接字请求之外的原 +因而生成,例如在当系统范围内启用接收时间戳时,如前所述。 + +SOF_TIMESTAMPING_RX_HARDWARE: + 请求由网络适配器生成的接收时间戳。 + +SOF_TIMESTAMPING_RX_SOFTWARE: + 当数据进入内核时请求接收时间戳。这些时间戳在设备驱动程序将数据包交给内核接收 + 协议栈后生成。 + +SOF_TIMESTAMPING_TX_HARDWARE: + 请求由网络适配器生成的传输时间戳。此标志可以通过套接字选项和控制消息启用。 + +SOF_TIMESTAMPING_TX_SOFTWARE: + 当数据离开内核时请求传输(TX)时间戳。这些时间戳由设备驱动程序生成,并且尽 + 可能贴近网络接口发送点,但始终在内核将数据包传递给网络接口之前生成。因此, + 它们需要驱动程序支持,且可能并非所有设备都可用。此标志可通过套接字选项和 + 控制消息两种方式启用。 + +SOF_TIMESTAMPING_TX_SCHED: + 在进入数据包调度器之前请求传输时间戳。内核传输延迟(如果很长)通常由排队 + 延迟主导。此时间戳与在 SOF_TIMESTAMPING_TX_SOFTWARE 处获取的时间戳之 + 间的差异将暴露此延迟,并且与协议处理无关。协议处理中产生的延迟(如果有) + 可以通过从 send() 之前立即获取的用户空间时间戳中减去此时间戳来计算。在 + 具有虚拟设备的机器上,传输的数据包通过多个设备和多个数据包调度器,在每层 + 生成时间戳。这允许对排队延迟进行细粒度测量。此标志可以通过套接字选项和控 + 制消息启用。 + +SOF_TIMESTAMPING_TX_ACK: + 请求在发送缓冲区中的所有数据都已得到确认时生成传输(TX)时间戳。此选项 + 仅适用于可靠协议,目前仅在TCP协议中实现。对于该协议,它可能会过度报告 + 测量结果,因为时间戳是在send()调用时缓冲区中的所有数据(包括该缓冲区) + 都被确认时生成的,即累积确认。该机制会忽略选择确认(SACK)和前向确认 + (FACK)。此标志可通过套接字选项和控制消息两种方式启用。 + +SOF_TIMESTAMPING_TX_COMPLETION: + 在数据包传输完成时请求传输时间戳。完成时间戳由内核在从硬件接收数据包完成 + 报告时生成。硬件可能一次报告多个数据包,完成时间戳反映报告的时序而不是实 + 际传输时间。此标志可以通过套接字选项和控制消息启用。 + + +1.3.2 时间戳报告 +^^^^^^^^^^^^^^^^ + +其他三个位控制将在生成的控制消息中报告哪些时间戳。对这些位的更改在协议栈中 +的时间戳报告位置立即生效。仅当数据包设置了相关的时间戳生成请求时,才会报告 +其时间戳。 + +SOF_TIMESTAMPING_SOFTWARE: + 在可用时报告任何软件时间戳。 + +SOF_TIMESTAMPING_SYS_HARDWARE: + 此选项已被弃用和忽略。 + +SOF_TIMESTAMPING_RAW_HARDWARE: + 在可用时报告由 SOF_TIMESTAMPING_TX_HARDWARE 或 SOF_TIMESTAMPING_RX_HARDWARE + 生成的硬件时间戳。 + + +1.3.3 时间戳选项 +^^^^^^^^^^^^^^^^ + +接口支持以下选项 + +SOF_TIMESTAMPING_OPT_ID: + 每个数据包生成一个唯一标识符。一个进程可以同时存在多个未完成的时间戳请求。 + 数据包在传输路径中可能会发生重排序(例如在数据包调度器中)。在这种情况下, + 时间戳会以与原始send()调用不同的顺序排队到错误队列中。如此一来,仅根据 + 时间戳顺序或 payload(有效载荷)检查,并不总能将时间戳与原始send()调用 + 唯一匹配。 + + 此选项在 send() 时将每个数据包与唯一标识符关联,并与时间戳一起返回。 + 标识符源自每个套接字的 u32 计数器(会回绕)。对于数据报套接字,计数器 + 随每个发送的数据包递增。对于流套接字,它随每个字节递增。对于流套接字, + 还要设置 SOF_TIMESTAMPING_OPT_ID_TCP,请参阅下面的部分。 + + 计数器从零开始。在首次启用套接字选项时初始化。在禁用后再重新启用选项时 + 重置。重置计数器不会更改系统中现有数据包的标识符。 + + 此选项仅针对传输时间戳实现。在这种情况下,时间戳总是与sock_extended_err + 结构体一起回环。该选项会修改ee_data字段,以传递一个在该套接字所有同时 + 存在的未完成时间戳请求中唯一的 ID。 + + 进程可以通过控制消息SCM_TS_OPT_ID(TCP 套接字不支持)传递特定 ID, + 从而选择性地覆盖默认生成的 ID,示例如下:: + + struct msghdr *msg; + ... + cmsg = CMSG_FIRSTHDR(msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_TS_OPT_ID; + cmsg->cmsg_len = CMSG_LEN(sizeof(__u32)); + *((__u32 *) CMSG_DATA(cmsg)) = opt_id; + err = sendmsg(fd, msg, 0); + + +SOF_TIMESTAMPING_OPT_ID_TCP: + 与 SOF_TIMESTAMPING_OPT_ID 一起传递给新的 TCP 时间戳应用程序。 + SOF_TIMESTAMPING_OPT_ID 定义了流套接字计数器的增量,但其起始点 + 并不完全显而易见。此选项修复了这一点。 + + 对于流套接字,如果设置了 SOF_TIMESTAMPING_OPT_ID,则此选项应始终 + 设置。在数据报套接字上,选项没有效果。 + + 一个合理的期望是系统调用后计数器重置为零,因此后续写入 N 字节将生成 + 计数器为 N-1 的时间戳。SOF_TIMESTAMPING_OPT_ID_TCP 在所有条件下 + 都实现了此行为。 + + SOF_TIMESTAMPING_OPT_ID 不带修饰符时通常报告相同,特别是在套接字选项 + 在无数据传输时设置时。如果正在传输数据,它可能与输出队列的长度(SIOCOUTQ) + 偏差。 + + 差异是由于基于 snd_una 与 write_seq 的。snd_una 是 peer 确认的 stream + 的偏移量。这取决于外部因素,例如网络 RTT。write_seq 是进程写入的最后一个 + 字节。此偏移量不受外部输入影响。 + + 差异细微,在套接字选项初始化时配置时不易察觉,但 SOF_TIMESTAMPING_OPT_ID_TCP + 行为在任何时候都更稳健。 + +SOF_TIMESTAMPING_OPT_CMSG: + 支持所有时间戳数据包的 recv() cmsg。控制消息已无条件地在所有接收时间戳数据包 + 和 IPv6 数据包上支持,以及在发送时间戳数据包的 IPv4 数据包上支持。此选项扩展 + 了它们以在发送时间戳数据包的 IPv4 数据包上支持。一个用例是启用 socket 选项 + IP_PKTINFO 以关联数据包与其出口设备,通过启用 socket 选项 IP_PKTINFO 同时。 + + +SOF_TIMESTAMPING_OPT_TSONLY: + 仅适用于传输时间戳。使内核返回一个 cmsg 与一个空数据包一起,而不是与原 + 始数据包一起。这减少了套接字接收预算(SO_RCVBUF)中收取的内存量,并即使 + 在 sysctl net.core.tstamp_allow_data 为 0 时也提供时间戳。此选项禁用 + SOF_TIMESTAMPING_OPT_CMSG。 + +SOF_TIMESTAMPING_OPT_STATS: + 与传输时间戳一起获取的选项性统计信息。它必须与 SOF_TIMESTAMPING_OPT_TSONLY + 一起使用。当传输时间戳可用时,统计信息可在类型为 SCM_TIMESTAMPING_OPT_STATS + 的单独控制消息中获取,作为 TLV(struct nlattr)类型的列表。这些统计信息允许应 + 用程序将各种传输层统计信息与传输时间戳关联,例如某个数据块被 peer 的接收窗口限 + 制了多长时间。 + +SOF_TIMESTAMPING_OPT_PKTINFO: + 启用 SCM_TIMESTAMPING_PKTINFO 控制消息以接收带有硬件时间戳的数据包。 + 消息包含 struct scm_ts_pktinfo,它提供接收数据包的实际接口索引和层 2 长度。 + 只有在 CONFIG_NET_RX_BUSY_POLL 启用且驱动程序使用 NAPI 时,才会返回非零的 + 有效接口索引。该结构还包含另外两个字段,但它们是保留字段且未定义。 + +SOF_TIMESTAMPING_OPT_TX_SWHW: + 请求在 SOF_TIMESTAMPING_TX_HARDWARE 和 SOF_TIMESTAMPING_TX_SOFTWARE + 同时启用时,为传出数据包生成硬件和软件时间戳。如果同时生成两个时间戳,两个单 + 独的消息将回环到套接字的错误队列,每个消息仅包含一个时间戳。 + +SOF_TIMESTAMPING_OPT_RX_FILTER: + 过滤掉虚假接收时间戳:仅当匹配的时间戳生成标志已启用时才报告接收时间戳。 + + 接收时间戳在入口路径中生成较早,在数据包的目的套接字确定之前。如果任何套接 + 字启用接收时间戳,所有套接字的数据包将接收时间戳数据包。包括那些请求时间戳 + 报告与 SOF_TIMESTAMPING_SOFTWARE 和/或 SOF_TIMESTAMPING_RAW_HARDWARE, + 但未请求接收时间戳生成。这可能发生在仅请求发送时间戳时。 + + 接收虚假时间戳通常是无害的。进程可以忽略意外的非零值。但它使行为在其他套接 + 字上微妙地依赖。此标志隔离套接字以获得更确定的行为。 + +新应用程序鼓励传递 SOF_TIMESTAMPING_OPT_ID 以区分时间戳并传递 +SOF_TIMESTAMPING_OPT_TSONLY 以操作,而不管 sysctl net.core.tstamp_allow_data +的设置。 + +例外情况是当进程需要额外的 cmsg 数据时,例如 SOL_IP/IP_PKTINFO 以检测出 +口网络接口。然后传递选项 SOF_TIMESTAMPING_OPT_CMSG。此选项依赖于访问原 +始数据包的内容,因此不能与 SOF_TIMESTAMPING_OPT_TSONLY 组合。 + + +1.3.4. 通过控制消息启用时间戳 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +除了套接字选项外,时间戳生成还可以通过 cmsg 按写入请求,仅适用于 +SOF_TIMESTAMPING_TX_*(见第 1.3.1 节)。使用此功能,应用程序可以无需启用和 +禁用时间戳即可采样每个 sendmsg() 的时间戳:: + + struct msghdr *msg; + ... + cmsg = CMSG_FIRSTHDR(msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SO_TIMESTAMPING; + cmsg->cmsg_len = CMSG_LEN(sizeof(__u32)); + *((__u32 *) CMSG_DATA(cmsg)) = SOF_TIMESTAMPING_TX_SCHED | + SOF_TIMESTAMPING_TX_SOFTWARE | + SOF_TIMESTAMPING_TX_ACK; + err = sendmsg(fd, msg, 0); + +通过 cmsg 设置的 SOF_TIMESTAMPING_TX_* 标志将覆盖通过 setsockopt 设置的 +SOF_TIMESTAMPING_TX_* 标志。 + +此外,应用程序仍然需要通过 setsockopt 启用时间戳报告以接收时间戳:: + + __u32 val = SOF_TIMESTAMPING_SOFTWARE | + SOF_TIMESTAMPING_OPT_ID /* 或任何其他标志 */; + err = setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &val, sizeof(val)); + + +1.4 字节流时间戳 +---------------- + +SO_TIMESTAMPING 接口支持字节流的时间戳。每个请求解释为请求当整个缓冲区内容 +通过时间戳点时。也就是说,对于流选项 SOF_TIMESTAMPING_TX_SOFTWARE 将记录 +当所有字节都到达设备驱动程序时,无论数据被转换成多少个数据包。 + +一般来说,字节流没有自然分隔符,因此将时间戳与数据相关联是非平凡的。字节范围 +可能跨段,任何段可能合并(可能合并先前分段缓冲区关联的独立 send() 调用)。段 +可以重新排序,同一字节范围可以在多个段中并存,对于实现重传的协议。 + +所有时间戳必须实现相同的语义,否则它们是不可比较的。以不同于简单情况(缓冲区 +到 skb 的 1:1 映射)的方式处理“罕见”角落情况是不够的,因为性能调试通常需要 +关注这些异常。 + +在实践中,时间戳可以与字节流段一致地关联,如果时间戳语义和测量时序的选择正确。 +此挑战与决定 IP 分片策略没有不同。在那里,定义是仅对第一个分片进行时间戳。对 +于字节流,我们选择仅在所有字节通过某个点时生成时间戳。SOF_TIMESTAMPING_TX_ACK +定义的实现和推理是容易的。一个需要考虑 SACK 的实现会更复杂,因为可能存在传输 +空洞和乱序到达。 + +在主机上,TCP 也可以通过 Nagle、cork、autocork、分段和 GSO 打破简单的 1:1 +缓冲区到 skbuff 映射。实现确保在所有情况下都正确,通过跟踪每个 send() 传递 +给send() 的最后一个字节,即使它在 skbuff 扩展或合并操作后不再是最后一个字 +节。它存储相关的序列号在 skb_shinfo(skb)->tskey。因为一个 skbuff 只有一 +个这样的字段,所以只能生成一个时间戳。 + +在罕见情况下,如果两个请求折叠到同一个 skb,则时间戳请求可能会被错过。进程可 +以通过始终在请求之间刷新 TCP 栈来检测此情况,例如启用 TCP_NODELAY 和禁用 +TCP_CORK和 autocork。在 linux-4.7 之后,更好的预防合并方法是使用 MSG_EOR +标志在sendmsg()时。 + +这些预防措施确保时间戳仅在所有字节通过时间戳点时生成,假设网络栈本身不会重新 +排序段。栈确实试图避免重新排序。唯一的例外是管理员控制:可以构造一个数据包调 +度器配置,将来自同一流的不同段延迟不同。这种设置通常不常见。 + + +2 数据接口 +========== + +时间戳通过 recvmsg() 的辅助数据功能读取。请参阅 `man 3 cmsg` 了解此接口的 +详细信息。套接字手册页面 (`man 7 socket`) 描述了如何检索SO_TIMESTAMP 和 +SO_TIMESTAMPNS 生成的数据包时间戳。 + + +2.1 SCM_TIMESTAMPING 记录 +------------------------- + +这些时间戳在 cmsg_level SOL_SOCKET、cmsg_type SCM_TIMESTAMPING 和类型为 + +对于 SO_TIMESTAMPING_OLD:: + + struct scm_timestamping { + struct timespec ts[3]; + }; + +对于 SO_TIMESTAMPING_NEW:: + + struct scm_timestamping64 { + struct __kernel_timespec ts[3]; + +始终使用 SO_TIMESTAMPING_NEW 时间戳以始终获得 struct scm_timestamping64 +格式的时间戳。 + +SO_TIMESTAMPING_OLD 在 32 位机器上 2038 年后返回错误的时间戳。 + +该结构可以返回最多三个时间戳。这是一个遗留功能。任何时候至少有一个字 +段不为零。大多数时间戳都通过 ts[0] 传递。硬件时间戳通过 ts[2] 传递。 + +ts[1] 以前用于存储硬件时间戳转换为系统时间。相反,将硬件时钟设备直接 +暴露为HW PTP时钟源,以允许用户空间进行时间转换,并可选地与用户空间 +PTP 堆栈(如linuxptp)同步系统时间。对于 PTP 时钟 API,请参阅 +Documentation/driver-api/ptp.rst。 + +注意,如果同时启用了 SO_TIMESTAMP 或 SO_TIMESTAMPNS 与 +SO_TIMESTAMPING 使用 SOF_TIMESTAMPING_SOFTWARE,在 recvmsg() +调用时会生成一个虚假的软件时间戳,并传递给 ts[0] 当真实软件时间戳缺 +失时。这也发生在硬件传输时间戳上。 + +2.1.1 传输时间戳与 MSG_ERRQUEUE +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +对于传输时间戳,传出数据包回环到套接字的错误队列,并附加发送时间戳(s)。 +进程通过调用带有 MSG_ERRQUEUE 标志的 recvmsg() 接收时间戳,并传递 +一个足够大的 msg_control缓冲区以接收相关的元数据结构。recvmsg 调用 +返回原始传出数据包,并附加两个辅助消息。 + +一个 cm_level SOL_IP(V6) 和 cm_type IP(V6)_RECVERR 嵌入一个 +struct sock_extended_err这定义了错误类型。对于时间戳,ee_errno +字段是 ENOMSG。另一个辅助消息将具有 cm_level SOL_SOCKET 和 cm_type +SCM_TIMESTAMPING。这嵌入了 struct scm_timestamping。 + + +2.1.1.2 时间戳类型 +~~~~~~~~~~~~~~~~~~ + +三个 struct timespec 的语义由 struct sock_extended_err 中的 +ee_info 字段定义。它包含一个类型 SCM_TSTAMP_* 来定义实际传递给 +scm_timestamping 的时间戳。 + +SCM_TSTAMP_* 类型与之前讨论的 SOF_TIMESTAMPING_* 控制字段完全 +匹配,只有一个例外对于遗留原因,SCM_TSTAMP_SND 等于零,可以设置为 +SOF_TIMESTAMPING_TX_HARDWARE 和 SOF_TIMESTAMPING_TX_SOFTWARE。 +它是第一个,如果 ts[2] 不为零,否则是第二个,在这种情况下,时间戳存 +储在ts[0] 中。 + + +2.1.1.3 分片 +~~~~~~~~~~~~ + +传出数据报分片很少见,但可能发生,例如通过显式禁用 PMTU 发现。如果 +传出数据包被分片,则仅对第一个分片进行时间戳,并返回给发送套接字。 + + +2.1.1.4 数据包负载 +~~~~~~~~~~~~~~~~~~ + +调用应用程序通常不关心接收它传递给堆栈的整个数据包负载:套接字错误队 +列机制仅是一种将时间戳附加到其上的方法。在这种情况下,应用程序可以选 +择读取较小的数据报,甚至长度为 0。负载相应地被截断。直到进程调用 +recvmsg() 到错误队列,然而,整个数据包仍在队列中,占用 SO_RCVBUF 预算。 + + +2.1.1.5 阻塞读取 +~~~~~~~~~~~~~~~~ + +从错误队列读取始终是非阻塞操作。要阻塞等待时间戳,请使用 poll 或 +select。poll() 将在 pollfd.revents 中返回 POLLERR,如果错误队列 +中有数据。没有必要在 pollfd.events中传递此标志。此标志在请求时被忽 +略。另请参阅 `man 2 poll`。 + + +2.1.2 接收时间戳 +^^^^^^^^^^^^^^^^ + +在接收时,没有理由从套接字错误队列读取。SCM_TIMESTAMPING 辅助数据与 +数据包数据一起通过正常 recvmsg() 发送。由于这不是套接字错误,它不伴 +随消息 SOL_IP(V6)/IP(V6)_RECVERROR。在这种情况下,struct +scm_timestamping 中的三个字段含义隐式定义。ts[0] 在设置时包含软件 +时间戳,ts[1] 再次被弃用,ts[2] 在设置时包含硬件时间戳。 + + +3. 硬件时间戳配置:ETHTOOL_MSG_TSCONFIG_SET/GET +=============================================== + +硬件时间戳也必须为每个设备驱动程序初始化,该驱动程序预期执行硬件时间戳。 +参数在 include/uapi/linux/net_tstamp.h 中定义为:: + + struct hwtstamp_config { + int flags; /* 目前没有定义的标志,必须为零 */ + int tx_type; /* HWTSTAMP_TX_* */ + int rx_filter; /* HWTSTAMP_FILTER_* */ + }; + +期望的行为通过 tsconfig netlink 套接字 ``ETHTOOL_MSG_TSCONFIG_SET`` +传递到内核,并通过 ``ETHTOOL_A_TSCONFIG_TX_TYPES``、 +``ETHTOOL_A_TSCONFIG_RX_FILTERS`` 和 ``ETHTOOL_A_TSCONFIG_HWTSTAMP_FLAGS`` +netlink 属性设置 struct hwtstamp_config 相应地。 + +``ETHTOOL_A_TSCONFIG_HWTSTAMP_PROVIDER`` netlink 嵌套属性用于选择 +硬件时间戳的来源。它由设备源的索引和时间戳类型限定符组成。 + +驱动程序可以自由使用比请求更宽松的配置。预期驱动程序应仅实现可以直接支持的 +最通用模式。例如,如果硬件可以支持 HWTSTAMP_FILTER_PTP_V2_EVENT,则它 +通常应始终升级HWTSTAMP_FILTER_PTP_V2_L2_SYNC,依此类推,因为 +HWTSTAMP_FILTER_PTP_V2_EVENT 更通用(更实用)。 + +支持硬件时间戳的驱动程序应更新 struct,并可能返回更宽松的实际配置。如果 +请求的数据包无法进行时间戳,则不应更改任何内容,并返回 ERANGE(与 EINVAL +相反,这表明 SIOCSHWTSTAMP 根本不支持)。 + +只有具有管理权限的进程才能更改配置。用户空间负责确保多个进程不会相互干扰, +并确保设置被重置。 + +任何进程都可以通过请求 tsconfig netlink 套接字 ``ETHTOOL_MSG_TSCONFIG_GET`` +读取实际配置。 + +遗留配置是使用 ioctl(SIOCSHWTSTAMP) 与指向 struct ifreq 的指针,其 +ifr_data指向 struct hwtstamp_config。tx_type 和 rx_filter 是驱动 +程序期望执行的提示。如果请求的细粒度过滤对传入数据包不支持,驱动程序可能 +会对请求的数据包进行时间戳。ioctl(SIOCGHWTSTAMP) 以与 +ioctl(SIOCSHWTSTAMP) 相同的方式使用。然而,并非所有驱动程序都实现了这一点。 + +:: + + /* 可能的 hwtstamp_config->tx_type 值 */ + enum { + /* + * 不会需要硬件时间戳的传出数据包; + * 如果数据包到达并请求它,则不会进行硬件时间戳 + */ + HWTSTAMP_TX_OFF, + + /* + * 启用传出数据包的硬件时间戳; + * 数据包的发送者决定哪些数据包需要时间戳, + * 在发送数据包之前设置 SOF_TIMESTAMPING_TX_SOFTWARE + */ + HWTSTAMP_TX_ON, + }; + + /* 可能的 hwtstamp_config->rx_filter 值 */ + enum { + /* 时间戳不传入任何数据包 */ + HWTSTAMP_FILTER_NONE, + + /* 时间戳任何传入数据包 */ + HWTSTAMP_FILTER_ALL, + + /* 返回值:时间戳所有请求的数据包加上一些其他数据包 */ + HWTSTAMP_FILTER_SOME, + + /* PTP v1,UDP,任何事件数据包 */ + HWTSTAMP_FILTER_PTP_V1_L4_EVENT, + + /* 有关完整值列表,请检查 + * 文件 include/uapi/linux/net_tstamp.h + */ + }; + +3.1 硬件时间戳实现:设备驱动程序 +-------------------------------- + +支持硬件时间戳的驱动程序必须支持 ndo_hwtstamp_set NDO 或遗留 SIOCSHWTSTAMP +ioctl 并更新提供的 struct hwtstamp_config 与实际值,如 SIOCSHWTSTAMP 部分 +所述。它还应支持 ndo_hwtstamp_get 或遗留 SIOCGHWTSTAMP。 + +接收数据包的时间戳必须存储在 skb 中。要获取 skb 的共享时间戳结构,请调用 +skb_hwtstamps()。然后设置结构中的时间戳:: + + struct skb_shared_hwtstamps { + /* 硬件时间戳转换为自任意时间点的持续时间 + * 自定义点 + */ + ktime_t hwtstamp; + }; + +传出数据包的时间戳应按如下方式生成: + +- 在 hard_start_xmit() 中,检查 (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) + 是否不为零。如果是,则驱动程序期望执行硬件时间戳。 +- 如果此 skb 和请求都可能,则声明驱动程序正在执行时间戳,通过设置 skb_shinfo(skb)->tx_flags + 中的标志SKBTX_IN_PROGRESS,例如:: + + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + + 您可能希望保留与 skb 关联的指针,而不是释放 skb。不支持硬件时间戳的驱 + 动程序不会这样做。驱动程序绝不能触及 sk_buff::tstamp!它用于存储网络 + 子系统生成的软件时间戳。 +- 驱动程序应在尽可能接近将 sk_buff 传递给硬件时调用 skb_tx_timestamp()。 + skb_tx_timestamp()提供软件时间戳(如果请求),并且硬件时间戳不可用 + (SKBTX_IN_PROGRESS 未设置)。 +- 一旦驱动程序发送数据包并/或获取硬件时间戳,它就会通过 skb_tstamp_tx() + 传递时间戳,原始 skb,原始硬件时间戳。skb_tstamp_tx() 克隆原始 skb 并 + 添加时间戳,因此原始 skb 现在必须释放。如果获取硬件时间戳失败,则驱动程序 + 不应回退到软件时间戳。理由是,这会在处理管道中的稍后时间发生,而不是其他软 + 件时间戳,因此可能导致时间戳之间的差异。 + +3.2 堆叠 PTP 硬件时钟的特殊考虑 +------------------------------- + +在数据包的路径中可能存在多个 PHC(PTP 硬件时钟)。内核没有明确的机制允许用 +户选择用于时间戳以太网帧的 PHC。相反,假设最外层的 PHC 始终是最优的,并且 +内核驱动程序协作以实现这一目标。目前有 3 种堆叠 PHC 的情况,如下所示: + +3.2.1 DSA(分布式交换架构)交换机 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +这些是具有一个端口连接到(完全不知情的)主机以太网接口的以太网交换机,并且 +执行端口多路复用或可选转发加速功能。每个 DSA 交换机端口在用户看来都是独立的 +(虚拟)网络接口,其网络 I/O 在底层通过主机接口(在 TX 上重定向到主机端口, +在 RX 上拦截帧)执行。 + +当 DSA 交换机连接到主机端口时,PTP 同步必须受到限制,因为交换机的可变排队 +延迟引入了主机端口与其 PTP 伙伴之间的路径延迟抖动。因此,一些 DSA 交换机 +包含自己的时间戳时钟,并具有在自身 MAC上执行网络时间戳的能力,因此路径延迟 +仅测量线缆和 PHY 传播延迟。支持 Linux 的 DSA 交换机暴露了与任何其他网络 +接口相同的 ABI(除了 DSA 接口在网络 I/O 方面实际上是虚拟的,它们确实有自 +己的PHC)。典型地,但不是强制性地,所有DSA 交换机接口共享相同的 PHC。 + +通过设计,DSA 交换机对连接到其主机端口的 PTP 时间戳不需要任何特殊的驱动程 +序处理。然而,当主机端口也支持 PTP 时间戳时,DSA 将负责拦截 +``.ndo_eth_ioctl`` 调用,并阻止尝试在主机端口上启用硬件时间戳。这是因为 +SO_TIMESTAMPING API 不允许为同一数据包传递多个硬件时间戳,因此除了 DSA +交换机端口之外的任何人都不应阻止这样做。 + +在通用层,DSA 提供了以下基础设施用于 PTP 时间戳: + +- ``.port_txtstamp()``:在用户空间从用户空间请求带有硬件 TX 时间戳请求 + 的数据包之前调用的钩子。这是必需的,因为硬件时间戳在实际 MAC 传输后才可 + 用,因此驱动程序必须准备将时间戳与原始数据包相关联,以便它可以重新入队数 + 据包到套接字的错误队列。为了保存可能在时间戳可用时需要的数据包,驱动程序 + 可以调用 ``skb_clone_sk``,在 skb->cb 中保存克隆指针,并入队一个 tx + skb 队列。通常,交换机会有一个PTP TX 时间戳寄存器(或有时是一个 FIFO), + 其中时间戳可用。在 FIFO 的情况下,硬件可能会存储PTP 序列 ID/消息类型/ + 域号和实际时间戳的键值对。为了在等待时间戳的数据包队列和实际时间戳之间正 + 确关联,驱动程序可以使用 BPF 分类器(``ptp_classify_raw``) 来识别 PTP + 传输类型,并使用 ``ptp_parse_header`` 解释 PTP 头字段。可能存在一个 IRQ, + 当此时间戳可用时触发,或者驱动程序可能需要轮询,在调用 ``dev_queue_xmit()`` + 到主机接口之后。单步 TX 时间戳不需要数据包克隆,因为 PTP 协议不需要后续消 + 息(因为TX 时间戳已嵌入到数据包中),因此用户空间不期望数据包带有 TX 时间戳 + 被重新入队到其套接字的错误队列。 + +- ``.port_rxtstamp()``:在 RX 上,DSA 运行 BPF 分类器以识别 PTP 事件消息 + (任何其他数据包,包括 PTP 通用消息,不进行时间戳)。驱动程序提供原始(也是唯一) + 时间戳数据包,以便它可以标记它,如果它是立即可用的,或者延迟。在接收时,时间 + 戳可能要么在频带内(通过DSA 头中的元数据,或以其他方式附加到数据包),要么在频 + 带外(通过另一个 RX 时间戳FIFO)。在 RX 上延迟通常是必要的,当检索时间戳需要 + 可睡眠上下文时。在这种情况下,DSA驱动程序有责任调用 ``netif_rx()`` 在新鲜时 + 间戳的 skb 上。 + +3.2.2 以太网 PHYs +^^^^^^^^^^^^^^^^^ + +这些是通常在网络栈中履行第 1 层角色的设备,因此它们在 DSA 交换机中没有网络接 +口的表示。然而,PHY可能能够检测和时间戳 PTP 数据包,出于性能原因:在尽可能接 +近导线的地方获取的时间戳具有更稳定的同步性和更精确的精度。 + +支持 PTP 时间戳的 PHY 驱动程序必须创建 ``struct mii_timestamper`` 并添加 +指向它的指针在 ``phydev->mii_ts`` 中。 ``phydev->mii_ts`` 的存在将由网络 +堆栈检查。 + +由于 PHY 没有网络接口表示,PHY 的时间戳和 ethtool ioctl 操作需要通过其各自 +的 MAC驱动程序进行中介。因此,与 DSA 交换机不同,需要对每个单独的 MAC 驱动 +程序进行 PHY时间戳支持的修改。这包括: + +- 在 ``.ndo_eth_ioctl`` 中检查,是否 ``phy_has_hwtstamp(netdev->phydev)`` + 为真或假。如果是,则 MAC 驱动程序不应处理此请求,而应将其传递给 PHY 使用 + ``phy_mii_ioctl()``。 + +- 在 RX 上,特殊干预可能或可能不需要,具体取决于将 skb 传递到网络堆栈的函数。 + 在 plain ``netif_rx()`` 和类似情况下,MAC 驱动程序必须检查是否 + ``skb_defer_rx_timestamp(skb)`` 是必要的,如果是,则不调用 ``netif_rx()``。 + 如果 ``CONFIG_NETWORK_PHY_TIMESTAMPING`` 启用,并且 + ``skb->dev->phydev->mii_ts`` 存在,它的 ``.rxtstamp()`` 钩子现在将被调 + 用,以使用与 DSA 类似的逻辑确定 RX 时间戳延迟是否必要。同样像 DSA,它成为 + PHY 驱动程序的责任,在时间戳可用时发送数据包到堆栈。 + + 对于其他 skb 接收函数,例如 ``napi_gro_receive`` 和 ``netif_receive_skb``, + 堆栈会自动检查是否 ``skb_defer_rx_timestamp()`` 是必要的,因此此检查不 + 需要在驱动程序内部。 + +- 在 TX 上,同样,特殊干预可能或可能不需要。调用 ``mii_ts->txtstamp()``钩 + 子的函数名为``skb_clone_tx_timestamp()``。此函数可以直接调用(在这种情 + 况下,确实需要显式 MAC 驱动程序支持),但函数也 piggybacks 从 + ``skb_tx_timestamp()`` 调用,许多 MAC 驱动程序已经为软件时间戳目的执行。 + 因此,如果 MAC 支持软件时间戳,则它不需要在此阶段执行任何其他操作。 + +3.2.3 MII 总线嗅探设备 +^^^^^^^^^^^^^^^^^^^^^^ + +这些执行与时间戳以太网 PHY 相同的角色,除了它们是离散设备,因此可以与任何 PHY +组合,即使它不支持时间戳。在 Linux 中,它们是可发现的,可以通过 Device Tree +附加到 ``struct phy_device``,对于其余部分,它们使用与那些相同的 mii_ts 基 +础设施。请参阅 Documentation/devicetree/bindings/ptp/timestamper.txt 了 +解更多详细信息。 + +3.2.4 MAC 驱动程序的其他注意事项 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +堆叠 PHC 可能会暴露 MAC 驱动程序的错误,这些错误在未堆叠 PHC 时无法触发。一个 +例子涉及此行代码,已经在前面的部分中介绍过:: + + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + +任何 TX 时间戳逻辑,无论是普通的 MAC 驱动程序、DSA 交换机驱动程序、PHY 驱动程 +序还是 MII 总线嗅探设备驱动程序,都应该设置此标志。但一个未意识到 PHC 堆叠的 +MAC 驱动程序可能会被其他不是它自己的实体设置此标志,并传递一个重复的时间戳。例 +如,典型的 TX 时间戳逻辑可能是将传输部分分为 2 个部分: + +1. "TX":检查是否通过 ``.ndo_eth_ioctl``("``priv->hwtstamp_tx_enabled + == true``")和当前 skb 是否需要 TX 时间戳("``skb_shinfo(skb)->tx_flags + & SKBTX_HW_TSTAMP``")。如果为真,则设置 "``skb_shinfo(skb)->tx_flags + |= SKBTX_IN_PROGRESS``" 标志。注意:如上所述,在堆叠 PHC 系统中,此条件 + 不应触发,因为此 MAC 肯定不是最外层的 PHC。但这是典型的错误所在。传输继续 + 使用此数据包。 + +2. "TX 确认":传输完成。驱动程序检查是否需要收集任何 TX 时间戳。这里通常是典 + 型的错误所在:驱动程序采取捷径,只检查 "``skb_shinfo(skb)->tx_flags & + SKBTX_IN_PROGRESS``" 是否设置。在堆叠 PHC 系统中,这是错误的,因为此 MAC + 驱动程序不是唯一在 TX 数据路径中启用 SKBTX_IN_PROGRESS 的实体。 + +此问题的正确解决方案是 MAC 驱动程序在其 "TX 确认" 部分中有一个复合检查,不仅 +针对 "``skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS``",还针对 +"``priv->hwtstamp_tx_enabled == true``"。因为系统确保 PTP 时间戳仅对最 +外层 PHC 启用,此增强检查将避免向用户空间传递重复的 TX 时间戳。 diff --git a/Documentation/translations/zh_CN/rust/general-information.rst b/Documentation/translations/zh_CN/rust/general-information.rst index 251f6ee2bb44..9b5e37e13f38 100644 --- a/Documentation/translations/zh_CN/rust/general-information.rst +++ b/Documentation/translations/zh_CN/rust/general-information.rst @@ -13,6 +13,7 @@ 本文档包含了在内核中使用Rust支持时需要了解的有用信息。 +.. _rust_code_documentation_zh_cn: 代码文档 -------- diff --git a/Documentation/translations/zh_CN/rust/index.rst b/Documentation/translations/zh_CN/rust/index.rst index b01f887e7167..5347d4729588 100644 --- a/Documentation/translations/zh_CN/rust/index.rst +++ b/Documentation/translations/zh_CN/rust/index.rst @@ -10,7 +10,35 @@ Rust ==== -与内核中的Rust有关的文档。若要开始在内核中使用Rust,请阅读quick-start.rst指南。 +与内核中的Rust有关的文档。若要开始在内核中使用Rust,请阅读 quick-start.rst 指南。 + +Rust 实验 +--------- +Rust 支持在 v6.1 版本中合并到主线,以帮助确定 Rust 作为一种语言是否适合内核, +即是否值得进行权衡。 + +目前,Rust 支持主要面向对 Rust 支持感兴趣的内核开发人员和维护者, +以便他们可以开始处理抽象和驱动程序,并帮助开发基础设施和工具。 + +如果您是终端用户,请注意,目前没有适合或旨在生产使用的内置驱动程序或模块, +并且 Rust 支持仍处于开发/实验阶段,尤其是对于特定内核配置。 + +代码文档 +-------- + +给定一个内核配置,内核可能会生成 Rust 代码文档,即由 ``rustdoc`` 工具呈现的 HTML。 + +.. only:: rustdoc and html + + 该内核文档使用 `Rust 代码文档 <rustdoc/kernel/index.html>`_ 构建。 + +.. only:: not rustdoc and html + + 该内核文档不使用 Rust 代码文档构建。 + +预生成版本提供在:https://rust.docs.kernel.org。 + +请参阅 :ref:`代码文档 <rust_code_documentation_zh_cn>` 部分以获取更多详细信息。 .. toctree:: :maxdepth: 1 @@ -19,6 +47,9 @@ Rust general-information coding-guidelines arch-support + testing + +你还可以在 :doc:`../../../process/kernel-docs` 中找到 Rust 的学习材料。 .. only:: subproject and html diff --git a/Documentation/translations/zh_CN/rust/testing.rst b/Documentation/translations/zh_CN/rust/testing.rst new file mode 100644 index 000000000000..ca81f1cef6eb --- /dev/null +++ b/Documentation/translations/zh_CN/rust/testing.rst @@ -0,0 +1,215 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/rust/testing.rst + +:翻译: + + 郭杰 Ben Guo <benx.guo@gmail.com> + +测试 +==== + +本文介绍了如何在内核中测试 Rust 代码。 + +有三种测试类型: + +- KUnit 测试 +- ``#[test]`` 测试 +- Kselftests + +KUnit 测试 +---------- + +这些测试来自 Rust 文档中的示例。它们会被转换为 KUnit 测试。 + +使用 +**** + +这些测试可以通过 KUnit 运行。例如,在命令行中使用 ``kunit_tool`` ( ``kunit.py`` ):: + + ./tools/testing/kunit/kunit.py run --make_options LLVM=1 --arch x86_64 --kconfig_add CONFIG_RUST=y + +或者,KUnit 也可以在内核启动时以内置方式运行。获取更多 KUnit 信息,请参阅 +Documentation/dev-tools/kunit/index.rst。 +关于内核内置与命令行测试的详细信息,请参阅 Documentation/dev-tools/kunit/architecture.rst。 + +要使用这些 KUnit 文档测试,需要在内核配置中启用以下选项:: + + CONFIG_KUNIT + Kernel hacking -> Kernel Testing and Coverage -> KUnit - Enable support for unit tests + CONFIG_RUST_KERNEL_DOCTESTS + Kernel hacking -> Rust hacking -> Doctests for the `kernel` crate + +KUnit 测试即文档测试 +******************** + +文档测试( *doctests* )一般用于展示函数、结构体或模块等的使用方法。 + +它们非常方便,因为它们就写在文档旁边。例如: + +.. code-block:: rust + + /// 求和两个数字。 + /// + /// ``` + /// assert_eq!(mymod::f(10, 20), 30); + /// ``` + pub fn f(a: i32, b: i32) -> i32 { + a + b + } + +在用户空间中,这些测试由 ``rustdoc`` 负责收集并运行。单独使用这个工具已经很有价值, +因为它可以验证示例能否成功编译(确保和代码保持同步), +同时还可以运行那些不依赖内核 API 的示例。 + +然而,在内核中,这些测试会转换成 KUnit 测试套件。 +这意味着文档测试会被编译成 Rust 内核对象,从而可以在构建的内核环境中运行。 + +通过与 KUnit 集成,Rust 的文档测试可以复用内核现有的测试设施。 +例如,内核日志会显示:: + + KTAP version 1 + 1..1 + KTAP version 1 + # Subtest: rust_doctests_kernel + 1..59 + # rust_doctest_kernel_build_assert_rs_0.location: rust/kernel/build_assert.rs:13 + ok 1 rust_doctest_kernel_build_assert_rs_0 + # rust_doctest_kernel_build_assert_rs_1.location: rust/kernel/build_assert.rs:56 + ok 2 rust_doctest_kernel_build_assert_rs_1 + # rust_doctest_kernel_init_rs_0.location: rust/kernel/init.rs:122 + ok 3 rust_doctest_kernel_init_rs_0 + ... + # rust_doctest_kernel_types_rs_2.location: rust/kernel/types.rs:150 + ok 59 rust_doctest_kernel_types_rs_2 + # rust_doctests_kernel: pass:59 fail:0 skip:0 total:59 + # Totals: pass:59 fail:0 skip:0 total:59 + ok 1 rust_doctests_kernel + +文档测试中,也可以正常使用 `? <https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator>`_ 运算符,例如: + +.. code-block:: rust + + /// ``` + /// # use kernel::{spawn_work_item, workqueue}; + /// spawn_work_item!(workqueue::system(), || pr_info!("x\n"))?; + /// # Ok::<(), Error>(()) + /// ``` + +这些测试和普通代码一样,也可以在 ``CLIPPY=1`` 条件下通过 Clippy 进行编译, +因此可以从额外的 lint 检查中获益。 + +为了便于开发者定位文档测试出错的具体行号,日志会输出一条 KTAP 诊断信息。 +其中标明了原始测试的文件和行号(不是 ``rustdoc`` 生成的临时 Rust 文件位置):: + + # rust_doctest_kernel_types_rs_2.location: rust/kernel/types.rs:150 + +Rust 测试中常用的断言宏是来自 Rust 标准库( ``core`` )中的 ``assert!`` 和 ``assert_eq!`` 宏。 +内核提供了一个定制版本,这些宏的调用会被转发到 KUnit。 +和 KUnit 测试不同的是,这些宏不需要传递上下文参数( ``struct kunit *`` )。 +这使得它们更易于使用,同时文档的读者无需关心底层用的是什么测试框架。 +此外,这种方式未来也许可以让我们更容易测试第三方代码。 + +当前有一个限制:KUnit 不支持在其他任务中执行断言。 +因此,如果断言真的失败了,我们只是简单地把错误打印到内核日志里。 +另外,文档测试不适用于非公开的函数。 + +作为文档中的测试示例,应当像 “实际代码” 一样编写。 +例如:不要使用 ``unwrap()`` 或 ``expect()``,请使用 `? <https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator>`_ 运算符。 +更多背景信息,请参阅: + + https://rust.docs.kernel.org/kernel/error/type.Result.html#error-codes-in-c-and-rust + +``#[test]`` 测试 +---------------- + +此外,还有 ``#[test]`` 测试。与文档测试类似,这些测试与用户空间中的测试方式也非常相近,并且同样会映射到 KUnit。 + +这些测试通过 ``kunit_tests`` 过程宏引入,该宏将测试套件的名称作为参数。 + +例如,假设想要测试前面文档测试示例中的函数 ``f``,我们可以在定义该函数的同一文件中编写: + +.. code-block:: rust + + #[kunit_tests(rust_kernel_mymod)] + mod tests { + use super::*; + + #[test] + fn test_f() { + assert_eq!(f(10, 20), 30); + } + } + +如果我们执行这段代码,内核日志会显示:: + + KTAP version 1 + # Subtest: rust_kernel_mymod + # speed: normal + 1..1 + # test_f.speed: normal + ok 1 test_f + ok 1 rust_kernel_mymod + +与文档测试类似, ``assert!`` 和 ``assert_eq!`` 宏被映射回 KUnit 并且不会发生 panic。 +同样,支持 `? <https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator>`_ 运算符, +测试函数可以什么都不返回(单元类型 ``()``)或 ``Result`` (任何 ``Result<T, E>``)。例如: + +.. code-block:: rust + + #[kunit_tests(rust_kernel_mymod)] + mod tests { + use super::*; + + #[test] + fn test_g() -> Result { + let x = g()?; + assert_eq!(x, 30); + Ok(()) + } + } + +如果我们运行测试并且调用 ``g`` 失败,那么内核日志会显示:: + + KTAP version 1 + # Subtest: rust_kernel_mymod + # speed: normal + 1..1 + # test_g: ASSERTION FAILED at rust/kernel/lib.rs:335 + Expected is_test_result_ok(test_g()) to be true, but is false + # test_g.speed: normal + not ok 1 test_g + not ok 1 rust_kernel_mymod + +如果 ``#[test]`` 测试可以对用户起到示例作用,那就应该改用文档测试。 +即使是 API 的边界情况,例如错误或边界问题,放在示例中展示也同样有价值。 + +``rusttest`` 宿主机测试 +----------------------- + +这类测试运行在用户空间,可以通过 ``rusttest`` 目标在构建内核的宿主机中编译并运行:: + + make LLVM=1 rusttest + +当前操作需要内核 ``.config``。 + +目前,它们主要用于测试 ``macros`` crate 的示例。 + +Kselftests +---------- + +Kselftests 可以在 ``tools/testing/selftests/rust`` 文件夹中找到。 + +测试所需的内核配置选项列在 ``tools/testing/selftests/rust/config`` 文件中, +可以借助 ``merge_config.sh`` 脚本合并到现有配置中:: + + ./scripts/kconfig/merge_config.sh .config tools/testing/selftests/rust/config + +Kselftests 会在内核源码树中构建,以便在运行相同版本内核的系统上执行测试。 + +一旦安装并启动了与源码树匹配的内核,测试即可通过以下命令编译并执行:: + + make TARGETS="rust" kselftest + +请参阅 Documentation/dev-tools/kselftest.rst 文档以获取更多信息。 diff --git a/Documentation/translations/zh_CN/scsi/index.rst b/Documentation/translations/zh_CN/scsi/index.rst new file mode 100644 index 000000000000..5f1803e2706c --- /dev/null +++ b/Documentation/translations/zh_CN/scsi/index.rst @@ -0,0 +1,92 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scsi/index.rst + +:翻译: + + 郝栋栋 doubled <doubled@leap-io-kernel.com> + +:校译: + + + +========== +SCSI子系统 +========== + +.. toctree:: + :maxdepth: 1 + +简介 +==== + +.. toctree:: + :maxdepth: 1 + + scsi + +SCSI驱动接口 +============ + +.. toctree:: + :maxdepth: 1 + + scsi_mid_low_api + scsi_eh + +SCSI驱动参数 +============ + +.. toctree:: + :maxdepth: 1 + + scsi-parameters + link_power_management_policy + +SCSI主机适配器驱动 +================== + +.. toctree:: + :maxdepth: 1 + + libsas + sd-parameters + wd719x + +Todolist: + +* 53c700 +* aacraid +* advansys +* aha152x +* aic79xx +* aic7xxx +* arcmsr_spec +* bfa +* bnx2fc +* BusLogic +* cxgb3i +* dc395x +* dpti +* FlashPoint +* g_NCR5380 +* hpsa +* hptiop +* lpfc +* megaraid +* ncr53c8xx +* NinjaSCSI +* ppa +* qlogicfas +* scsi-changer +* scsi_fc_transport +* scsi-generic +* smartpqi +* st +* sym53c500_cs +* sym53c8xx_2 +* tcm_qla2xxx +* ufs + +* scsi_transport_srp/figures diff --git a/Documentation/translations/zh_CN/scsi/libsas.rst b/Documentation/translations/zh_CN/scsi/libsas.rst new file mode 100644 index 000000000000..15fa71cdd821 --- /dev/null +++ b/Documentation/translations/zh_CN/scsi/libsas.rst @@ -0,0 +1,425 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scsi/libsas.rst + +:翻译: + + 张钰杰 Yujie Zhang <yjzhang@leap-io-kernel.com> + +:校译: + +====== +SAS 层 +====== + +SAS 层是一个管理基础架构,用于管理 SAS LLDD。它位于 SCSI Core +与 SAS LLDD 之间。 体系结构如下: SCSI Core 关注的是 SAM/SPC 相 +关的问题;SAS LLDD 及其序列控制器负责 PHY 层、OOB 信号以及链路 +管理;而 SAS 层则负责以下任务:: + + * SAS Phy、Port 和主机适配器(HA)事件管理(事件由 LLDD + 生成,由 SAS 层处理); + * SAS 端口的管理(创建与销毁); + * SAS 域的发现与重新验证; + * SAS 域内设备的管理; + * SCSI 主机的注册与注销; + * 将设备注册到 SCSI Core(SAS 设备)或 libata(SATA 设备); + * 扩展器的管理,并向用户空间导出扩展器控制接口。 + +SAS LLDD 是一种 PCI 设备驱动程序。它负责 PHY 层和 OOB(带外) +信号的管理、厂商特定的任务,并向 SAS 层上报事件。 + +SAS 层实现了 SAS 1.1 规范中定义的大部分 SAS 功能。 + +sas_ha_struct 结构体用于向 SAS 层描述一个 SAS LLDD。该结构的 +大部分字段由 SAS 层使用,但其中少数字段需要由 LLDD 进行初始化。 + +在完成硬件初始化之后,应当在驱动的 probe() 函数中调用 +sas_register_ha()。该函数会将 LLDD 注册到 SCSI 子系统中,创 +建一个对应的 SCSI 主机,并将你的 SAS 驱动程序注册到其在 sysfs +下创建的 SAS 设备树中。随后该函数将返回。接着,你需要使能 PHY, +以启动实际的 OOB(带外)过程;此时驱动将开始调用 notify_* 系 +列事件回调函数。 + +结构体说明 +========== + +``struct sas_phy`` +------------------ + +通常情况下,该结构体会被静态地嵌入到驱动自身定义的 PHY 结构体中, +例如:: + + struct my_phy { + blah; + struct sas_phy sas_phy; + bleh; + } + +随后,在主机适配器(HA)的结构体中,所有的 PHY 通常以 my_phy +数组的形式存在(如下文所示)。 + +在初始化各个 PHY 时,除了初始化驱动自定义的 PHY 结构体外,还 +需要同时初始化其中的 sas_phy 结构体。 + +一般来说,PHY 的管理由 LLDD 负责,而端口(port)的管理由 SAS +层负责。因此,PHY 的初始化与更新由 LLDD 完成,而端口的初始化与 +更新则由 SAS 层完成。系统设计中规定,某些字段可由 LLDD 进行读 +写,而 SAS 层只能读取这些字段;反之亦然。其设计目的是为了避免不 +必要的锁操作。 + +在该设计中,某些字段可由 LLDD 进行读写(RW),而 SAS 层仅可读 +取这些字段;反之亦然。这样设计的目的在于避免不必要的锁操作。 + +enabled + - 必须设置(0/1) + +id + - 必须设置[0,MAX_PHYS)] + +class, proto, type, role, oob_mode, linkrate + - 必须设置。 + +oob_mode + - 当 OOB(带外信号)完成后,设置此字段,然后通知 SAS 层。 + +sas_addr + - 通常指向一个保存该 PHY 的 SAS 地址的数组,该数组可能位于 + 驱动自定义的 my_phy 结构体中。 + +attached_sas_addr + - 当 LLDD 接收到 IDENTIFY 帧或 FIS 帧时,应在通知 SAS 层 + 之前设置该字段。其设计意图在于:有时 LLDD 可能需要伪造或 + 提供一个与实际不同的 SAS 地址用于该 PHY/端口,而该机制允许 + LLDD 这样做。理想情况下,应将 SAS 地址从 IDENTIFY 帧中 + 复制过来;对于直接连接的 SATA 设备,也可以由 LLDD 生成一 + 个 SAS 地址。后续的发现过程可能会修改此字段。 + +frame_rcvd + - 当接收到 IDENTIFY 或 FIS 帧时,将该帧复制到此处。正确的 + 操作流程是获取锁 → 复制数据 → 设置 frame_rcvd_size → 释 + 放锁 → 调用事件通知。该字段是一个指针,因为驱动无法精确确 + 定硬件帧的大小;因此,实际的帧数据数组应定义在驱动自定义的 + PHY 结构体中,然后让此指针指向该数组。在持锁状态下,将帧从 + DMA 可访问内存区域复制到该数组中。 + +sas_prim + - 用于存放接收到的原语(primitive)。参见 sas.h。操作流程同 + 样是:获取锁 → 设置 primitive → 释放锁 → 通知事件。 + +port + - 如果该 PHY 属于某个端口(port),此字段指向对应的 sas_port + 结构体。LLDD 仅可读取此字段。它由 SAS 层设置,用于指向当前 + PHY 所属的 sas_port。 + +ha + - 可以由 LLDD 设置;但无论是否设置,SAS 层都会再次对其进行赋值。 + +lldd_phy + - LLDD 应将此字段设置为指向自身定义的 PHY 结构体,这样当 SAS + 层调用某个回调并传入 sas_phy 时,驱动可以快速定位自身的 PHY + 结构体。如果 sas_phy 是嵌入式成员,也可以使用 container_of() + 宏进行访问——两种方式均可。 + +``struct sas_port`` +------------------- + +LLDD 不应修改该结构体中的任何字段——它只能读取这些字段。这些字段的 +含义应当是不言自明的。 + +phy_mask 为 32 位,目前这一长度已足够使用,因为尚未听说有主机适配 +器拥有超过8 个 PHY。 + +lldd_port + - 目前尚无明确用途。不过,对于那些希望在 LLDD 内部维护自身端 + 口表示的驱动,实现时可以利用该字段。 + +``struct sas_ha_struct`` +------------------------ + +它通常静态声明在你自己的 LLDD 结构中,用于描述您的适配器:: + + struct my_sas_ha { + blah; + struct sas_ha_struct sas_ha; + struct my_phy phys[MAX_PHYS]; + struct sas_port sas_ports[MAX_PHYS]; /* (1) */ + bleh; + }; + + (1) 如果你的 LLDD 没有自己的端口表示 + +需要初始化(示例函数如下所示)。 + +pcidev +^^^^^^ + +sas_addr + - 由于 SAS 层不想弄乱内存分配等, 因此这指向静态分配的数 + 组中的某个位置(例如,在您的主机适配器结构中),并保存您或 + 制造商等给出的主机适配器的 SAS 地址。 + +sas_port +^^^^^^^^ + +sas_phy + - 指向结构体的指针数组(参见上文关于 sas_addr 的说明)。 + 这些指针必须设置。更多细节见下文说明。 + +num_phys + - 表示 sas_phy 数组中 PHY 的数量,同时也表示 sas_port + 数组中的端口数量。一个端口最多对应一个 PHY,因此最大端口数 + 等于 num_phys。因此,结构中不再单独使用 num_ports 字段, + 而仅使用 num_phys。 + +事件接口:: + + /* LLDD 调用以下函数来通知 SAS 类层发生事件 */ + void sas_notify_port_event(struct sas_phy *, enum port_event, gfp_t); + void sas_notify_phy_event(struct sas_phy *, enum phy_event, gfp_t); + +端口事件通知:: + + /* SAS 类层调用以下回调来通知 LLDD 端口事件 */ + void (*lldd_port_formed)(struct sas_phy *); + void (*lldd_port_deformed)(struct sas_phy *); + +如果 LLDD 希望在端口形成或解散时接收通知,则应将上述回调指针设 +置为符合函数类型定义的处理函数。 + +SAS LLDD 还应至少实现 SCSI 协议中定义的一种任务管理函数(TMFs):: + + /* 任务管理函数. 必须在进程上下文中调用 */ + int (*lldd_abort_task)(struct sas_task *); + int (*lldd_abort_task_set)(struct domain_device *, u8 *lun); + int (*lldd_clear_task_set)(struct domain_device *, u8 *lun); + int (*lldd_I_T_nexus_reset)(struct domain_device *); + int (*lldd_lu_reset)(struct domain_device *, u8 *lun); + int (*lldd_query_task)(struct sas_task *); + +如需更多信息,请参考 T10.org。 + +端口与适配器管理:: + + /* 端口与适配器管理 */ + int (*lldd_clear_nexus_port)(struct sas_port *); + int (*lldd_clear_nexus_ha)(struct sas_ha_struct *); + +SAS LLDD 至少应实现上述函数中的一个。 + +PHY 管理:: + + /* PHY 管理 */ + int (*lldd_control_phy)(struct sas_phy *, enum phy_func); + +lldd_ha + - 应设置为指向驱动的主机适配器(HA)结构体的指针。如果 sas_ha_struct + 被嵌入到更大的结构体中,也可以通过 container_of() 宏来获取。 + +一个示例的初始化与注册函数可以如下所示:(该函数应在 probe() +函数的最后调用)但必须在使能 PHY 执行 OOB 之前调用:: + + static int register_sas_ha(struct my_sas_ha *my_ha) + { + int i; + static struct sas_phy *sas_phys[MAX_PHYS]; + static struct sas_port *sas_ports[MAX_PHYS]; + + my_ha->sas_ha.sas_addr = &my_ha->sas_addr[0]; + + for (i = 0; i < MAX_PHYS; i++) { + sas_phys[i] = &my_ha->phys[i].sas_phy; + sas_ports[i] = &my_ha->sas_ports[i]; + } + + my_ha->sas_ha.sas_phy = sas_phys; + my_ha->sas_ha.sas_port = sas_ports; + my_ha->sas_ha.num_phys = MAX_PHYS; + + my_ha->sas_ha.lldd_port_formed = my_port_formed; + + my_ha->sas_ha.lldd_dev_found = my_dev_found; + my_ha->sas_ha.lldd_dev_gone = my_dev_gone; + + my_ha->sas_ha.lldd_execute_task = my_execute_task; + + my_ha->sas_ha.lldd_abort_task = my_abort_task; + my_ha->sas_ha.lldd_abort_task_set = my_abort_task_set; + my_ha->sas_ha.lldd_clear_task_set = my_clear_task_set; + my_ha->sas_ha.lldd_I_T_nexus_reset= NULL; (2) + my_ha->sas_ha.lldd_lu_reset = my_lu_reset; + my_ha->sas_ha.lldd_query_task = my_query_task; + + my_ha->sas_ha.lldd_clear_nexus_port = my_clear_nexus_port; + my_ha->sas_ha.lldd_clear_nexus_ha = my_clear_nexus_ha; + + my_ha->sas_ha.lldd_control_phy = my_control_phy; + + return sas_register_ha(&my_ha->sas_ha); + } + +(2) SAS 1.1 未定义 I_T Nexus Reset TMF(任务管理功能)。 + +事件 +==== + +事件是 SAS LLDD 唯一的通知 SAS 层发生任何情况的方式。 +LLDD 没有其他方法可以告知 SAS 层其内部或 SAS 域中发生的事件。 + +Phy 事件:: + + PHYE_LOSS_OF_SIGNAL, (C) + PHYE_OOB_DONE, + PHYE_OOB_ERROR, (C) + PHYE_SPINUP_HOLD. + +端口事件,通过 _phy_ 传递:: + + PORTE_BYTES_DMAED, (M) + PORTE_BROADCAST_RCVD, (E) + PORTE_LINK_RESET_ERR, (C) + PORTE_TIMER_EVENT, (C) + PORTE_HARD_RESET. + +主机适配器事件: + HAE_RESET + +SAS LLDD 应能够生成以下事件:: + + - 来自 C 组的至少一个事件(可选), + - 标记为 M(必需)的事件为必需事件(至少一种); + - 若希望 SAS 层处理域重新验证(domain revalidation),则 + 应生成标记为 E(扩展器)的事件(仅需一种); + - 未标记的事件为可选事件。 + +含义 + +HAE_RESET + - 当 HA 发生内部错误并被复位时。 + +PORTE_BYTES_DMAED + - 在接收到 IDENTIFY/FIS 帧时。 + +PORTE_BROADCAST_RCVD + - 在接收到一个原语时。 + +PORTE_LINK_RESET_ERR + - 定时器超时、信号丢失、丢失 DWS 等情况。 [1]_ + +PORTE_TIMER_EVENT + - DWS 复位超时定时器到期时。[1]_ + +PORTE_HARD_RESET + - 收到 Hard Reset 原语。 + +PHYE_LOSS_OF_SIGNAL + - 设备已断开连接。 [1]_ + +PHYE_OOB_DONE + - OOB 过程成功完成,oob_mode 有效。 + +PHYE_OOB_ERROR + - 执行 OOB 过程中出现错误,设备可能已断开。 [1]_ + +PHYE_SPINUP_HOLD + - 检测到 SATA 设备,但未发送 COMWAKE 信号。 + +.. [1] 应设置或清除 phy 中相应的字段,或者从 tasklet 中调用 + 内联函数 sas_phy_disconnected(),该函数只是一个辅助函数。 + +执行命令 SCSI RPC:: + + int (*lldd_execute_task)(struct sas_task *, gfp_t gfp_flags); + +用于将任务排队提交给 SAS LLDD,@task 为要执行的任务,@gfp_mask +为定义调用者上下文的 gfp 掩码。 + +此函数应实现 执行 SCSI RPC 命令。 + +也就是说,当调用 lldd_execute_task() 时,命令应当立即在传输 +层发出。SAS LLDD 中在任何层级上都不应再进行队列排放。 + +返回值:: + + * 返回 -SAS_QUEUE_FULL 或 -ENOMEM 表示未排入队列; + * 返回 0 表示任务已成功排入队列。 + +:: + + struct sas_task { + dev —— 此任务目标设备; + task_proto —— 协议类型,为 enum sas_proto 中的一种; + scatter —— 指向散布/聚集(SG)列表数组的指针; + num_scatter —— SG 列表元素数量; + total_xfer_len —— 预计传输的总字节数; + data_dir —— 数据传输方向(PCI_DMA_*); + task_done —— 任务执行完成时的回调函数。 + }; + +发现 +==== + +sysfs 树有以下用途:: + + a) 它显示当前时刻 SAS 域的物理布局,即展示当前物理世界中 + 域的实际结构。 + b) 显示某些设备的参数。 _at_discovery_time_. + +下面是一个指向 tree(1) 程序的链接,该工具在查看 SAS 域时非常 +有用: +ftp://mama.indstate.edu/linux/tree/ + +我期望用户空间的应用程序最终能够为此创建一个图形界面。 + +也就是说,sysfs 域树不会显示或保存某些状态变化,例如,如果你更 +改了 READY LED 含义的设置,sysfs 树不会反映这种状态变化;但它 +确实会显示域设备的当前连接状态。 + +维护内部设备状态变化的职责由上层(命令集驱动)和用户空间负责。 + +当某个设备或多个设备从域中拔出时,这一变化会立即反映在 sysfs +树中,并且这些设备会从系统中移除。 + +结构体 domain_device 描述了 SAS 域中的任意设备。它完全由 SAS +层管理。一个任务会指向某个域设备,SAS LLDD 就是通过这种方式知 +道任务应发送到何处。SAS LLDD 只读取 domain_device 结构的内容, +但不会创建或销毁它。 + +用户空间中的扩展器管理 +====================== + +在 sysfs 中的每个扩展器目录下,都有一个名为 "smp_portal" 的 +文件。这是一个二进制的 sysfs 属性文件,它实现了一个 SMP 入口 +(注意:这并不是一个 SMP 端口),用户空间程序可以通过它发送 +SMP 请求并接收 SMP 响应。 + +该功能的实现方式看起来非常简单: + +1. 构建要发送的 SMP 帧。其格式和布局在 SAS 规范中有说明。保持 + CRC 字段为 0。 + +open(2) + +2. 以读写模式打开该扩展器的 SMP portal sysfs 文件。 + +write(2) + +3. 将第 1 步中构建的帧写入文件。 + +read(2) + +4. 读取与所构建帧预期返回长度相同的数据量。如果读取的数据量与 + 预期不符,则表示发生了某种错误。 + +close(2) + +整个过程在 "expander_conf.c" 文件中的函数 do_smp_func() +及其调用者中有详细展示。 + +对应的内核实现位于 "sas_expander.c" 文件中。 + +程序 "expander_conf.c" 实现了上述逻辑。它接收一个参数——扩展器 +SMP portal 的 sysfs 文件名,并输出扩展器的信息,包括路由表内容。 + +SMP portal 赋予了你对扩展器的完全控制权,因此请谨慎操作。 diff --git a/Documentation/translations/zh_CN/scsi/link_power_management_policy.rst b/Documentation/translations/zh_CN/scsi/link_power_management_policy.rst new file mode 100644 index 000000000000..f2ab8fdf4aa8 --- /dev/null +++ b/Documentation/translations/zh_CN/scsi/link_power_management_policy.rst @@ -0,0 +1,32 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scsi/link_power_management_policy.rst + +:翻译: + + 郝栋栋 doubled <doubled@leap-io-kernel.com> + +:校译: + + + +================ +链路电源管理策略 +================ + +该参数允许用户设置链路(接口)的电源管理模式。 +共计三类可选项: + +===================== ===================================================== +选项 作用 +===================== ===================================================== +min_power 指示控制器在可能的情况下尽量使链路处于最低功耗。 + 这可能会牺牲一定的性能,因为从低功耗状态恢复时会增加延迟。 + +max_performance 通常,这意味着不进行电源管理。指示 + 控制器优先考虑性能而非电源管理。 + +medium_power 指示控制器在可能的情况下进入较低功耗状态, + 而非最低功耗状态,从而改善min_power模式下的延迟。 +===================== ===================================================== diff --git a/Documentation/translations/zh_CN/scsi/scsi-parameters.rst b/Documentation/translations/zh_CN/scsi/scsi-parameters.rst new file mode 100644 index 000000000000..ace777e070ea --- /dev/null +++ b/Documentation/translations/zh_CN/scsi/scsi-parameters.rst @@ -0,0 +1,118 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scsi/scsi-parameters.rst + +:翻译: + + 郝栋栋 doubled <doubled@leap-io-kernel.com> + +:校译: + + + +============ +SCSI内核参数 +============ + +请查阅Documentation/admin-guide/kernel-parameters.rst以获取 +指定模块参数相关的通用信息。 + +当前文档可能不完全是最新和全面的。命令 ``modinfo -p ${modulename}`` +显示了可加载模块的参数列表。可加载模块被加载到内核中后,也会在 +/sys/module/${modulename}/parameters/ 目录下显示其参数。其 +中某些参数可以通过命令 +``echo -n ${value} > /sys/module/${modulename}/parameters/${parm}`` +在运行时修改。 + +:: + + advansys= [HW,SCSI] + 请查阅 drivers/scsi/advansys.c 文件头部。 + + aha152x= [HW,SCSI] + 请查阅 Documentation/scsi/aha152x.rst。 + + aha1542= [HW,SCSI] + 格式:<portbase>[,<buson>,<busoff>[,<dmaspeed>]] + + aic7xxx= [HW,SCSI] + 请查阅 Documentation/scsi/aic7xxx.rst。 + + aic79xx= [HW,SCSI] + 请查阅 Documentation/scsi/aic79xx.rst。 + + atascsi= [HW,SCSI] + 请查阅 drivers/scsi/atari_scsi.c。 + + BusLogic= [HW,SCSI] + 请查阅 drivers/scsi/BusLogic.c 文件中 + BusLogic_ParseDriverOptions()函数前的注释。 + + gvp11= [HW,SCSI] + + ips= [HW,SCSI] Adaptec / IBM ServeRAID 控制器 + 请查阅 drivers/scsi/ips.c 文件头部。 + + mac5380= [HW,SCSI] + 请查阅 drivers/scsi/mac_scsi.c。 + + scsi_mod.max_luns= + [SCSI] 最大可探测LUN数。 + 取值范围为 1 到 2^32-1。 + + scsi_mod.max_report_luns= + [SCSI] 接收到的最大LUN数。 + 取值范围为 1 到 16384。 + + NCR_D700= [HW,SCSI] + 请查阅 drivers/scsi/NCR_D700.c 文件头部。 + + ncr5380= [HW,SCSI] + 请查阅 Documentation/scsi/g_NCR5380.rst。 + + ncr53c400= [HW,SCSI] + 请查阅 Documentation/scsi/g_NCR5380.rst。 + + ncr53c400a= [HW,SCSI] + 请查阅 Documentation/scsi/g_NCR5380.rst。 + + ncr53c8xx= [HW,SCSI] + + osst= [HW,SCSI] SCSI磁带驱动 + 格式:<buffer_size>,<write_threshold> + 另请查阅 Documentation/scsi/st.rst。 + + scsi_debug_*= [SCSI] + 请查阅 drivers/scsi/scsi_debug.c。 + + scsi_mod.default_dev_flags= + [SCSI] SCSI默认设备标志 + 格式:<integer> + + scsi_mod.dev_flags= + [SCSI] 厂商和型号的黑/白名单条目 + 格式:<vendor>:<model>:<flags> + (flags 为整数值) + + scsi_mod.scsi_logging_level= + [SCSI] 日志级别的位掩码 + 位的定义请查阅 drivers/scsi/scsi_logging.h。 + 此参数也可以通过sysctl对dev.scsi.logging_level + 进行设置(/proc/sys/dev/scsi/logging_level)。 + 此外,S390-tools软件包提供了一个便捷的 + ‘scsi_logging_level’ 脚本,可以从以下地址下载: + https://github.com/ibm-s390-linux/s390-tools/blob/master/scripts/scsi_logging_level + + scsi_mod.scan= [SCSI] sync(默认)在发现SCSI总线过程中 + 同步扫描。async在内核线程中异步扫描,允许系统继续 + 启动流程。none忽略扫描,预期由用户空间完成扫描。 + + sim710= [SCSI,HW] + 请查阅 drivers/scsi/sim710.c 文件头部。 + + st= [HW,SCSI] SCSI磁带参数(缓冲区大小等) + 请查阅 Documentation/scsi/st.rst。 + + wd33c93= [HW,SCSI] + 请查阅 drivers/scsi/wd33c93.c 文件头部。 diff --git a/Documentation/translations/zh_CN/scsi/scsi.rst b/Documentation/translations/zh_CN/scsi/scsi.rst new file mode 100644 index 000000000000..5d6e39c7cbb5 --- /dev/null +++ b/Documentation/translations/zh_CN/scsi/scsi.rst @@ -0,0 +1,48 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scsi/scsi.rst + +:翻译: + + 郝栋栋 doubled <doubled@leap-io-kernel.com> + +:校译: + + + +============== +SCSI子系统文档 +============== + +Linux文档项目(LDP)维护了一份描述Linux内核(lk) 2.4中SCSI +子系统的文档。请参考: +https://www.tldp.org/HOWTO/SCSI-2.4-HOWTO 。LDP提供单页和 +多页的HTML版本,以及PostScript与PDF格式的文档。 + +在SCSI子系统中使用模块的注意事项 +================================ +Linux内核中的SCSI支持可以根据终端用户的需求以不同的方式模块 +化。为了理解你的选择,我们首先需要定义一些术语。 + +scsi-core(也被称为“中间层”)包含SCSI支持的核心。没有他你将 +无法使用任何其他SCSI驱动程序。SCSI核心支持可以是一个模块( +scsi_mod.o),也可以编译进内核。如果SCSI核心是一个模块,那么 +他必须是第一个被加载的SCSI模块,如果你将卸载该模块,那么他必 +须是最后一个被卸载的模块。实际上,modprobe和rmmod命令将确保 +SCSI子系统中模块加载与卸载的正确顺序。 + +一旦SCSI核心存在于内核中(无论是编译进内核还是作为模块加载), +独立的上层驱动和底层驱动可以按照任意顺序加载。磁盘驱动程序 +(sd_mod.o)、光盘驱动程序(sr_mod.o)、磁带驱动程序 [1]_ +(st.o)以及SCSI通用驱动程序(sg.o)代表了上层驱动,用于控制 +相应的各种设备。例如,你可以加载磁带驱动程序来使用磁带驱动器, +然后在不需要该驱动程序时卸载他(并释放相关内存)。 + +底层驱动程序用于支持您所运行硬件平台支持的不同主机卡。这些不同 +的主机卡通常被称为主机总线适配器(HBAs)。例如,aic7xxx.o驱动 +程序被用于控制Adaptec所属的所有最新的SCSI控制器。几乎所有的底 +层驱动都可以被编译为模块或直接编译进内核。 + +.. [1] 磁带驱动程序有一个变种用于控制OnStream磁带设备。其模块 + 名称为osst.o 。 diff --git a/Documentation/translations/zh_CN/scsi/scsi_eh.rst b/Documentation/translations/zh_CN/scsi/scsi_eh.rst new file mode 100644 index 000000000000..26e0f30f0949 --- /dev/null +++ b/Documentation/translations/zh_CN/scsi/scsi_eh.rst @@ -0,0 +1,482 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scsi/scsi_eh.rst + +:翻译: + + 郝栋栋 doubled <doubled@leap-io-kernel.com> + +:校译: + + +=================== +SCSI 中间层错误处理 +=================== + +本文档描述了SCSI中间层(mid layer)的错误处理基础架构。 +关于SCSI中间层的更多信息,请参阅: +Documentation/scsi/scsi_mid_low_api.rst。 + +.. 目录 + + [1] SCSI 命令如何通过中间层传递并进入错误处理(EH) + [1-1] scsi_cmnd(SCSI命令)结构体 + [1-2] scmd(SCSI 命令)是如何完成的? + [1-2-1] 通过scsi_done完成scmd + [1-2-2] 通过超时机制完成scmd + [1-3] 错误处理模块如何接管流程 + [2] SCSI错误处理机制工作原理 + [2-1] 基于细粒度回调的错误处理 + [2-1-1] 概览 + [2-1-2] scmd在错误处理流程中的传递路径 + [2-1-3] 控制流分析 + [2-2] 通过transportt->eh_strategy_handler()实现的错误处理 + [2-2-1] transportt->eh_strategy_handler()调用前的中间层状态 + [2-2-2] transportt->eh_strategy_handler()调用后的中间层状态 + [2-2-3] 注意事项 + + +1. SCSI命令在中间层及错误处理中的传递流程 +========================================= + +1.1 scsi_cmnd结构体 +------------------- + +每个SCSI命令都由struct scsi_cmnd(简称scmd)结构体 +表示。scmd包含两个list_head类型的链表节点:scmd->list +与scmd->eh_entry。其中scmd->list是用于空闲链表或设备 +专属的scmd分配链表,与错误处理讨论关联不大。而 +scmd->eh_entry则是专用于命令完成和错误处理链表,除非 +特别说明,本文讨论中所有scmd的链表操作均通过 +scmd->eh_entry实现。 + + +1.2 scmd是如何完成的? +---------------------- + +底层设备驱动(LLDD)在获取SCSI命令(scmd)后,存在两种 +完成路径:底层驱动可通过调用hostt->queuecommand()时从 +中间层传递的scsi_done回调函数主动完成命令,或者当命令未 +及时完成时由块层(block layer)触发超时处理机制。 + + +1.2.1 通过scsi_done回调完成SCSI命令 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +对于所有非错误处理(EH)命令,scsi_done()是其完成回调 +函数。它只调用blk_mq_complete_request()来删除块层的 +定时器并触发块设备软中断(BLOCK_SOFTIRQ)。 + +BLOCK_SOFTIRQ会间接调用scsi_complete(),进而调用 +scsi_decide_disposition()来决定如何处理该命令。 +scsi_decide_disposition()会查看scmd->result值和感 +应码数据来决定如何处理命令。 + + - SUCCESS + + 调用scsi_finish_command()来处理该命令。该函数会 + 执行一些维护操作,然后调用scsi_io_completion()来 + 完成I/O操作。scsi_io_completion()会通过调用 + blk_end_request及其相关函数来通知块层该请求已完成, + 如果发生错误,还会判断如何处理剩余的数据。 + + - NEEDS_RETRY + + - ADD_TO_MLQUEUE + + scmd被重新加入到块设备队列中。 + + - otherwise + + 调用scsi_eh_scmd_add(scmd)来处理该命令。 + 关于此函数的详细信息,请参见 [1-3]。 + + +1.2.2 scmd超时完成机制 +^^^^^^^^^^^^^^^^^^^^^^ + +SCSI命令超时处理机制由scsi_timeout()函数实现。 +当发生超时事件时,该函数 + + 1. 首先调用可选的hostt->eh_timed_out()回调函数。 + 返回值可能是以下3种情况之一: + + - ``SCSI_EH_RESET_TIMER`` + 表示需要延长命令执行时间并重启计时器。 + + - ``SCSI_EH_NOT_HANDLED`` + 表示eh_timed_out()未处理该命令。 + 此时将执行第2步的处理流程。 + + - ``SCSI_EH_DONE`` + 表示eh_timed_out()已完成该命令。 + + 2. 若未通过回调函数解决,系统将调用 + scsi_abort_command()发起异步中止操作,该操作最多 + 可执行scmd->allowed + 1次。但存在三种例外情况会跳 + 过异步中止而直接进入第3步处理:当检测到 + SCSI_EH_ABORT_SCHEDULED标志位已置位(表明该命令先 + 前已被中止过一次且当前重试仍失败)、当重试次数已达上 + 限、或当错误处理时限已到期时。在这些情况下,系统将跳 + 过异步中止流程而直接执行第3步处理方案。 + + 3. 最终未解决的命令会通过scsi_eh_scmd_add(scmd)移交给 + 错误处理子系统,具体流程详见[1-4]章节说明。 + +1.3 异步命令中止机制 +-------------------- + +当命令超时触发后,系统会通过scsi_abort_command()调度异 +步中止操作。若中止操作执行成功,则根据重试次数决定后续处 +理:若未达最大重试限制,命令将重新下发执行;若重试次数已 +耗尽,则命令最终以DID_TIME_OUT状态终止。当中止操作失败 +时,系统会调用scsi_eh_scmd_add()将该命令移交错误处理子 +系统,具体处理流程详见[1-4]。 + +1.4 错误处理(EH)接管机制 +------------------------ + +SCSI命令通过scsi_eh_scmd_add()函数进入错误处理流程,该函 +数执行以下操作: + + 1. 将scmd->eh_entry链接到shost->eh_cmd_q + + 2. 在shost->shost_state中设置SHOST_RECOVERY状态位 + + 3. 递增shost->host_failed失败计数器 + + 4. 当检测到shost->host_busy == shost->host_failed + 时(即所有进行中命令均已失败)立即唤醒SCSI错误处理 + 线程。 + +如上所述,当任一scmd被加入到shost->eh_cmd_q队列时,系统 +会立即置位shost_state中的SHOST_RECOVERY状态标志位,该操 +作将阻止块层向对应主机控制器下发任何新的SCSI命令。在此状 +态下,主机控制器上所有正在处理的scmd最终会进入以下三种状 +态之一:正常完成、失败后被移入到eh_cmd_q队列、或因超时被 +添加到shost->eh_cmd_q队列。 + +如果所有的SCSI命令都已经完成或失败,系统中正在执行的命令 +数量与失败命令数量相等( +即shost->host_busy == shost->host_failed),此时将唤 +醒SCSI错误处理线程。SCSI错误处理线程一旦被唤醒,就可以确 +保所有未完成命令均已标记为失败状态,并且已经被链接到 +shost->eh_cmd_q队列中。 + +需要特别说明的是,这并不意味着底层处理流程完全静止。当底层 +驱动以错误状态完成某个scmd时,底层驱动及其下层组件会立刻遗 +忘该命令的所有关联状态。但对于超时命令,除非 +hostt->eh_timed_out()回调函数已经明确通知底层驱动丢弃该 +命令(当前所有底层驱动均未实现此功能),否则从底层驱动视角 +看该命令仍处于活跃状态,理论上仍可能在某时刻完成。当然,由 +于超时计时器早已触发,所有此类延迟完成都将被系统直接忽略。 + +我们将在后续章节详细讨论关于SCSI错误处理如何执行中止操作( +即强制底层驱动丢弃已超时SCSI命令)。 + + +2. SCSI错误处理机制详解 +======================= + +SCSI底层驱动可以通过以下两种方式之一来实现SCSI错误处理。 + + - 细粒度的错误处理回调机制 + 底层驱动可选择实现细粒度的错误处理回调函数,由SCSI中间层 + 主导错误恢复流程并自动调用对应的回调函数。此实现模式的详 + 细设计规范在[2-1]节中展开讨论。 + + - eh_strategy_handler()回调函数 + 该回调函数作为统一的错误处理入口,需要完整实现所有的恢复 + 操作。具体而言,它必须涵盖SCSI中间层在常规恢复过程中执行 + 的全部处理流程,相关实现将在[2-2]节中详细描述。 + +当错误恢复流程完成后,SCSI错误处理系统通过调用 +scsi_restart_operations()函数恢复正常运行,该函数按顺序执行 +以下操作: + + 1. 验证是否需要执行驱动器安全门锁定机制 + + 2. 清除shost_state中的SHOST_RECOVERY状态标志位 + + 3. 唤醒所有在shost->host_wait上等待的任务。如果有人调用了 + scsi_block_when_processing_errors()则会发生这种情况。 + (疑问:由于错误处理期间块层队列已被阻塞,为何仍需显式 + 唤醒?) + + 4. 强制激活该主机控制器下所有设备的I/O队列 + + +2.1 基于细粒度回调的错误处理机制 +-------------------------------- + +2.1.1 概述 +^^^^^^^^^^^ + +如果不存在eh_strategy_handler(),SCSI中间层将负责驱动的 +错误处理。错误处理(EH)的目标有两个:一是让底层驱动程序、 +主机和设备不再维护已超时的SCSI命令(scmd);二是使他们准备 +好接收新命令。当一个SCSI命令(scmd)被底层遗忘且底层已准备 +好再次处理或拒绝该命令时,即可认为该scmd已恢复。 + +为实现这些目标,错误处理(EH)会逐步执行严重性递增的恢复 +操作。部分操作通过下发SCSI命令完成,而其他操作则通过调用 +以下细粒度的错误处理回调函数实现。这些回调函数可以省略, +若被省略则默认始终视为执行失败。 + +:: + + int (* eh_abort_handler)(struct scsi_cmnd *); + int (* eh_device_reset_handler)(struct scsi_cmnd *); + int (* eh_bus_reset_handler)(struct scsi_cmnd *); + int (* eh_host_reset_handler)(struct scsi_cmnd *); + +只有在低级别的错误恢复操作无法恢复部分失败的SCSI命令 +(scmd)时,才会采取更高级别的恢复操作。如果最高级别的错误 +处理失败,就意味着整个错误恢复(EH)过程失败,所有未能恢复 +的设备被强制下线。 + +在恢复过程中,需遵循以下规则: + + - 错误恢复操作针对待处理列表eh_work_q中的失败的scmds执 + 行。如果某个恢复操作成功恢复了一个scmd,那么该scmd会 + 从eh_work_q链表中移除。 + + 需要注意的是,对某个scmd执行的单个恢复操作可能会恢复 + 多个scmd。例如,对某个设备执行复位操作可能会恢复该设 + 备上所有失败的scmd。 + + - 仅当低级别的恢复操作完成且eh_work_q仍然非空时,才会 + 触发更高级别的操作 + + - SCSI错误恢复机制会重用失败的scmd来发送恢复命令。对于 + 超时的scmd,SCSI错误处理机制会确保底层驱动在重用scmd + 前已不再维护该命令。 + +当一个SCSI命令(scmd)被成功恢复后,错误处理逻辑会通过 +scsi_eh_finish_cmd()将其从待处理队列(eh_work_q)移 +至错误处理的本地完成队列(eh_done_q)。当所有scmd均恢 +复完成(即eh_work_q为空时),错误处理逻辑会调用 +scsi_eh_flush_done_q()对这些已恢复的scmd进行处理,即 +重新尝试或错误总终止(向上层通知失败)。 + +SCSI命令仅在满足以下全部条件时才会被重试:对应的SCSI设 +备仍处于在线状态,未设置REQ_FAILFAST标志或递增后的 +scmd->retries值仍小于scmd->allowed。 + +2.1.2 SCSI命令在错误处理过程中的流转路径 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + 1. 错误完成/超时 + + :处理: 调用scsi_eh_scmd_add()处理scmd + + - 将scmd添加到shost->eh_cmd_q + - 设置SHOST_RECOVERY标记位 + - shost->host_failed++ + + :锁要求: shost->host_lock + + 2. 启动错误处理(EH) + + :操作: 将所有scmd移动到EH本地eh_work_q队列,并 + 清空 shost->eh_cmd_q。 + + :锁要求: shost->host_lock(非严格必需,仅为保持一致性) + + 3. scmd恢复 + + :操作: 调用scsi_eh_finish_cmd()完成scmd的EH + + - 将scmd从本地eh_work_q队列移至本地eh_done_q队列 + + :锁要求: 无 + + :并发控制: 每个独立的eh_work_q至多一个线程,确保无锁 + 队列的访问 + + 4. EH完成 + + :操作: 调用scsi_eh_flush_done_q()重试scmd或通知上层处理 + 失败。此函数可以被并发调用,但每个独立的eh_work_q队 + 列至多一个线程,以确保无锁队列的访问。 + + - 从eh_done_q队列中移除scmd,清除scmd->eh_entry + - 如果需要重试,调用scsi_queue_insert()重新入队scmd + - 否则,调用scsi_finish_command()完成scmd + - 将shost->host_failed置为零 + + :锁要求: 队列或完成函数会执行适当的加锁操作 + + +2.1.3 控制流 +^^^^^^^^^^^^ + + 通过细粒度回调机制执行的SCSI错误处理(EH)是从 + scsi_unjam_host()函数开始的 + +``scsi_unjam_host`` + + 1. 持有shost->host_lock锁,将shost->eh_cmd_q中的命令移动 + 到本地的eh_work_q队里中,并释放host_lock锁。注意,这一步 + 会清空shost->eh_cmd_q。 + + 2. 调用scsi_eh_get_sense函数。 + + ``scsi_eh_get_sense`` + + 该操作针对没有有效感知数据的错误完成命令。大部分SCSI传输协议 + 或底层驱动在命令失败时会自动获取感知数据(自动感知)。出于性 + 能原因,建议使用自动感知,推荐使用自动感知机制,因为它不仅有 + 助于提升性能,还能避免从发生CHECK CONDITION到执行本操作之间, + 感知信息出现不同步的问题。 + + 注意,如果不支持自动感知,那么在使用scsi_done()以错误状态完成 + scmd 时,scmd->sense_buffer将包含无效感知数据。在这种情况下, + scsi_decide_disposition()总是返回FAILED从而触发SCSI错误处理 + (EH)。当该scmd执行到这里时,会重新获取感知数据,并再次调用 + scsi_decide_disposition()进行处理。 + + 1. 调用scsi_request_sense()发送REQUEST_SENSE命令。如果失败, + 则不采取任何操作。请注意,不采取任何操作会导致对该scmd执行 + 更高级别的恢复操作。 + + 2. 调用scsi_decide_disposition()处理scmd + + - SUCCESS + scmd->retries被设置为scmd->allowed以防止 + scsi_eh_flush_done_q()重试该scmd,并调用 + scsi_eh_finish_cmd()。 + + - NEEDS_RETRY + 调用scsi_eh_finish_cmd() + + - 其他情况 + 无操作。 + + 4. 如果!list_empty(&eh_work_q),则调用scsi_eh_ready_devs()。 + + ``scsi_eh_ready_devs`` + + 该函数采取四种逐步增强的措施,使失败的设备准备好处理新的命令。 + + 1. 调用scsi_eh_stu() + + ``scsi_eh_stu`` + + 对于每个具有有效感知数据且scsi_check_sense()判断为失败的 + scmd发送START STOP UNIT(STU)命令且将start置1。注意,由 + 于我们明确选择错误完成的scmd,可以确定底层驱动已不再维护该 + scmd,我们可以重用它进行STU。 + + 如果STU操作成功且sdev处于离线或就绪状态,所有在sdev上失败的 + scmd都会通过scsi_eh_finish_cmd()完成。 + + *注意* 如果hostt->eh_abort_handler()未实现或返回失败,可能 + 此时仍有超时的scmd,此时STU不会导致底层驱动不再维护scmd。但 + 是,如果STU执行成功,该函数会通过scsi_eh_finish_cmd()来完成 + sdev上的所有scmd,这会导致底层驱动处于不一致的状态。看来STU + 操作应仅在sdev不包含超时scmd时进行。 + + 2. 如果!list_empty(&eh_work_q),调用scsi_eh_bus_device_reset()。 + + ``scsi_eh_bus_device_reset`` + + 此操作与scsi_eh_stu()非常相似,区别在于使用 + hostt->eh_device_reset_handler()替代STU命令。此外,由于我们 + 没有发送SCSI命令且重置会清空该sdev上所有的scmd,所以无需筛选错 + 误完成的scmd。 + + 3. 如果!list_empty(&eh_work_q),调用scsi_eh_bus_reset()。 + + ``scsi_eh_bus_reset`` + + 对于每个包含失败scmd的SCSI通道调用 + hostt->eh_bus_reset_handler()。如果总线重置成功,那么该通道上 + 所有准备就绪或离线状态sdev上的失败scmd都会被处理处理完成。 + + 4. 如果!list_empty(&eh_work_q),调用scsi_eh_host_reset()。 + + ``scsi_eh_host_reset`` + + 调用hostt->eh_host_reset_handler()是最终的手段。如果SCSI主机 + 重置成功,主机上所有就绪或离线sdev上的失败scmd都会通过错误处理 + 完成。 + + 5. 如果!list_empty(&eh_work_q),调用scsi_eh_offline_sdevs()。 + + ``scsi_eh_offline_sdevs`` + + 离线所有包含未恢复scmd的所有sdev,并通过 + scsi_eh_finish_cmd()完成这些scmd。 + + 5. 调用scsi_eh_flush_done_q()。 + + ``scsi_eh_flush_done_q`` + + 此时所有的scmd都已经恢复(或放弃),并通过 + scsi_eh_finish_cmd()函数加入eh_done_q队列。该函数通过 + 重试或显示通知上层scmd的失败来刷新eh_done_q。 + + +2.2 基于transportt->eh_strategy_handler()的错误处理机制 +------------------------------------------------------------- + +在该机制中,transportt->eh_strategy_handler()替代 +scsi_unjam_host()的被调用,并负责整个错误恢复过程。该处理 +函数完成后应该确保底层驱动不再维护任何失败的scmd并且将设备 +设置为就绪(准备接收新命令)或离线状态。此外,该函数还应该 +执行SCSI错误处理的维护任务,以维护SCSI中间层的数据完整性。 +换句话说,eh_strategy_handler()必须实现[2-1-2]中除第1步 +外的所有步骤。 + + +2.2.1 transportt->eh_strategy_handler()调用前的SCSI中间层状态 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + 进入该处理函数时,以下条件成立。 + + - 每个失败的scmd的eh_flags字段已正确设置。 + + - 每个失败的scmd通过scmd->eh_entry链接到scmd->eh_cmd_q队列。 + + - 已设置SHOST_RECOVERY标志。 + + - `shost->host_failed == shost->host_busy`。 + +2.2.2 transportt->eh_strategy_handler()调用后的SCSI中间层状态 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + 从该处理函数退出时,以下条件成立。 + + - shost->host_failed为零。 + + - shost->eh_cmd_q被清空。 + + - 每个scmd->eh_entry被清空。 + + - 对每个scmd必须调用scsi_queue_insert()或scsi_finish_command()。 + 注意,该处理程序可以使用scmd->retries(剩余重试次数)和 + scmd->allowed(允许重试次数)限制重试次数。 + + +2.2.3 注意事项 +^^^^^^^^^^^^^^ + + - 需明确已超时的scmd在底层仍处于活跃状态,因此在操作这些 + scmd前,必须确保底层已彻底不再维护。 + + - 访问或修改shost数据结构时,必须持有shost->host_lock锁 + 以维持数据一致性。 + + - 错误处理完成后,每个故障设备必须彻底清除所有活跃SCSI命 + 令(scmd)的关联状态。 + + - 错误处理完成后,每个故障设备必须被设置为就绪(准备接收 + 新命令)或离线状态。 + + +Tejun Heo +htejun@gmail.com + +11th September 2005 diff --git a/Documentation/translations/zh_CN/scsi/scsi_mid_low_api.rst b/Documentation/translations/zh_CN/scsi/scsi_mid_low_api.rst new file mode 100644 index 000000000000..f701945a1b1c --- /dev/null +++ b/Documentation/translations/zh_CN/scsi/scsi_mid_low_api.rst @@ -0,0 +1,1174 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scsi/scsi_mid_low_api.rst + +:翻译: + + 郝栋栋 doubled <doubled@leap-io-kernel.com> + +:校译: + + + +========================= +SCSI中间层 — 底层驱动接口 +========================= + +简介 +==== +本文档概述了Linux SCSI中间层与SCSI底层驱动之间的接口。底层 +驱动(LLD)通常被称为主机总线适配器(HBA)驱动或主机驱动 +(HD)。在该上下文中,“主机”指的是计算机IO总线(例如:PCI总 +线或ISA总线)与SCSI传输层中单个SCSI启动器端口之间的桥梁。 +“启动器”端口(SCSI术语,参考SAM-3:http://www.t10.org)向 +“目标”SCSI端口(例如:磁盘)发送SCSI命令。在一个运行的系统 +中存在多种底层驱动(LLDs),但每种硬件类型仅对应一种底层驱动 +(LLD)。大多数底层驱动可以控制一个或多个SCSI HBA。部分HBA +内部集成多个主机控制器。 + +在某些情况下,SCSI传输层本身是已存在于Linux中的外部总线子系 +统(例如:USB和ieee1394)。在此类场景下,SCSI子系统的底层驱 +动将作为与其他驱动子系统的软件桥接层。典型示例包括 +usb-storage驱动(位于drivers/usb/storage目录)以 +及ieee1394/sbp2驱动(位于 drivers/ieee1394 目录)。 + +例如,aic7xxx底层驱动负责控制基于Adaptec公司7xxx芯片系列的 +SCSI并行接口(SPI)控制器。aic7xxx底层驱动可以内建到内核中 +或作为模块加载。一个Linux系统中只能运行一个aic7xxx底层驱动 +程序,但他可能控制多个主机总线适配器(HBA)。这些HBA可能位于 +PCI扩展卡或内置于主板中(或两者兼有)。某些基于aic7xxx的HBA +采用双控制器设计,因此会呈现为两个SCSI主机适配器。与大多数现 +代HBA相同,每个aic7xxx控制器都拥有其独立的PCI设备地址。[SCSI +主机与PCI设备之间一一对应虽然常见,但并非强制要求(例如ISA适 +配器就不适用此规则)。] + +SCSI中间层将SCSI底层驱动(LLD)与其他层(例如SCSI上层驱动以 +及块层)隔离开来。 + +本文档的版本大致与Linux内核2.6.8相匹配。 + +文档 +==== +内核源码树中设有专用的SCSI文档目录,通常位于 +Documentation/scsi目录下。大多数文档采用 +reStructuredText格式。本文档名为 +scsi_mid_low_api.rst,可在该目录中找到。该文档的最新版本可 +以访问 https://docs.kernel.org/scsi/scsi_mid_low_api.html +查阅。许多底层驱动(LLD)的文档也位于Documentation/scsi目录 +下(例如aic7xxx.rst)。SCSI中间层的简要说明见scsi.rst文件, +该文档包含指向Linux Kernel 2.4系列SCSI子系统的文档链接。此 +外还收录了两份SCSI上层驱动文档:st.rst(SCSI磁带驱动)与 +scsi-generic.rst(用通用SCSI(sg)驱动)。 + +部分底层驱动的文档(或相关URL)可能嵌在C源代码文件或与其 +源码同位于同一目录下。例如,USB大容量存储驱动的文档链接可以在 +目录/usr/src/linux/drivers/usb/storage下找到。 + +驱动程序结构 +============ +传统上,SCSI子系统的底层驱动(LLD)至少包含drivers/scsi +目录下的两个文件。例如,一个名为“xyz”的驱动会包含一个头文件 +xyz.h和一个源文件xyz.c。[实际上所有代码完全可以合并为单个 +文件,头文件并非必需的。] 部分需要跨操作系统移植的底层驱动会 +采用更复杂的文件结构。例如,aic7xxx驱动,就为通用代码与操作 +系统专用代码(如FreeBSD和Linux)分别创建了独立的文件。此类 +驱动通常会在drivers/scsi目录下拥有自己单独的子目录。 + +当需要向Linux内核添加新的底层驱动(LLD)时,必须留意 +drivers/scsi目录下的两个文件:Makefile以及Kconfig。建议参 +考现有底层驱动的代码组织方式。 + +随着Linux内核2.5开发内核逐步演进为2.6系列的生产版本,该接口 +也引入了一些变化。以驱动初始化代码为例,现有两种模型可用。其 +中旧模型与Linux内核2.4的实现相似,他基于在加载HBA驱动时检测 +到的主机,被称为“被动(passive)”初始化模型。而新的模型允许 +在底层驱动(LLD)的生命周期内动态拔插HBA,这种方式被称为“热 +插拔(hotplug)”初始化模型。推荐使用新的模型,因为他既能处理 +传统的永久连接SCSI设备,也能处理现代支持热插拔的类SCSI设备 +(例如通过USB或IEEE 1394连接的数码相机)。这两种初始化模型将 +在后续的章节中分别讨论。 + +SCSI底层驱动(LLD)通过以下3种方式与SCSI子系统进行交互: + + a) 直接调用由SCSI中间层提供的接口函数 + b) 将一组函数指针传递给中间层提供的注册函数,中间层将在 + 后续运行的某个时刻调用这些函数。这些函数由LLD实现。 + c) 直接访问中间层维护的核心数据结构 + +a)组中所涉及的所有函数,均列于下文“中间层提供的函数”章节中。 + +b)组中涉及的所有函数均列于下文名为“接口函数”的章节中。这些 +函数指针位于结构体struct scsi_host_template中,该结构体实 +例会被传递给scsi_host_alloc()。对于LLD未实现的接口函数,应 +对struct scsi_host_template中的对应成员赋NULL。如果在文件 +作用域定义一个struct scsi_host_template的实例,没有显式初 +始化的函数指针成员将自动设置为NULL。 + +c)组中提到的用法在“热插拔”环境中尤其需要谨慎处理。LLD必须 +明确知晓这些与中间层及其他层级共享的数据结构的生命周期。 + +LLD中定义的所有函数以及在文件作用域内定义的所有数据都应声明 +为static。例如,在一个名为“xxx”的LLD中的sdev_init()函数定 +义如下: +``static int xxx_sdev_init(struct scsi_device * sdev) { /* code */ }`` + +热插拔初始化模型 +================ +在该模型中,底层驱动(LLD)控制SCSI主机适配器在子系统中的注 +册与注销时机。主机最早可以在驱动初始化阶段被注册,最晚可以在 +驱动卸载时被移除。通常,驱动会响应来自sysfs probe()的回调, +表示已检测到一个主机总线适配器(HBA)。在确认该新设备是LLD的 +目标设备后,LLD初始化HBA,并将一个新的SCSI主机适配器注册到 +SCSI中间层。 + +在LLD初始化过程中,驱动应当向其期望发现HBA的IO总线(例如PCI +总线)进行注册。该操作通常可以通过sysfs完成。任何驱动参数( +特别是那些在驱动加载后仍可修改的参数)也可以在此时通过sysfs +注册。当LLD注册其首个HBA时,SCSI中间层首次感受到该LLD的存在。 + +在稍后的某个时间点,当LLD检测到新的HBA时,接下来在LLD与SCSI +中间层之间会发生一系列典型的调用过程。该示例展示了中间层如何 +扫描新引入的HBA,在该过程中发现了3个SCSI设备,其中只有前两个 +设备有响应:: + + HBA探测:假设在扫描中发现2个SCSI设备 + 底层驱动 中间层 底层驱动 + =======---------------======---------------======= + scsi_host_alloc() --> + scsi_add_host() ----> + scsi_scan_host() -------+ + | + sdev_init() + sdev_configure() --> scsi_change_queue_depth() + | + sdev_init() + sdev_configure() + | + sdev_init() *** + sdev_destroy() *** + + + *** 对于SCSI中间层尝试扫描但未响应的SCSI设备,系统调用 + sdev_init()和sdev_destroy()函数对。 + +如果LLD期望调整默认队列设置,可以在其sdev_configure()例程 +中调用scsi_change_queue_depth()。 + +当移除一个HBA时,可能是由于卸载LLD模块相关的有序关闭(例如通 +过rmmod命令),也可能是由于sysfs的remove()回调而触发的“热拔 +插”事件。无论哪种情况,其执行顺序都是相同的:: + + HBA移除:假设连接了2个SCSI设备 + 底层驱动 中间层 底层驱动 + =======---------------------======-----------------======= + scsi_remove_host() ---------+ + | + sdev_destroy() + sdev_destroy() + scsi_host_put() + +LLD用于跟踪struct Scsi_Host的实例可能会非常有用 +(scsi_host_alloc()返回的指针)。这些实例由中间层“拥有”。 +当引用计数为零时,struct Scsi_Host实例会被 +scsi_host_put()释放。 + +HBA的热插拔是一个特殊的场景,特别是当HBA下的磁盘正在处理已挂 +载文件系统上的SCSI命令时。为了应对其中的诸多问题,中间层引入 +了引用计数逻辑。具体内容参考下文关于引用计数的章节。 + +热插拔概念同样适用于SCSI设备。目前,当添加HBA时, +scsi_scan_host() 函数会扫描该HBA所属SCSI传输通道上的设备。在 +新型SCSI传输协议中,HBA可能在扫描完成后才检测到新的SCSI设备。 +LLD可通过以下步骤通知中间层新SCSI设备的存在:: + + SCSI设备热插拔 + 底层驱动 中间层 底层驱动 + =======-------------------======-----------------======= + scsi_add_device() ------+ + | + sdev_init() + sdev_configure() [--> scsi_change_queue_depth()] + +类似的,LLD可能会感知到某个SCSI设备已经被移除(拔出)或与他的连 +接已中断。某些现有的SCSI传输协议(例如SPI)可能直到后续SCSI命令 +执行失败时才会发现设备已经被移除,中间层会将该设备设置为离线状态。 +若LLD检测到SCSI设备已经被移除,可通过以下流程触发上层对该设备的 +移除操作:: + + SCSI设备热拔插 + 底层驱动 中间层 底层驱动 + =======-------------------======-----------------======= + scsi_remove_device() -------+ + | + sdev_destroy() + +对于LLD而言,跟踪struct scsi_device实例可能会非常有用(该结构 +的指针会作为参数传递给sdev_init()和sdev_configure()回调函数)。 +这些实例的所有权归属于中间层(mid-level)。struct scsi_device +实例在sdev_destroy()执行后释放。 + +引用计数 +======== +Scsi_Host结构体已引入引用计数机制。该机制将struct Scsi_Host +实例的所有权分散到使用他的各SCSI层,而此前这类实例完全由中间 +层独占管理。底层驱动(LLD)通常无需直接操作这些引用计数,仅在 +某些特定场景下可能需要介入。 + +与struct Scsi_Host相关的引用计数函数主要有以下3种: + + - scsi_host_alloc(): + 返回指向新实例的指针,该实例的引用计数被设置为1。 + + - scsi_host_get(): + 给定实例的引用计数加1。 + + - scsi_host_put(): + 给定实例的引用计数减1。如果引用计数减少到0,则释放该实例。 + +scsi_device结构体现已引入引用计数机制。该机制将 +struct scsi_device实例的所有权分散到使用他的各SCSI层,而此 +前这类实例完全由中间层独占管理。相关访问函数声明详见 +include/scsi/scsi_device.h文件末尾部分。若LLD需要保留 +scsi_device实例的指针副本,则应调用scsi_device_get()增加其 +引用计数;不再需要该指针时,可通过scsi_device_put()递减引用 +计数(该操作可能会导致该实例被释放)。 + +.. Note:: + + struct Scsi_Host实际上包含两个并行维护的引用计数器,该引 + 用计数由这些函数共同操作。 + +编码规范 +======== + +首先,Linus Torvalds关于C语言编码风格的观点可以在 +Documentation/process/coding-style.rst文件中找到。 + +此外,在相关gcc编译器支持的前提下,鼓励使用大多数C99标准的增强 +特性。因此,在适当的情况下鼓励使用C99风格的结构体和数组初始化 +方式。但不要过度使用,目前对可变长度数组(VLA)的支持还待完善。 +一个例外是 ``//`` 风格的注释;在Linux中倾向于使 +用 ``/*...*/`` 注释格式。 + +对于编写良好、经过充分测试且有完整文档的代码不需要重新格式化 +以符合上述规范。例如,aic7xxx驱动是从FreeBSD和Adaptec代码库 +移植到Linux的。毫无疑问,FreeBSD和Adaptec遵循其原有的编码规 +范。 + + +中间层提供的函数 +================ +这些函数由SCSI中间层提供,供底层驱动(LLD)调用。这些函数的名 +称(即入口点)均已导出,因此作为模块加载的LLD可以访问他们。内 +核会确保在任何LLD初始化之前,SCSI中间层已先行加载并完成初始化。 +下文按字母顺序列出这些函数,其名称均以 ``scsi_`` 开头。 + +摘要: + + - scsi_add_device - 创建新的SCSI逻辑单元(LU)设备实例 + - scsi_add_host - 执行sysfs注册并设置传输类 + - scsi_change_queue_depth - 调整SCSI设备队列深度 + - scsi_bios_ptable - 返回块设备分区表的副本 + - scsi_block_requests - 阻止向指定主机提交新命令 + - scsi_host_alloc - 分配引用计数为1的新SCSI主机适配器实例scsi_host + - scsi_host_get - 增加SCSI主机适配器实例的引用计数 + - scsi_host_put - 减少SCSI主机适配器的引用计数(归零时释放) + - scsi_remove_device - 卸载并移除SCSI设备 + - scsi_remove_host - 卸载并移除主机控制器下的所有SCSI设备 + - scsi_report_bus_reset - 报告检测到的SCSI总线复位事件 + - scsi_scan_host - 执行SCSI总线扫描 + - scsi_track_queue_full - 跟踪连续出现的队列满事件 + - scsi_unblock_requests - 恢复向指定主机提交命令 + +详细信息:: + + /** + * scsi_add_device - 创建新的SCSI逻辑单元(LU)设备实例 + * @shost: 指向SCSI主机适配器实例的指针 + * @channel: 通道号(通常为0) + * @id: 目标ID号 + * @lun: 逻辑单元号(LUN) + * + * 返回指向新的struct scsi_device实例的指针, + * 如果出现异常(例如在给定地址没有设备响应),则返 + * 回ERR_PTR(-ENODEV) + * + * 是否阻塞:是 + * + * 注意事项:本函数通常在添加HBA的SCSI总线扫描过程 + * 中由系统内部调用(即scsi_scan_host()执行期间)。因此, + * 仅应在以下情况调用:HBA在scsi_scan_host()完成扫描后, + * 又检测到新的SCSI设备(逻辑单元)。若成功执行,本次调用 + * 可能会触发LLD的以下回调函数:sdev_init()以及 + * sdev_configure() + * + * 函数定义:drivers/scsi/scsi_scan.c + **/ + struct scsi_device * scsi_add_device(struct Scsi_Host *shost, + unsigned int channel, + unsigned int id, unsigned int lun) + + + /** + * scsi_add_host - 执行sysfs注册并设置传输类 + * @shost: 指向SCSI主机适配器实例的指针 + * @dev: 指向scsi类设备结构体(struct device)的指针 + * + * 成功返回0,失败返回负的errno(例如:-ENOMEM) + * + * 是否阻塞:否 + * + * 注意事项:仅在“热插拔初始化模型”中需要调用,且必须在 + * scsi_host_alloc()成功执行后调用。该函数不会扫描总线; + * 总线扫描可通过调用scsi_scan_host()或其他传输层特定的 + * 方法完成。在调用该函数之前,LLD必须先设置好传输模板, + * 并且只能在调用该函数之后才能访问传输类 + * (transport class)相关的数据结构。 + * + * 函数定义:drivers/scsi/hosts.c + **/ + int scsi_add_host(struct Scsi_Host *shost, struct device * dev) + + + /** + * scsi_change_queue_depth - 调整SCSI设备队列深度 + * @sdev: 指向要更改队列深度的SCSI设备的指针 + * @tags 如果启用了标记队列,则表示允许的标记数, + * 或者在非标记模式下,LLD可以排队的命令 + * 数(如 cmd_per_lun)。 + * + * 无返回 + * + * 是否阻塞:否 + * + * 注意事项:可以在任何时刻调用该函数,只要该SCSI设备受该LLD控 + * 制。[具体来说,可以在sdev_configure()执行期间或之后,且在 + * sdev_destroy()执行之前调用。] 该函数可安全地在中断上下文中 + * 调用。 + * + * 函数定义:drivers/scsi/scsi.c [更多注释请参考源代码] + **/ + int scsi_change_queue_depth(struct scsi_device *sdev, int tags) + + + /** + * scsi_bios_ptable - 返回块设备分区表的副本 + * @dev: 指向块设备的指针 + * + * 返回指向分区表的指针,失败返回NULL + * + * 是否阻塞:是 + * + * 注意事项:调用方负责释放返回的内存(通过 kfree() 释放) + * + * 函数定义:drivers/scsi/scsicam.c + **/ + unsigned char *scsi_bios_ptable(struct block_device *dev) + + + /** + * scsi_block_requests - 阻止向指定主机提交新命令 + * + * @shost: 指向特定主机的指针,用于阻止命令的发送 + * + * 无返回 + * + * 是否阻塞:否 + * + * 注意事项:没有定时器或其他任何机制可以解除阻塞,唯一的方式 + * 是由LLD调用scsi_unblock_requests()方可恢复。 + * + * 函数定义:drivers/scsi/scsi_lib.c + **/ + void scsi_block_requests(struct Scsi_Host * shost) + + + /** + * scsi_host_alloc - 创建SCSI主机适配器实例并执行基础初始化 + * @sht: 指向SCSI主机模板的指针 + * @privsize: 在hostdata数组中分配的额外字节数(该数组是返 + * 回的Scsi_Host实例的最后一个成员) + * + * 返回指向新的Scsi_Host实例的指针,失败返回NULL + * + * 是否阻塞:是 + * + * 注意事项:当此调用返回给LLD时,该主机适配器上的 + * SCSI总线扫描尚未进行。hostdata数组(默认长度为 + * 零)是LLD专属的每主机私有区域,供LLD独占使用。 + * 两个相关的引用计数都被设置为1。完整的注册(位于 + * sysfs)与总线扫描由scsi_add_host()和 + * scsi_scan_host()稍后执行。 + * 函数定义:drivers/scsi/hosts.c + **/ + struct Scsi_Host * scsi_host_alloc(const struct scsi_host_template * sht, + int privsize) + + + /** + * scsi_host_get - 增加SCSI主机适配器实例的引用计数 + * @shost: 指向Scsi_Host实例的指针 + * + * 无返回 + * + * 是否阻塞:目前可能会阻塞,但可能迭代为不阻塞 + * + * 注意事项:会同时增加struct Scsi_Host中两个子对 + * 象的引用计数 + * + * 函数定义:drivers/scsi/hosts.c + **/ + void scsi_host_get(struct Scsi_Host *shost) + + + /** + * scsi_host_put - 减少SCSI主机适配器实例的引用计数 + * (归零时释放) + * @shost: 指向Scsi_Host实例的指针 + * + * 无返回 + * + * 是否阻塞:当前可能会阻塞,但可能会改为不阻塞 + * + * 注意事项:实际会递减两个子对象中的计数。当后一个引用 + * 计数归零时系统会自动释放Scsi_Host实例。 + * LLD 无需关注Scsi_Host实例的具体释放时机,只要在平衡 + * 引用计数使用后不再访问该实例即可。 + * 函数定义:drivers/scsi/hosts.c + **/ + void scsi_host_put(struct Scsi_Host *shost) + + + /** + * scsi_remove_device - 卸载并移除SCSI设备 + * @sdev: 指向SCSI设备实例的指针 + * + * 返回值:成功返回0,若设备未连接,则返回-EINVAL + * + * 是否阻塞:是 + * + * 如果LLD发现某个SCSI设备(逻辑单元,lu)已经被移除, + * 但其主机适配器实例依旧存在,则可以请求移除该SCSI设备。 + * 如果该调用成功将触发sdev_destroy()回调函数的执行。调 + * 用完成后,sdev将变成一个无效的指针。 + * + * 函数定义:drivers/scsi/scsi_sysfs.c + **/ + int scsi_remove_device(struct scsi_device *sdev) + + + /** + * scsi_remove_host - 卸载并移除主机控制器下的所有SCSI设备 + * @shost: 指向SCSI主机适配器实例的指针 + * + * 返回值:成功返回0,失败返回1(例如:LLD正忙??) + * + * 是否阻塞:是 + * + * 注意事项:仅在使用“热插拔初始化模型”时调用。应在调用 + * scsi_host_put()前调用。 + * + * 函数定义:drivers/scsi/hosts.c + **/ + int scsi_remove_host(struct Scsi_Host *shost) + + + /** + * scsi_report_bus_reset - 报告检测到的SCSI总线复位事件 + * @shost: 指向关联的SCSI主机适配器的指针 + * @channel: 发生SCSI总线复位的通道号 + * + * 返回值:无 + * + * 是否阻塞:否 + * + * 注意事项:仅当复位来自未知来源时才需调用此函数。 + * 由SCSI中间层发起的复位无需调用,但调用也不会导 + * 致副作用。此函数的主要作用是确保系统能正确处理 + * CHECK_CONDITION状态。 + * + * 函数定义:drivers/scsi/scsi_error.c + **/ + void scsi_report_bus_reset(struct Scsi_Host * shost, int channel) + + + /** + * scsi_scan_host - 执行SCSI总线扫描 + * @shost: 指向SCSI主机适配器实例的指针 + * + * 是否阻塞:是 + * + * 注意事项:应在调用scsi_add_host()后调用 + * + * 函数定义:drivers/scsi/scsi_scan.c + **/ + void scsi_scan_host(struct Scsi_Host *shost) + + + /** + * scsi_track_queue_full - 跟踪指定设备上连续的QUEUE_FULL + * 事件,以判断是否需要及何时调整 + * 该设备的队列深度。 + * @sdev: 指向SCSI设备实例的指针 + * @depth: 当前该设备上未完成的SCSI命令数量(不包括返回 + * QUEUE_FULL的命令) + * + * 返回值:0 - 当前队列深度无需调整 + * >0 - 需要将队列深度调整为此返回值指定的新深度 + * -1 - 需要回退到非标记操作模式,并使用 + * host->cmd_per_lun作为非标记命令队列的 + * 深度限制 + * + * 是否阻塞:否 + * + * 注意事项:LLD可以在任意时刻调用该函数。系统将自动执行“正确 + * 的处理流程”;该函数支持在中断上下文中安全地调用 + * + * 函数定义:drivers/scsi/scsi.c + **/ + int scsi_track_queue_full(struct scsi_device *sdev, int depth) + + + /** + * scsi_unblock_requests - 恢复向指定主机适配器提交命令 + * + * @shost: 指向要解除阻塞的主机适配器的指针 + * + * 返回值:无 + * + * 是否阻塞:否 + * + * 函数定义:drivers/scsi/scsi_lib.c + **/ + void scsi_unblock_requests(struct Scsi_Host * shost) + + + +接口函数 +======== +接口函数由底层驱动(LLD)定义实现,其函数指针保存在 +struct scsi_host_template实例中,并将该实例传递给 +scsi_host_alloc()。 +部分接口函数为必选实现项。所有 +接口函数都应声明为static,约定俗成的命名规则如下, +驱动“xyz”应将其sdev_configure()函数声明为:: + + static int xyz_sdev_configure(struct scsi_device * sdev); + +其余接口函数的命名规范均依此类推。 + +需将该函数指针赋值给“struct scsi_host_template”实例 +的‘sdev_configure’成员变量中,并将该结构体实例指针传 +递到中间层的scsi_host_alloc()函数。 + +各个接口函数的详细说明可参考include/scsi/scsi_host.h +文件,具体描述位于“struct scsi_host_template”结构体 +各个成员的上方。在某些情况下,scsi_host.h头文件中的描 +述比本文提供的更为详尽。 + +以下按字母顺序列出所有接口函数及其说明。 + +摘要: + + - bios_param - 获取磁盘的磁头/扇区/柱面参数 + - eh_timed_out - SCSI命令超时回调 + - eh_abort_handler - 中止指定的SCSI命令 + - eh_bus_reset_handler - 触发SCSI总线复位 + - eh_device_reset_handler - 执行SCSI设备复位 + - eh_host_reset_handler - 复位主机(主机总线适配器) + - info - 提供指定主机适配器的相关信息 + - ioctl - 驱动可响应ioctl控制命令 + - proc_info - 支持/proc/scsi/{驱动名}/{主机号}文件节点的读写操作 + - queuecommand - 将SCSI命令提交到主机控制器,命令执行完成后调用‘done’回调 + - sdev_init - 在向新设备发送SCSI命令前的初始化 + - sdev_configure - 设备挂载后的精细化微调 + - sdev_destroy - 设备即将被移除前的清理 + + +详细信息:: + + /** + * bios_param - 获取磁盘的磁头/扇区/柱面参数 + * @sdev: 指向SCSI设备实例的指针(定义于 + * include/scsi/scsi_device.h中) + * @bdev: 指向块设备实例的指针(定义于fs.h中) + * @capacity: 设备容量(以512字节扇区为单位) + * @params: 三元数组用于保存输出结果: + * params[0]:磁头数量(最大255) + * params[1]:扇区数量(最大63) + * params[2]:柱面数量 + * + * 返回值:被忽略 + * + * 并发安全声明: 无锁 + * + * 调用上下文说明: 进程上下文(sd) + * + * 注意事项: 若未提供此函数,系统将基于READ CAPACITY + * 使用默认几何参数。params数组已预初始化伪值,防止函 + * 数无输出。 + * + * 可选实现说明:由LLD选择性定义 + **/ + int bios_param(struct scsi_device * sdev, struct block_device *bdev, + sector_t capacity, int params[3]) + + + /** + * eh_timed_out - SCSI命令超时回调 + * @scp: 标识超时的命令 + * + * 返回值: + * + * EH_HANDLED: 我已修复该错误,请继续完成该命令 + * EH_RESET_TIMER: 我需要更多时间,请重置定时器并重新开始计时 + * EH_NOT_HANDLED 开始正常的错误恢复流程 + * + * 并发安全声明: 无锁 + * + * 调用上下文说明: 中断上下文 + * + * 注意事项: 该回调函数为LLD提供一个机会进行本地 + * 错误恢复处理。此处的恢复仅限于判断该未完成的命 + * 令是否还有可能完成。此回调中不允许中止或重新启 + * 动该命令。 + * + * 可选实现说明:由LLD选择性定义 + **/ + int eh_timed_out(struct scsi_cmnd * scp) + + + /** + * eh_abort_handler - 中止指定的SCSI命令 + * @scp: 标识要中止的命令 + * + * 返回值:如果命令成功中止,则返回SUCCESS,否则返回FAILED + * + * 并发安全声明: 无锁 + * + * 调用上下文说明: 内核线程 + * + * 注意事项: 该函数仅在命令超时时才被调用。 + * + * 可选实现说明:由LLD选择性定义 + **/ + int eh_abort_handler(struct scsi_cmnd * scp) + + + /** + * eh_bus_reset_handler - 发起SCSI总线复位 + * @scp: 包含该设备的SCSI总线应进行重置 + * + * 返回值:重置成功返回SUCCESS;否则返回FAILED + * + * 并发安全声明: 无锁 + * + * 调用上下文说明: 内核线程 + * + * 注意事项: 由SCSI错误处理线程(scsi_eh)调用。 + * 在错误处理期间,当前主机适配器的所有IO请求均 + * 被阻塞。 + * + * 可选实现说明:由LLD选择性定义 + **/ + int eh_bus_reset_handler(struct scsi_cmnd * scp) + + + /** + * eh_device_reset_handler - 发起SCSI设备复位 + * @scp: 指定将被重置的SCSI设备 + * + * 返回值:如果命令成功中止返回SUCCESS,否则返回FAILED + * + * 并发安全声明: 无锁 + * + * 调用上下文说明: 内核线程 + * + * 注意事项: 由SCSI错误处理线程(scsi_eh)调用。 + * 在错误处理期间,当前主机适配器的所有IO请求均 + * 被阻塞。 + * + * 可选实现说明:由LLD选择性定义 + **/ + int eh_device_reset_handler(struct scsi_cmnd * scp) + + + /** + * eh_host_reset_handler - 复位主机(主机总线适配器) + * @scp: 管理该设备的SCSI主机适配器应该被重置 + * + * 返回值:如果命令成功中止返回SUCCESS,否则返回FAILED + * + * 并发安全声明: 无锁 + * + * 调用上下文说明: 内核线程 + * + * 注意事项: 由SCSI错误处理线程(scsi_eh)调用。 + * 在错误处理期间,当前主机适配器的所有IO请求均 + * 被阻塞。当使用默认的eh_strategy策略时,如果 + * _abort_、_device_reset_、_bus_reset_和该处 + * 理函数均未定义(或全部返回FAILED),系统强制 + * 该故障设备处于离线状态 + * + * 可选实现说明:由LLD选择性定义 + **/ + int eh_host_reset_handler(struct scsi_cmnd * scp) + + + /** + * info - 提供给定主机适配器的详细信息:驱动程序名称 + * 以及用于区分不同主机适配器的数据结构 + * @shp: 指向目标主机的struct Scsi_Host实例 + * + * 返回值:返回以NULL结尾的ASCII字符串。[驱动 + * 负责管理返回的字符串所在内存并确保其在整个 + * 主机适配器生命周期内有效。] + * + * 并发安全声明: 无锁 + * + * 调用上下文说明: 进程上下文 + * + * 注意事项: 通常提供诸如I/O地址或中断号 + * 等PCI或ISA信息。如果未实现该函数,则 + * 默认使用struct Scsi_Host::name 字段。 + * 返回的字符串应为单行(即不包含换行符)。 + * 通过SCSI_IOCTL_PROBE_HOST ioctl可获 + * 取该函数返回的字符串,如果该函数不可用, + * 则ioctl返回struct Scsi_Host::name中 + * 的字符串。 + + * + * 可选实现说明:由LLD选择性定义 + **/ + const char * info(struct Scsi_Host * shp) + + + /** + * ioctl - 驱动可响应ioctl控制命令 + * @sdp: ioctl操作针对的SCSI设备 + * @cmd: ioctl命令号 + * @arg: 指向用户空间读写数据的指针。由于他指向用 + * 户空间,必须使用适当的内核函数 + * (如 copy_from_user())。按照Unix的风 + * 格,该参数也可以视为unsigned long 类型。 + * + * 返回值:如果出错则返回负的“errno”值。返回0或正值表 + * 示成功,并将返回值传递给用户空间。 + * + * 并发安全声明:无锁 + * + * 调用上下文说明:进程上下文 + * + * 注意事项:SCSI子系统使用“逐层下传 + * (trickle down)”的ioctl模型。 + * 用户层会对上层驱动设备节点 + * (例如/dev/sdc)发起ioctl()调用, + * 如果上层驱动无法识别该命令,则将其 + * 传递给SCSI中间层,若中间层也无法识 + * 别,则再传递给控制该设备的LLD。 + * 根据最新的Unix标准,对于不支持的 + * ioctl()命令,应返回-ENOTTY。 + * + * 可选实现说明:由LLD选择性定义 + **/ + int ioctl(struct scsi_device *sdp, int cmd, void *arg) + + + /** + * proc_info - 支持/proc/scsi/{驱动名}/{主机号}文件节点的读写操作 + * @buffer: 输入或出的缓冲区锚点(writeto1_read0==0表示向buffer写 + * 入,writeto1_read0==1表示由buffer读取) + * @start: 当writeto1_read0==0时,用于指定驱动实际填充的起始位置; + * 当writeto1_read0==1时被忽略。 + * @offset: 当writeto1_read0==0时,表示用户关注的数据在缓冲区中的 + * 偏移。当writeto1_read0==1时忽略。 + * @length: 缓冲区的最大(或实际使用)长度 + * @host_no: 目标SCSI Host的编号(struct Scsi_Host::host_no) + * @writeto1_read0: 1 -> 表示数据从用户空间写入驱动 + * (例如,“echo some_string > /proc/scsi/xyz/2”) + * 0 -> 表示用户从驱动读取数据 + * (例如,“cat /proc/scsi/xyz/2”) + * + * 返回值:当writeto1_read0==1时返回写入长度。否则, + * 返回从offset偏移开始输出到buffer的字符数。 + * + * 并发安全声明:无锁 + * + * 调用上下文说明:进程上下文 + * + * 注意事项:该函数由scsi_proc.c驱动,与proc_fs交互。 + * 当前SCSI子系统可移除对proc_fs的支持,相关配置选。 + * + * 可选实现说明:由LLD选择性定义 + **/ + int proc_info(char * buffer, char ** start, off_t offset, + int length, int host_no, int writeto1_read0) + + + /** + * queuecommand - 将SCSI命令提交到主机控制器,命令执行完成后调用scp->scsi_done回调函数 + * @shost: 指向目标SCSI主机控制器 + * @scp: 指向待处理的SCSI命令 + * + * 返回值:成功返回0。 + * + * 如果发生错误,则返回: + * + * SCSI_MLQUEUE_DEVICE_BUSY表示设备队列满, + * SCSI_MLQUEUE_HOST_BUSY表示整个主机队列满 + * + * 在这两种情况下,中间层将自动重新提交该I/O请求 + * + * - 若返回SCSI_MLQUEUE_DEVICE_BUSY,则仅暂停该 + * 特定设备的命令处理,当该设备的某个命令完成返回 + * 时(或在短暂延迟后如果没有其他未完成命令)将恢 + * 复其处理。其他设备的命令仍正常继续处理。 + * + * - 若返回SCSI_MLQUEUE_HOST_BUSY,将暂停该主机 + * 的所有I/O操作,当任意命令从该主机返回时(或在 + * 短暂延迟后如果没有其他未完成命令)将恢复处理。 + * + * 为了与早期的queuecommand兼容,任何其他返回值 + * 都被视作SCSI_MLQUEUE_HOST_BUSY。 + * + * 对于其他可立即检测到的错误,可通过以下流程处 + * 理:设置scp->result为适当错误值,调用scp->scsi_done + * 回调函数,然后该函数返回0。若该命令未立即执行(LLD + * 正在启动或将要启动该命令),则应将scp->result置0并 + * 返回0。 + * + * 命令所有权说明:若驱动返回0,则表示驱动获得该命令的 + * 所有权, + * 并必须确保最终执行scp->scsi_done回调函数。注意:驱动 + * 可以在返回0之前调用scp->scsi_done,但一旦调用该回 + * 调函数后,就只能返回0。若驱动返回非零值,则禁止在任何时 + * 刻执行该命令的scsi_done回调函数。 + * + * 并发安全声明:在2.6.36及更早的内核版本中,调用该函数时持有 + * struct Scsi_Host::host_lock锁(通过“irqsave”获取中断安全的自旋锁), + * 并且返回时仍需保持该锁;从Linux 2.6.37开始,queuecommand + * 将在无锁状态下被调用。 + * + * 调用上下文说明:在中断(软中断)或进程上下文中 + * + * 注意事项:该函数执行应当非常快速,通常不会等待I/O + * 完成。因此scp->scsi_done回调函数通常会在该函数返 + * 回后的某个时刻被调用(经常直接从中断服务例程中调用)。 + * 某些情况下(如模拟SCSI INQUIRY响应的伪适配器驱动), + * scp->scsi_done回调可能在该函数返回前就被调用。 + * 若scp->scsi_done回调函数未在指定时限内被调用,SCSI中 + * 间层将启动错误处理流程。当调用scp->scsi_done回调函数 + * 时,若“result”字段被设置为CHECK CONDITION, + * 则LLD应执行自动感知并填充 + * struct scsi_cmnd::sense_buffer数组。在中间层将 + * 命令加入LLD队列之前前,scsi_cmnd::sense_buffer数组 + * 会被清零。 + * + * 可选实现说明:LLD必须实现 + **/ + int queuecommand(struct Scsi_Host *shost, struct scsi_cmnd * scp) + + + /** + * sdev_init - 在向新设备发送任何SCSI命令前(即开始扫描 + * 之前)调用该函数 + * @sdp: 指向即将被扫描的新设备的指针 + * + * 返回值:返回0表示正常。返回其他值表示出错, + * 该设备将被忽略。 + * + * 并发安全声明:无锁 + * + * 调用上下文说明:进程上下文 + * + * 注意事项:该函数允许LLD在设备首次扫描前分配所需的资源。 + * 对应的SCSI设备可能尚未真正存在,但SCSI中间层即将对其进 + * 行扫描(例如发送INQUIRY命令等)。如果设备存在,将调用 + * sdev_configure()进行配置;如果设备不存在,则调用 + * sdev_destroy()销毁。更多细节请参考 + * include/scsi/scsi_host.h文件。 + * + * 可选实现说明:由LLD选择性定义 + **/ + int sdev_init(struct scsi_device *sdp) + + + /** + * sdev_configure - 在设备首次完成扫描(即已成功响应INQUIRY + * 命令)之后,LDD可调用该函数对设备进行进一步配置 + * @sdp: 已连接的设备 + * + * 返回值:返回0表示成功。任何其他返回值都被视为错误,此时 + * 设备将被标记为离线。[被标记离线的设备不会调用sdev_destroy(), + * 因此需要LLD主动清理资源。] + * + * 并发安全声明:无锁 + * + * 调用上下文说明:进程上下文 + * + * 注意事项:该接口允许LLD查看设备扫描代码所发出的初始INQUIRY + * 命令的响应,并采取对应操作。具体实现细节请参阅 + * include/scsi/scsi_host.h文件。 + * + * 可选实现说明:由LLD选择性定义 + **/ + int sdev_configure(struct scsi_device *sdp) + + + /** + * sdev_destroy - 当指定设备即将被关闭时调用。此时该设备 + * 上的所有I/O活动均已停止。 + * @sdp: 即将关闭的设备 + * + * 返回值:无 + * + * 并发安全声明:无锁 + * + * 调用上下文说明:进程上下文 + * + * 注意事项:该设备的中间层数据结构仍然存在 + * 但即将被销毁。驱动程序此时应当释放为该设 + * 备分配的所有专属资源。系统将不再向此sdp + * 实例发送任何命令。[但该设备可能在未来被 + * 重新连接,届时将通过新的struct scsi_device + * 实例,并触发后续的sdev_init()和 + * sdev_configure()调用过程。] + * + * 可选实现说明:由LLD选择性定义 + **/ + void sdev_destroy(struct scsi_device *sdp) + + + +数据结构 +======== +struct scsi_host_template +------------------------- +每个LLD对应一个“struct scsi_host_template” +实例 [#]_。该结构体通常被初始化为驱动头文件中的静 +态全局变量,此方式可确保未显式初始化的成员自动置零 +(0或NULL)。关键成员变量说明如下: + + name + - 驱动程序的名称(可以包含空格,请限制在80个字符以内) + + proc_name + - 在“/proc/scsi/<proc_name>/<host_no>” + 和sysfs的“drivers”目录中使用的名称。因此 + “proc_name”应仅包含Unix文件名中可接受 + 的字符。 + + ``(*queuecommand)()`` + - 中间层使用的主要回调函数,用于将SCSI命令 + 提交到LLD。 + + vendor_id + - 该字段是一个唯一标识值,用于确认提供 + Scsi_Host LLD的供应商,最常用于 + 验证供应商特定的消息请求。该值由标识符类型 + 和供应商特定值组成,有效格式描述请参阅 + scsi_netlink.h头文件。 + +该结构体的完整定义及详细注释请参阅 ``include/scsi/scsi_host.h``。 + +.. [#] 在极端情况下,单个驱动需要控制多种不同类型的硬件时,驱动可 + 能包含多个实例,(例如某个LLD驱动同时处理ISA和PCI两种类型 + 的适配卡,并为每种硬件类型维护独立的 + struct scsi_host_template实例)。 + +struct Scsi_Host +---------------- +每个由LLD控制的主机适配器对应一个struct Scsi_Host实例。 +该结构体与struct scsi_host_template具有多个相同成员。 +当创建struct Scsi_Host实例时(通过hosts.c中的 +scsi_host_alloc()函数),这些通用成员会从LLD的 +struct scsi_host_template实例初始化而来。关键成员说明 +如下: + + host_no + - 系统范围内唯一的主机标识号,按升序从0开始分配 + can_queue + - 必须大于0,表示适配器可处理的最大并发命令数,禁 + 止向适配器发送超过此数值的命令数 + this_id + - 主机适配器的SCSI ID(SCSI启动器标识),若未知则 + 设置为-1 + sg_tablesize + - 主机适配器支持的最大散列表(scatter-gather)元素 + 数。设置为SG_ALL或更小的值可避免使用链式SG列表, + 且最小值必须为1 + max_sectors + - 单个SCSI命令中允许的最大扇区数(通常为512字节/ + 扇区)。默认值为0,此时会使用 + SCSI_DEFAULT_MAX_SECTORS(在scsi_host.h中定义), + 当前该值为1024。因此,如果未定义max_sectors,则磁盘的 + 最大传输大小为512KB。注意:这个大小可能不足以支持 + 磁盘固件上传。 + cmd_per_lun + - 主机适配器的设备上,每个LUN可排队的最大命令数。 + 此值可通过LLD调用scsi_change_queue_depth()进行 + 调整。 + hostt + - 指向LLD struct scsi_host_template实例的指针, + 当前struct Scsi_Host实例正是由此模板生成。 + hostt->proc_name + - LLD的名称,sysfs使用的驱动名。 + transportt + - 指向LLD struct scsi_transport_template实例的指 + 针(如果存在)。当前支持FC与SPI传输协议。 + hostdata[0] + - 为LLD在struct Scsi_Host结构体末尾预留的区域,大小由 + scsi_host_alloc()的第二个参数(privsize)决定。 + +scsi_host结构体的完整定义详见include/scsi/scsi_host.h。 + +struct scsi_device +------------------ +通常而言,每个SCSI逻辑单元(Logical Unit)对应一个该结构 +的实例。连接到主机适配器的SCSI设备通过三个要素唯一标识:通 +道号(Channel Number)、目标ID(Target ID)和逻辑单元号 +(LUN)。 +该结构体完整定义于include/scsi/scsi_device.h。 + +struct scsi_cmnd +---------------- +该结构体实例用于在LLD与SCSI中间层之间传递SCSI命令 +及其响应。SCSI中间层会确保:提交到LLD的命令数不超过 +scsi_change_queue_depth()(或struct Scsi_Host::cmd_per_lun) +设定的上限,且每个SCSI设备至少分配一个struct scsi_cmnd实例。 +关键成员说明如下: + + cmnd + - 包含SCSI命令的数组 + cmd_len + - SCSI命令的长度(字节为单位) + sc_data_direction + - 数据的传输方向。请参考 + include/linux/dma-mapping.h中的 + “enum dma_data_direction”。 + result + - LLD在调用“done”之前设置该值。值为0表示命令成功 + 完成(并且所有数据(如果有)已成功在主机与SCSI + 目标设备之间完成传输)。“result”是一个32位无符 + 号整数,可以视为2个相关字节。SCSI状态值位于最 + 低有效位。请参考include/scsi/scsi.h中的 + status_byte()与host_byte()宏以及其相关常量。 + sense_buffer + - 这是一个数组(最大长度为SCSI_SENSE_BUFFERSIZE + 字节),当SCSI状态(“result”的最低有效位)设为 + CHECK_CONDITION(2)时,该数组由LLD填写。若 + CHECK_CONDITION被置位,且sense_buffer[0]的高 + 半字节值为7,则中间层会认为sense_buffer数组 + 包含有效的SCSI感知数据;否则,中间层会发送 + REQUEST_SENSE SCSI命令来获取感知数据。由于命令 + 排队的存在,后一种方式容易出错,因此建议LLD始终 + 支持“自动感知”。 + device + - 指向与该命令关联的scsi_device对象的指针。 + resid_len (通过调用scsi_set_resid() / scsi_get_resid()访问) + - LLD应将此无符号整数设置为请求的传输长度(即 + “request_bufflen”)减去实际传输的字节数。“resid_len” + 默认设置为0,因此如果LLD无法检测到数据欠载(不能报告溢出), + 则可以忽略它。LLD应在调用“done”之前设置 + “resid_len”。 + underflow + - 如果实际传输的字节数小于该字段值,LLD应将 + DID_ERROR << 16赋值给“result”。并非所有 + LLD都实现此项检查,部分LLD仅将错误信息输出 + 到日志,而未真正报告DID_ERROR。更推荐 + 的做法是由LLD实现“resid_len”的支持。 + +建议LLD在从SCSI目标设备进行数据传输时设置“resid_len”字段 +(例如READ操作)。当这些数据传输的感知码是MEDIUM ERROR或 +HARDWARE ERROR(有时也包括RECOVERED ERROR)时设置 +resid_len尤为重要。在这种情况下,如果LLD无法确定接收了多 +少数据,那么最安全的做法是表示没有接收到任何数据。例如: +为了表明没有接收到任何有效数据,LLD可以使用如下辅助函数:: + + scsi_set_resid(SCpnt, scsi_bufflen(SCpnt)); + +其中SCpnt是一个指向scsi_cmnd对象的指针。如果表示仅接收到 +三个512字节的数据块,可以这样设置resid_len:: + + scsi_set_resid(SCpnt, scsi_bufflen(SCpnt) - (3 * 512)); + +scsi_cmnd结构体定义在 include/scsi/scsi_cmnd.h文件中。 + + +锁 +=== +每个struct Scsi_Host实例都有一个名为default_lock +的自旋锁(spin_lock),它在scsi_host_alloc()函数 +中初始化(该函数定义在hosts.c文件中)。在同一个函数 +中,struct Scsi_Host::host_lock指针会被初始化为指 +向default_lock。此后,SCSI中间层执行的加 +锁和解锁操作都会使用host_lock指针。过去,驱动程序可 +以重写host_lock指针,但现在不再允许这样做。 + + +自动感知 +======== +自动感知(Autosense或auto-sense)在SAM-2规范中被定 +义为:当SCSI命令完成状态为CHECK CONDITION时,“自动 +将感知数据(sense data)返回给应用程序客户端”。底层 +驱动(LLD)应当执行自动感知。当LLD检测到 +CHECK CONDITION状态时,可通过以下任一方式完成: + + a) 要求SCSI协议(例如SCSI并行接口(SPI))在此 + 类响应中执行一次额外的数据传输 + + b) 或由LLD主动发起REQUEST SENSE命令获取感知数据 + +无论采用哪种方式,当检测到CHECK CONDITION状态时,中 +间层通过检查结构体scsi_cmnd::sense_buffer[0]的值来 +判断LLD是否已执行自动感知。若该字节的高半字节为7 +(或 0xf),则认为已执行自动感知;若该字节为其他值 +(且此字节在每条命令执行前会被初始化为0),则中间层将 +主动发起REQUEST SENSE命令。 + +在存在命令队列的场景下,保存失败命令感知数据的“nexus” +可能会在等待REQUEST SENSE命令期间变得不同步。因此, +最佳实践是由LLD执行自动感知。 + + +自Linux内核2.4以来的变更 +======================== +io_request_lock已被多个细粒度锁替代。与底层驱动 +(LLD)相关的锁是struct Scsi_Host::host_lock,且每 +个SCSI主机都独立拥有一个该锁。 + +旧的错误处理机制已经被移除。这意味着LLD的接口函数 +abort()与reset()已经被删除。 +struct scsi_host_template::use_new_eh_code标志 +也已经被移除。 + +在Linux内核2.4中,SCSI子系统的配置描述与其他Linux子系 +统的配置描述集中存放在Documentation/Configure.help +文件中。在Linux内核2.6中,SCSI子系统拥有独立的配置文 +件drivers/scsi/Kconfig(体积更小),同时包含配置信息 +与帮助信息。 + +struct SHT已重命名为struct scsi_host_template。 + +新增“热插拔初始化模型”以及许多用于支持该功能的额外函数。 + + +致谢 +==== +以下人员对本文档做出了贡献: + + - Mike Anderson <andmike at us dot ibm dot com> + - James Bottomley <James dot Bottomley at hansenpartnership dot com> + - Patrick Mansfield <patmans at us dot ibm dot com> + - Christoph Hellwig <hch at infradead dot org> + - Doug Ledford <dledford at redhat dot com> + - Andries Brouwer <Andries dot Brouwer at cwi dot nl> + - Randy Dunlap <rdunlap at xenotime dot net> + - Alan Stern <stern at rowland dot harvard dot edu> + + +Douglas Gilbert +dgilbert at interlog dot com + +2004年9月21日 diff --git a/Documentation/translations/zh_CN/scsi/sd-parameters.rst b/Documentation/translations/zh_CN/scsi/sd-parameters.rst new file mode 100644 index 000000000000..b3d0445dc9f3 --- /dev/null +++ b/Documentation/translations/zh_CN/scsi/sd-parameters.rst @@ -0,0 +1,38 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scsi/sd-parameters.rst + +:翻译: + + 郝栋栋 doubled <doubled@leap-io-kernel.com> + +:校译: + + + +============================ +Linux SCSI磁盘驱动(sd)参数 +============================ + +缓存类型(读/写) +------------------ +启用/禁用驱动器读写缓存。 + +=========================== ===== ===== ======= ======= + 缓存类型字符串 WCE RCD 写缓存 读缓存 +=========================== ===== ===== ======= ======= + write through 0 0 关闭 开启 + none 0 1 关闭 关闭 + write back 1 0 开启 开启 + write back, no read (daft) 1 1 开启 关闭 +=========================== ===== ===== ======= ======= + +将缓存类型设置为“write back”并将该设置保存到驱动器:: + + # echo "write back" > cache_type + +如果要修改缓存模式但不使更改持久化,可在缓存类型字符串前 +添加“temporary ”。例如:: + + # echo "temporary write back" > cache_type diff --git a/Documentation/translations/zh_CN/scsi/wd719x.rst b/Documentation/translations/zh_CN/scsi/wd719x.rst new file mode 100644 index 000000000000..79757c42032b --- /dev/null +++ b/Documentation/translations/zh_CN/scsi/wd719x.rst @@ -0,0 +1,35 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/scsi/libsas.rst + +:翻译: + + 张钰杰 Yujie Zhang <yjzhang@leap-io-kernel.com> + +:校译: + +==================================================== +Western Digital WD7193, WD7197 和 WD7296 SCSI 卡驱动 +==================================================== + +这些卡需要加载固件。固件可从 WD 提供下载的 Windows NT 驱动程 +序中提取。地址如下: + +http://support.wdc.com/product/download.asp?groupid=801&sid=27&lang=en + +该文件或网页上都未包含任何许可声明,因此该固件可能无法被收录到 +linux-firmware 项目中。 + +提供的脚本可用于下载并提取固件,生成 wd719x-risc.bin 和 +wd719x-wcs.bin 文件。请将它们放置在 /lib/firmware/ 目录下。 +脚本内容如下: + + #!/bin/sh + wget http://support.wdc.com/download/archive/pciscsi.exe + lha xi pciscsi.exe pci-scsi.exe + lha xi pci-scsi.exe nt/wd7296a.sys + rm pci-scsi.exe + dd if=wd7296a.sys of=wd719x-risc.bin bs=1 skip=5760 count=14336 + dd if=wd7296a.sys of=wd719x-wcs.bin bs=1 skip=20096 count=514 + rm wd7296a.sys diff --git a/Documentation/translations/zh_CN/security/SCTP.rst b/Documentation/translations/zh_CN/security/SCTP.rst new file mode 100644 index 000000000000..f2774b0d66b5 --- /dev/null +++ b/Documentation/translations/zh_CN/security/SCTP.rst @@ -0,0 +1,317 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/security/SCTP.rst + +:翻译: + 赵硕 Shuo Zhao <zhaoshuo@cqsoftware.com.cn> + +==== +SCTP +==== + +SCTP的LSM支持 +============= + +安全钩子 +-------- + +对于安全模块支持,已经实现了三个特定于SCTP的钩子:: + + security_sctp_assoc_request() + security_sctp_bind_connect() + security_sctp_sk_clone() + security_sctp_assoc_established() + +这些钩子的用法在下面的 `SCTP的SELinux支持`_ 一章中描述SELinux的实现。 + + +security_sctp_assoc_request() +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +将关联INIT数据包的 ``@asoc`` 和 ``@chunk->skb`` 传递给安全模块。 +成功时返回 0,失败时返回错误。 +:: + + @asoc - 指向sctp关联结构的指针。 + @skb - 指向包含关联数据包skbuff的指针。 + + +security_sctp_bind_connect() +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +将一个或多个IPv4/IPv6地址传递给安全模块进行基于 ``@optname`` 的验证, +这将导致是绑定还是连接服务,如下面的权限检查表所示。成功时返回 0,失败 +时返回错误。 +:: + + @sk - 指向sock结构的指针。 + @optname - 需要验证的选项名称。 + @address - 一个或多个IPv4 / IPv6地址。 + @addrlen - 地址的总长度。使用sizeof(struct sockaddr_in)或 + sizeof(struct sockaddr_in6)来计算每个ipv4或ipv6地址。 + + ------------------------------------------------------------------ + | BIND 类型检查 | + | @optname | @address contains | + |----------------------------|-----------------------------------| + | SCTP_SOCKOPT_BINDX_ADD | 一个或多个 ipv4 / ipv6 地址 | + | SCTP_PRIMARY_ADDR | 单个 ipv4 or ipv6 地址 | + | SCTP_SET_PEER_PRIMARY_ADDR | 单个 ipv4 or ipv6 地址 | + ------------------------------------------------------------------ + + ------------------------------------------------------------------ + | CONNECT 类型检查 | + | @optname | @address contains | + |----------------------------|-----------------------------------| + | SCTP_SOCKOPT_CONNECTX | 一个或多个 ipv4 / ipv6 地址 | + | SCTP_PARAM_ADD_IP | 一个或多个 ipv4 / ipv6 地址 | + | SCTP_SENDMSG_CONNECT | 单个 ipv4 or ipv6 地址 | + | SCTP_PARAM_SET_PRIMARY | 单个 ipv4 or ipv6 地址 | + ------------------------------------------------------------------ + +条目 ``@optname`` 的摘要如下:: + + SCTP_SOCKOPT_BINDX_ADD - 允许在(可选地)调用 bind(3) 后,关联额外 + 的绑定地址。 + sctp_bindx(3) 用于在套接字上添加一组绑定地址。 + + SCTP_SOCKOPT_CONNECTX - 允许分配多个地址以连接到对端(多宿主)。 + sctp_connectx(3) 使用多个目标地址在SCTP + 套接字上发起连接。 + + SCTP_SENDMSG_CONNECT - 通过sendmsg(2)或sctp_sendmsg(3)在新关联上 + 发起连接。 + + SCTP_PRIMARY_ADDR - 设置本地主地址。 + + SCTP_SET_PEER_PRIMARY_ADDR - 请求远程对端将某个地址设置为其主地址。 + + SCTP_PARAM_ADD_IP - 在启用动态地址重配置时使用。 + SCTP_PARAM_SET_PRIMARY - 如下所述,启用重新配置功能。 + + +为了支持动态地址重新配置,必须在两个端点上启用以下 +参数(或使用适当的 **setsockopt**\(2)):: + + /proc/sys/net/sctp/addip_enable + /proc/sys/net/sctp/addip_noauth_enable + +当相应的 ``@optname`` 存在时,以下的 *_PARAM_* 参数会 +通过ASCONF块发送到对端:: + + @optname ASCONF Parameter + ---------- ------------------ + SCTP_SOCKOPT_BINDX_ADD -> SCTP_PARAM_ADD_IP + SCTP_SET_PEER_PRIMARY_ADDR -> SCTP_PARAM_SET_PRIMARY + + +security_sctp_sk_clone() +~~~~~~~~~~~~~~~~~~~~~~~~ +每当通过 **accept**\(2)创建一个新的套接字(即TCP类型的套接字),或者当 +一个套接字被‘剥离’时如用户空间调用 **sctp_peeloff**\(3),会调用此函数。 +:: + + @asoc - 指向当前sctp关联结构的指针。 + @sk - 指向当前套接字结构的指针。 + @newsk - 指向新的套接字结构的指针。 + + +security_sctp_assoc_established() +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +当收到COOKIE ACK时调用,对于客户端,对端的secid将被保存 +到 ``@asoc->peer_secid`` 中:: + + @asoc - 指向sctp关联结构的指针。 + @skb - 指向COOKIE ACK数据包的skbuff指针。 + + +用于关联建立的安全钩子 +---------------------- + +下图展示了在建立关联时 ``security_sctp_bind_connect()``、 ``security_sctp_assoc_request()`` +和 ``security_sctp_assoc_established()`` 的使用。 +:: + + SCTP 端点 "A" SCTP 端点 "Z" + ============= ============= + sctp_sf_do_prm_asoc() + 关联的设置可以通过connect(2), + sctp_connectx(3),sendmsg(2) + or sctp_sendmsg(3)来发起。 + 这将导致调用security_sctp_bind_connect() + 发起与SCTP对端端点"Z"的关联。 + INIT ---------------------------------------------> + sctp_sf_do_5_1B_init() + 响应一个INIT数据块。 + SCTP对端端点"A"正在请求一个临时关联。 + 如果是首次关联,调用security_sctp_assoc_request() + 来设置对等方标签。 + 如果不是首次关联,检查是否被允许。 + 如果允许,则发送: + <----------------------------------------------- INIT ACK + | + | 否则,生成审计事件并默默丢弃该数据包。 + | + COOKIE ECHO ------------------------------------------> + sctp_sf_do_5_1D_ce() + 响应一个COOKIE ECHO数据块。 + 确认该cookie并创建一个永久关联。 + 调用security_sctp_assoc_request() + 执行与INIT数据块响应相同的操作。 + <------------------------------------------- COOKIE ACK + | | + sctp_sf_do_5_1E_ca | + 调用security_sctp_assoc_established() | + 来设置对方标签 | + | | + | 如果是SCTP_SOCKET_TCP或是剥离的套接 + | 字,会调用 security_sctp_sk_clone() + | 来克隆新的套接字。 + | | + 建立 建立 + | | + ------------------------------------------------------------------ + | 关联建立 | + ------------------------------------------------------------------ + + +SCTP的SELinux支持 +================= + +安全钩子 +-------- + +上面的 `SCTP的LSM支持`_ 章节描述了以下SCTP安全钩子,SELinux的细节 +说明如下:: + + security_sctp_assoc_request() + security_sctp_bind_connect() + security_sctp_sk_clone() + security_sctp_assoc_established() + + +security_sctp_assoc_request() +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +将关联INIT数据包的 ``@asoc`` 和 ``@chunk->skb`` 传递给安全模块。 +成功时返回 0,失败时返回错误。 +:: + + @asoc - 指向sctp关联结构的指针。 + @skb - 指向关联数据包skbuff的指针。 + +安全模块执行以下操作: + 如果这是 ``@asoc->base.sk`` 上的首次关联,则将对端的sid设置 + 为 ``@skb`` 中的值。这将确保只有一个对端sid分配给可能支持多个 + 关联的 ``@asoc->base.sk``。 + + 否则验证 ``@asoc->base.sk peer sid`` 是否与 ``@skb peer sid`` + 匹配,以确定该关联是否应被允许或拒绝。 + + 将sctp的 ``@asoc sid`` 设置为套接字的sid(来自 ``asoc->base.sk``) + 并从 ``@skb peer sid`` 中提取MLS部分。这将在SCTP的TCP类型套接字及 + 剥离连接中使用,因为它们会导致生成一个新的套接字。 + + 如果配置了IP安全选项(CIPSO/CALIPSO),则会在套接字上设置IP选项。 + + +security_sctp_bind_connect() +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +根据 ``@optname`` 检查ipv4/ipv6地址所需的权限,具体如下:: + + ------------------------------------------------------------------ + | BIND 权限检查 | + | @optname | @address contains | + |----------------------------|-----------------------------------| + | SCTP_SOCKOPT_BINDX_ADD | 一个或多个 ipv4 / ipv6 地址 | + | SCTP_PRIMARY_ADDR | 单个 ipv4 or ipv6 地址 | + | SCTP_SET_PEER_PRIMARY_ADDR | 单个 ipv4 or ipv6 地址 | + ------------------------------------------------------------------ + + ------------------------------------------------------------------ + | CONNECT 权限检查 | + | @optname | @address contains | + |----------------------------|-----------------------------------| + | SCTP_SOCKOPT_CONNECTX | 一个或多个 ipv4 / ipv6 地址 | + | SCTP_PARAM_ADD_IP | 一个或多个 ipv4 / ipv6 地址 | + | SCTP_SENDMSG_CONNECT | 单个 ipv4 or ipv6 地址 | + | SCTP_PARAM_SET_PRIMARY | 单个 ipv4 or ipv6 地址 | + ------------------------------------------------------------------ + + +`SCTP的LSM支持`_ 提供了 ``@optname`` 摘要,并且还描述了当启用动态地址重新 +配置时,ASCONF块的处理过程。 + + +security_sctp_sk_clone() +~~~~~~~~~~~~~~~~~~~~~~~~ +每当通过 **accept**\(2)(即TCP类型的套接字)创建一个新的套接字,或者 +当一个套接字被“剥离”如用户空间调用 **sctp_peeloff**\(3)时, +``security_sctp_sk_clone()`` 将会分别将新套接字的sid和对端sid设置为 +``@asoc sid`` 和 ``@asoc peer sid`` 中包含的值。 +:: + + @asoc - 指向当前sctp关联结构的指针。 + @sk - 指向当前sock结构的指针。 + @newsk - 指向新sock结构的指针。 + + +security_sctp_assoc_established() +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +当接收到COOKIE ACK时调用,它将连接的对端sid设置为 ``@skb`` 中的值:: + + @asoc - 指向sctp关联结构的指针。 + @skb - 指向COOKIE ACK包skbuff的指针。 + + +策略声明 +-------- +以下支持SCTP的类和权限在内核中是可用的:: + + class sctp_socket inherits socket { node_bind } + +当启用以下策略功能时:: + + policycap extended_socket_class; + +SELinux对SCTP的支持添加了用于连接特定端口类型 ``name_connect`` 权限 +以及在下面的章节中进行解释的 ``association`` 权限。 + +如果用户空间工具已更新,SCTP将支持如下所示的 ``portcon`` 声明:: + + portcon sctp 1024-1036 system_u:object_r:sctp_ports_t:s0 + + +SCTP对端标签 +------------ +每个SCTP套接字仅分配一个对端标签。这个标签将在建立第一个关联时分配。 +任何后续在该套接字上的关联都会将它们的数据包对端标签与套接字的对端标 +签进行比较,只有在它们不同的情况下 ``association`` 权限才会被验证。 +这是通过检查套接字的对端sid与接收到的数据包中的对端sid来验证的,以决 +定是否允许或拒绝该关联。 + +注: + 1) 如果对端标签未启用,则对端上下文将始终是 ``SECINITSID_UNLABELED`` + (在策略声明中为 ``unlabeled_t`` )。 + + 2) 由于SCTP可以在单个套接字上支持每个端点(多宿主)的多个传输地址,因此 + 可以配置策略和NetLabel为每个端点提供不同的对端标签。由于套接字的对端 + 标签是由第一个关联的传输地址决定的,因此建议所有的对端标签保持一致。 + + 3) 用户空间可以使用 **getpeercon**\(3) 来检索套接字的对端上下文。 + + 4) 虽然这不是SCTP特有的,但在使用NetLabel时要注意,如果标签分配给特定的接 + 口,而该接口‘goes down’,则NetLabel服务会移除该条目。因此,请确保网络启 + 动脚本调用 **netlabelctl**\(8) 来设置所需的标签(详细信息, + 请参阅 **netlabel-config**\(8) 辅助脚本)。 + + 5) NetLabel SCTP对端标签规则应用如下所述标签为“netlabel”的一组帖子: + https://www.paul-moore.com/blog/t. + + 6) CIPSO仅支持IPv4地址: ``socket(AF_INET, ...)`` + CALIPSO仅支持IPv6地址: ``socket(AF_INET6, ...)`` + + 测试CIPSO/CALIPSO时请注意以下事项: + a) 如果SCTP数据包由于无效标签无法送达,CIPSO会发送一个ICMP包。 + b) CALIPSO不会发送ICMP包,只会默默丢弃数据包。 + + 7) RFC 3554不支持IPSEC —— SCTP/IPSEC支持尚未在用户空间实现(**racoon**\(8) + 或 **ipsec_pluto**\(8)),尽管内核支持 SCTP/IPSEC。 diff --git a/Documentation/translations/zh_CN/security/index.rst b/Documentation/translations/zh_CN/security/index.rst index 78d9d4b36dca..d33b107405c7 100644 --- a/Documentation/translations/zh_CN/security/index.rst +++ b/Documentation/translations/zh_CN/security/index.rst @@ -18,7 +18,9 @@ credentials snp-tdx-threat-model lsm + lsm-development sak + SCTP self-protection siphash tpm/index @@ -28,7 +30,5 @@ TODOLIST: * IMA-templates * keys/index -* lsm-development -* SCTP * secrets/index * ipe diff --git a/Documentation/translations/zh_CN/security/ipe.rst b/Documentation/translations/zh_CN/security/ipe.rst new file mode 100644 index 000000000000..55968f0c7ae3 --- /dev/null +++ b/Documentation/translations/zh_CN/security/ipe.rst @@ -0,0 +1,398 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/security/sak.rst + +:翻译: + 赵硕 Shuo Zhao <zhaoshuo@cqsoftware.com.cn> + +完整性策略执行(IPE)-内核文档 +============================== + +.. NOTE:: + + 这是针对开发人员而不是管理员的文档。如果您正在 + 寻找有关IPE使用的文档,请参阅 :doc:`IPE admin + guide </admin-guide/LSM/ipe>`。 + +历史背景 +-------- + +最初促使IPE实施的原因,是需要创建一个锁定式系统。该系统将 +从一开始就具备安全性,并且在可执行代码和系统功能关键的特定 +数据文件上,提供强有力的完整性保障。只有当这些特定数据文件 +符合完整性策略时,它们才可以被读取。系统中还将存在强制访问 +控制机制,因此扩展属性(xattrs)也必须受到保护。这就引出了 +需要选择能够提供完整性保证的机制。当时,有两种主要机制被考 +虑,用以在满足这些要求的前提下保证系统完整性: + + 1. IMA + EVM Signatures + 2. DM-Verity + +这两个选项都经过了仔细考虑,然而在原始的IPE使用场景 +中,最终选择DM-Verity而非IMA+EVM作为完整性机制,主 +要有三个原因: + + 1. 防护额外的攻击途径 + + * 使用IMA+EVM时,如果没有加密解决方案,系统很容易受到 + 离线攻击,特别是针对上述特定数据文件的攻击。 + + 与可执行文件不同,读取操作(如对受保护数据文件的读 + 取操作)无法强制性进行全局完整性验证。这意味着必须 + 有一种选择机制来决定是否应对某个读取操作实施完整性 + 策略。 + + 在当时,这是通过强制访问控制标签来实现的,IMA策略会 + 指定哪些标签需要进行完整性验证,这带来了一个问题: + EVM虽然可以保护标签,但如果攻击者离线修改文件系统, + 那么攻击者就可以清除所有的扩展属性(xattrs)——包括 + 用于确定文件是否应受完整性策略约束的SELinux标签。 + + 使用DM-Verity,由于xattrs被保存为Merkel树的一部分, + 如果对由dm-verity保护的文件系统进行了离线挂载,校验 + 和将不在匹配,文件将无法读取。 + + * 由于用户空间的二进制文件在Linux中是分页加载的,dm- + verity同样提供了对抗恶意块设备的额外保护。在这样的 + 攻击中,块设备最初报告适当的内容以供IMA哈希计算,通 + 过所需的完整性检查。然后,在访问真实数据时发生的页面 + 错误将报告攻击者的有效载荷。由于dm-verity会在页面错 + 误发生时检查数据(以及磁盘访问),因此这种攻击得到了 + 缓解。 + + 2. 性能: + + * dm-verity在块被读取时按需提供完整性验证,而不需要将整 + 个文件读入内存进行验证。 + + 3. 签名的简化性: + + * 不需要两个签名(IMA 然后是 EVM):一个签名可以覆盖整个 + 块设备。 + * 签名可以存储在文件系统元数据之外。 + * 该签名支持基于 x.509 的签名基础设施。 + +下一步是选择一个策略来执行完整性验证机制,该策略的最低 +要求是: + + 1. 策略本身必须经过完整性验证(防止针对它的简单攻击)。 + 2. 策略本身必须抵抗回滚攻击。 + 3. 策略执行必须具有类似宽松模式的功能。 + 4. 策略必须能够在不重启的情况下,完整地进行更新。 + 5. 策略更新必须是原子性的。 + 6. 策略必须支持撤销先前创建的组件。 + 7. 策略必须在任何时间点都能进行审计。 + +当时,IMA作为唯一的完整性策略机制,被用来与这些要求进行对比, +但未能满足所有最低要求。尽管考虑过扩展IMA以涵盖这些要求,但 +最终因两个原因被放弃: + + 1. 回归风险;这其中许多变更将导致对已经存在于内核的IMA进行 + 重大代码更改,因此可能会影响用户。 + + 2. IMA在该系统中用于测量和证明;将测量策略与本地完整性策略 + 的执行分离被认为是有利的。 + +由于这些原因,决定创建一个新的LSM,其职责是仅限于本地完整性 +策略的执行。 + +职责和范围 +---------- + +IPE顾名思义,本质上是一种完整性策略执行解决方案;IPE并不强制规定 +如何提供完整性保障,而是将这一决策权留给系统管理员,管理员根据自身 +需求,选择符合的机制来设定安全标准。存在几种不同的完整性解决方案, +它们提供了不同程度的安全保障;而IPE允许系统管理员理论上为所有这些 +解决方案制定策略。 + +IPE自身没有内置确保完整性的固有机制。相反,在构建具备完整性保障能力 +的系统时,存在更高效的分层方案可供使用。需要重点注意的是,用于证明完 +整性的机制,与用于执行完整性声明的策略是相互独立的。 + +因此,IPE依据以下方面进行设计: + + 1. 便于与完整性提供机制集成。 + 2. 便于平台管理员/系统管理员使用。 + +设计理由: +--------- + +IPE是在评估其他操作系统和环境中的现有完整性策略解决方案后设计的。 +在对其他实现的调查中,发现了一些缺陷: + + 1. 策略不易为人们读取,通常需要二进制中间格式。 + 2. 默认情况下会隐式采取单一的、不可定制的操作。 + 3. 调试策略需要手动来确定违反了哪个规则。 + 4. 编写策略需要对更大系统或操作系统有深入的了解。 + +IPE尝试避免所有这些缺陷。 + +策略 +~~~~ + +纯文本 +^^^^^^ + +IPE的策略是纯文本格式的。相较于其他Linux安全模块(LSM), +策略文件体积略大,但能解决其他平台上部分完整性策略方案存在 +的两个核心问题。 + +第一个问题是代码维护和冗余的问题。为了编写策略,策略必须是 +以某种形式的字符串形式呈现(无论是 XML、JSON、YAML 等结构化 +格式,还是其他形式),以便策略编写者能够理解所写内容。在假设 +的二进制策略设计中,需要一个序列化器将策略将可读的形式转换为 +二进制形式,同时还需要一个反序列化器来将二进制形式转换为内核 +中的数据结构。 + +最终,还需要另一个反序列化器将是必要的,用于将二进制形式转换 +为人类可读的形式,并尽可能保存所有信息,这是因为使用此访问控 +制系统的用户必须维护一个校验表和原始文件,才能理解哪些策略已 +经部署在该系统上,哪些没有。对于单个用户来说,这可能没问题, +因为旧的策略可以在更新生效后很快被丢弃。但对于管理成千上万、 +甚至数十万台计算机的用户,且这些计算机有不同的操作系统和不同 +的操作需求,这很快就成了一个问题,因为数年前的过时策略可能仍然 +存在,从而导致需要快速恢复策略或投资大量基础设施来跟踪每个策略 +的内容。 + +有了这三个独立的序列化器/反序列化器,维护成本非常昂贵。如果策略 +避免使用二进制格式,则只需要一个序列化器;将人类可读的形式转换 +为内核中的数据结构。从而节省了代码维护成本,并保持了可操作性。 + +第二个关于二进制格式的问题是透明性,由于IPE根据系统资源的可信度 +来控制访问,因此其策略也必须可信,以便可以被更改。这是通过签名来 +完成的,这就需要签名过程。签名过程通常具有很高的安全标准,因为 +任何被签名的内容都可以被用来攻击完整性执行系统。签署时,签署者 +必须知道他们在签署什么,二进制策略可能会导致这一点的模糊化;签署 +者看到的只是一个不透明的二进制数据块。另一方面,对于纯文本策略中, +签署者看到的则是实际提交的策略。 + +启动策略 +~~~~~~~~ + +如果配置得当,IPE能够在内核启动并进入用户模式时立即执行策略。 +这意味着需要在用户模式开始的那一刻就存储一定的策略。通常,这种 +存储可以通过一下三种方式之一来处理: + + 1. 策略文件存储在磁盘上,内核在进入可能需要做出执行决策的代码 + 路径之前,先加载该策略。 + 2. 策略文件由引导加载程序传递给内核,内核解析这些策略。 + 3. 将一个策略文件编译到内核中,内核在初始化过程中对其进行解析并 + 执行。 + +第一种方式存在问题:内核从用户空间读取文件通常是不推荐的,并且在 +内核中极为罕见。 + +第二种选项同样存在问题:Linux在其整个生态系统中支持多种引导加载程序, +所有引导加载程序都必须支持这种新方法,或者需要有一个独立的来源,这 +可能会导致内核启动过程发生不必要的重大变化。 + +第三种选项是最佳选择,但需要注意的是,编译进内核的策略会占用磁盘空间。 +重要的是要使这一策略足够通用,以便用户空间能够加载新的、更复杂的策略, +同时也要足够严格,以防止过度授权并避免引发安全问题。 + +initramfs提供了一种建立此启动路径的方法。内核启动时以最小化的策略启动, +该策略仅信任initramfs。在initramfs内,当真实的根文件系统已挂载且尚未 +切换时,它会部署并激活一个信任新根文件系统的策略。这种方法防止了在任何 +步骤中出现过度授权,并保持内核策略的最小化。 + +启动 +^^^^ + +然而,并不是每个系统都以initramfs启动,因此编译进内核的启动策略需要具备 +一定的灵活性,以明确如何为启动的下一个阶段建立信任。为此,如果我们将编译 +进内核的策略设计为一个完整的IPE策略,这样系统构建者便能合理定义第一阶段启 +动的需求。 + +可更新、无需重启的策略 +~~~~~~~~~~~~~~~~~~~~~~ + +随着时间的推移,系统需求发生变化(例如,之前信任的应用程序中发现漏洞、秘钥 +轮换等)。更新内核以满足这些安全目标并非始终是一个合适的选择,因为内核更新并 +非完全无风险的,而搁置安全更新会使系统处于脆弱状态。这意味着IPE需要一个可以 +完全更新的策略(允许撤销现有的策略),并且这个更新来源必须是内核外部的(允许 +再不更新内核的情况下更新策略)。 + +此外,由于内核在调用之间是无状态的,并且从内核空间读取磁盘上的策略文件不是一 +个好主意,因此策略更新必须能够在不重启的情况下完成。 + +为了允许从外部来源进行更新,考虑到外部来源可能是恶意的,因此该策略需要具备可被 +识别为可信的机制。这一机制通过签名链实现:策略的签名需与内核中的某个信任源相 +关联。通常,这个信任源是 ``SYSTEM_TRUSTED_KEYRING`` ,这是一个在内核编译时就被 +初始化填充的密钥环,因为这符合上述编译进来策略的制作者与能够部署策略更新的实体 +相同的预期。 + +防回滚 / 防重放 +~~~~~~~~~~~~~~~ + +随着时间的推移,系统可能会发现漏洞,曾经受信任的资源可能不再可信,IPE的 +策略也不例外。可能会出现的情况是,策略制作者误部署了一个不安全的策略, +随后再用一个安全的策略进行修正。 + +假设一旦不安全的策略被部署,攻击者获取了这个不安全的策略,IPE需要有一种 +方式来防止从安全的策略更新回滚到不安全的策略。 + +最初,IPE的策略可以包含一个policy_version字段,声明系统上所有可激活策略 +所需的最低版本号。这将在系统运行期间防止回滚。 + +.. WARNING:: + + 然而,由于内核每次启动都是无状态的,因此该策略版本将在下次 + 启动时被重置为0.0.0。系统构建者需要意识到这一点,并确保在启 + 动后尽快部署新的安全策略,以确保攻击者部署不安全的策略的几 + 率最小化。 + +隐式操作: +~~~~~~~~~ + +隐式操作的问题只有在考虑系统中多个操作具有不同级别时才会显现出来。 +例如,考虑一个系统,该系统对可执行代码和系统中对其功能至关重要的 +特定数据提供强大的完整性保障。在这个系统中,可能存在三种类型的 +策略: + + 1. 一种策略,在这种策略中,如果操作未能匹配到任何规则,则该操 + 作将被拒绝。 + 2. 一种策略,在这种策略中,如果操作未能匹配到任何规则,则该操 + 作将被允许。 + 3. 一种策略,在这种策略中,如果操作未能匹配到任何规则,则执行 + 操作由策略作者指定。 + +第一种类型的策略示例如下:: + + op=EXECUTE integrity_verified=YES action=ALLOW + +在示例系统中,这对于可执行文件来说效果很好,因为所有可执行文件 +都应该拥有完整性保障。但问题出现在第二个要求上,即关于特定数据 +文件的要求。这将导致如下策略(假设策略按行依次执行):: + + op=EXECUTE integrity_verified=YES action=ALLOW + + op=READ integrity_verified=NO label=critical_t action=DENY + op=READ action=ALLOW + +若阅读过文档,了解策略按顺序执行且默认动作是拒绝,那么这个策略的 +逻辑还算清晰;但最后一行规则实际上将读取操作的默认动作改成了允许。 +这种设计是必要的,因为在实际系统中,存在一些无需验证的读取操作(例 +如向日志文件追加内容时的读取操作)。 + +第二种策略类型(未匹配任何规则时默认允许)在管控特定数据文件时逻辑 +更清晰,其策略可简化为:: + + op=READ integrity_verified=NO label=critical_t action=DENY + +但与第一种策略类似,这种默认允许的策略在管控执行操作时会存在缺陷, +因此仍需显式覆盖默认动作:: + + op=EXECUTE integrity_verified=YES action=ALLOW + op=EXECUTE action=DENY + + op=READ integrity_verified=NO label=critical_t action=DENY + +这就引出了第三种策略类型(自定义默认动作)。该类型无需让用户绞尽脑汁 +通过空规则覆盖默认动作,而是强制用户根据自身场景思考合适的默认动作是 +什么,并显式声明:: + + DEFAULT op=EXECUTE action=DENY + op=EXECUTE integrity_verified=YES action=ALLOW + + DEFAULT op=READ action=ALLOW + op=READ integrity_verified=NO label=critical_t action=DENY + +策略调试: +~~~~~~~~~ + +在开发策略时,知道策略违反了哪一行有助于减少调试成本;可以 +将调查的范围缩小到导致该行为的确切行。有些完整性策略系统并 +不提供这一信息,而是提供评估过程中使用的信息。这随后需要将 +这些信息和策略进行关联,以分析哪里了问题。 + +相反,IPE只会输出匹配到的规则。这将调查范围限制到确切到策略行 +(在特定规则的情况下)或部分(在DEFAULT规则的情况下)。当在 +评估策略时观察到策略失败时,这可以减少迭代和调查的时间。 + +IPE的策略引擎还被设计成让人类容易理解如何调查策略失败。每一 +行都会按编写顺序进行评估,因此算法非常简单,便于人类重现步 +骤并找出可能导致失败的原因。而在调查其他的系统中,加载策略 +时会进行优化(例如对规则排序)。在这些系统中,调试需要多个 +步骤,而且没有先阅读代码的情况下,终端用户可能无法完全理解 +该算法的原理。 + +简化策略: +~~~~~~~~~ + +最后,IPE的策略是为系统管理员设计的,而不是内核开发人员。 +IPE不涉及单独的LSM钩子(或系统调用),而是涵盖操作。这 +意味着,系统管理员不需要知道像 ``mmap`` 、 ``mprotect`` 、 +``execve`` 和 ``uselib`` 这些系统调用必须有规则进行保护, +而只需要知道他们想要限制代码执行。这减少了由于缺乏对底层 +系统的了解而可能导致的绕过情况;而IPE的维护者作为内核开发 +人员,可以做出正确的选择,确定某些操作是否与这些操作匹配, +以及在什么条件下匹配。 + +实现说明 +-------- + +匿名内存 +~~~~~~~~ + +在IPE中,匿名内存的处理方式与其他任何类型的访问没有区别。当匿 +名内存使用 ``+X`` 映射时,它仍然会进入 ``file_mmp`` 或 +``file_mprotect`` 钩子,但此时会带有一个 ``NULL`` 文件对象 +这会像其他文件一样提交进行评估。然而,所有当前的信任属性都会 +评估为假,因为它们都是基于文件的,而此次操作并不与任何文件相关联。 + +.. WARNING:: + + 这也适用于 ``kernel_load_data`` 钩子,当内核从一个没有文件 + 支持的用户空间缓冲区加载数据时。在这种情况下,所有当前的信任 + 属性也将评估为false。 + +Securityfs接口 +~~~~~~~~~~~~~~ + +每个策略的对应的securityfs树是有些独特的。例如,对于一个标准的 +securityfs策略树:: + + MyPolicy + |- active + |- delete + |- name + |- pkcs7 + |- policy + |- update + |- version + +策略存储在MyPolicy对应节点的 ``->i_private`` 数据中。 + +测试 +---- + +IPE为策略解析器提供了KUnit测试。推荐kunitconfig:: + + CONFIG_KUNIT=y + CONFIG_SECURITY=y + CONFIG_SECURITYFS=y + CONFIG_PKCS7_MESSAGE_PARSER=y + CONFIG_SYSTEM_DATA_VERIFICATION=y + CONFIG_FS_VERITY=y + CONFIG_FS_VERITY_BUILTIN_SIGNATURES=y + CONFIG_BLOCK=y + CONFIG_MD=y + CONFIG_BLK_DEV_DM=y + CONFIG_DM_VERITY=y + CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG=y + CONFIG_NET=y + CONFIG_AUDIT=y + CONFIG_AUDITSYSCALL=y + CONFIG_BLK_DEV_INITRD=y + + CONFIG_SECURITY_IPE=y + CONFIG_IPE_PROP_DM_VERITY=y + CONFIG_IPE_PROP_DM_VERITY_SIGNATURE=y + CONFIG_IPE_PROP_FS_VERITY=y + CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG=y + CONFIG_SECURITY_IPE_KUNIT_TEST=y + +此外,IPE 具有一个基于 Python 的集成 +`测试套件 <https://github.com/microsoft/ipe/tree/test-suite>`_ +可以测试用户界面和强制执行功能。 diff --git a/Documentation/translations/zh_CN/security/lsm-development.rst b/Documentation/translations/zh_CN/security/lsm-development.rst new file mode 100644 index 000000000000..7ed3719a9d07 --- /dev/null +++ b/Documentation/translations/zh_CN/security/lsm-development.rst @@ -0,0 +1,19 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/security/lsm-development.rst + +:翻译: + 赵硕 Shuo Zhao <zhaoshuo@cqsoftware.com.cn> + +================= +Linux安全模块开发 +================= + +基于https://lore.kernel.org/r/20071026073721.618b4778@laptopd505.fenrus.org, +当一种新的LSM的意图(它试图防范什么,以及在哪些情况下人们会期望使用它)在 +``Documentation/admin-guide/LSM/`` 中适当记录下来后,就会被接受进入内核。 +这使得LSM的代码可以很轻松的与其目标进行对比,从而让最终用户和发行版可以更 +明智地决定那些LSM适合他们的需求。 + +有关可用的 LSM 钩子接口的详细文档,请参阅 ``security/security.c`` 及相关结构。 diff --git a/Documentation/translations/zh_CN/security/secrets/coco.rst b/Documentation/translations/zh_CN/security/secrets/coco.rst new file mode 100644 index 000000000000..a27bc1acdb7c --- /dev/null +++ b/Documentation/translations/zh_CN/security/secrets/coco.rst @@ -0,0 +1,96 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../../disclaimer-zh_CN.rst + +:Original: Documentation/security/secrets/coco.rst + +:翻译: + + 赵硕 Shuo Zhao <zhaoshuo@cqsoftware.com.cn> + +============ +机密计算密钥 +============ + +本文档介绍了在EFI驱动程序和efi_secret内核模块中,机密计算密钥从固件 +到操作系统的注入处理流程。 + +简介 +==== + +机密计算硬件(如AMD SEV,Secure Encrypted Virtualization)允许虚拟机 +所有者将密钥注入虚拟机(VM)内存,且主机/虚拟机监控程序无法读取这些密 +钥。在SEV中,密钥注入需在虚拟机启动流程的早期阶段(客户机开始运行前) +执行。 + +efi_secret内核模块允许用户空间应用程序通过securityfs(安全文件系统)访 +问这些密钥。 + +密钥数据流 +========== + +客户机固件可能会为密钥注入预留一块指定的内存区域,并将该区域的位置(基准 +客户机物理地址GPA和长度)在EFI配置表中,通过 ``LINUX_EFI_COCO_SECRET_AREA_GUID`` +条目(对应的GUID值为 ``adf956ad-e98c-484c-ae11-b51c7d336447`` )的形式发布。 +固件应将此内存区域标记为 ``EFI_RESERVED_TYPE`` ,因此内核不应将其用于自身用途。 + +虚拟机启动过程中,虚拟机管理器可向该区域注入密钥。在AMD SEV和SEV-ES中,此 +操作通过 ``KVM_SEV_LAUNCH_SECRET`` 命令执行(参见 [sev_CN]_ )。注入的“客户机 +所有者密钥数据”应采用带GUID的密钥值表结构,其二进制格式在 ``drivers/virt/ +coco/efi_secret/efi_secret.c`` 文件的EFI密钥区域结构部分中有详细描述。 + +内核启动时,内核的EFI驱动程序将保存密钥区域位置(来自EFI配置表)到 ``efi.coco_secret`` +字段。随后,它会检查密钥区域是否已填充:映射该区域并检查其内容是否以 +``EFI_SECRET_TABLE_HEADER_GUID`` (对应的GUID为 ``1e74f542-71dd-4d66-963e-ef4287ff173b`` ) +开头。如果密钥区域已填充,EFI驱动程序将自动加载efi_secret内核模块,并通过securityfs将密钥 +暴露给用户空间应用程序。efi_secret文件系统接口的详细信息请参考 [secrets-coco-abi_CN]_ 。 + + +应用使用示例 +============ + +假设客户机需要对加密文件进行计算处理。客户机所有者通过密钥注入机制提供解密密钥 +(即密钥)。客户机应用程序从efi_secret文件系统读取该密钥,然后将文件解密到内存中, +接着对内容进行需要的计算。 + +在此示例中,主机无法从磁盘镜像中读取文件,因为文件是加密的;主机无法读取解密密钥, +因为它是通过密钥注入机制(即安全通道)传递的;主机也无法读取内存中的解密内容,因为 +这是一个机密型(内存加密)客户机。 + +以下是一个简单的示例,展示了在客户机中使用efi_secret模块的过程,在启动时注入了 +一个包含4个密钥的EFI密钥区域:: + + # ls -la /sys/kernel/security/secrets/coco + total 0 + drwxr-xr-x 2 root root 0 Jun 28 11:54 . + drwxr-xr-x 3 root root 0 Jun 28 11:54 .. + -r--r----- 1 root root 0 Jun 28 11:54 736870e5-84f0-4973-92ec-06879ce3da0b + -r--r----- 1 root root 0 Jun 28 11:54 83c83f7f-1356-4975-8b7e-d3a0b54312c6 + -r--r----- 1 root root 0 Jun 28 11:54 9553f55d-3da2-43ee-ab5d-ff17f78864d2 + -r--r----- 1 root root 0 Jun 28 11:54 e6f5a162-d67f-4750-a67c-5d065f2a9910 + + # hd /sys/kernel/security/secrets/coco/e6f5a162-d67f-4750-a67c-5d065f2a9910 + 00000000 74 68 65 73 65 2d 61 72 65 2d 74 68 65 2d 6b 61 |these-are-the-ka| + 00000010 74 61 2d 73 65 63 72 65 74 73 00 01 02 03 04 05 |ta-secrets......| + 00000020 06 07 |..| + 00000022 + + # rm /sys/kernel/security/secrets/coco/e6f5a162-d67f-4750-a67c-5d065f2a9910 + + # ls -la /sys/kernel/security/secrets/coco + total 0 + drwxr-xr-x 2 root root 0 Jun 28 11:55 . + drwxr-xr-x 3 root root 0 Jun 28 11:54 .. + -r--r----- 1 root root 0 Jun 28 11:54 736870e5-84f0-4973-92ec-06879ce3da0b + -r--r----- 1 root root 0 Jun 28 11:54 83c83f7f-1356-4975-8b7e-d3a0b54312c6 + -r--r----- 1 root root 0 Jun 28 11:54 9553f55d-3da2-43ee-ab5d-ff17f78864d2 + + +参考文献 +======== + +请参见 [sev-api-spec_CN]_ 以获取有关SEV ``LAUNCH_SECRET`` 操作的更多信息。 + +.. [sev_CN] Documentation/virt/kvm/x86/amd-memory-encryption.rst +.. [secrets-coco-abi_CN] Documentation/ABI/testing/securityfs-secrets-coco +.. [sev-api-spec_CN] https://www.amd.com/system/files/TechDocs/55766_SEV-KM_API_Specification.pdf + diff --git a/Documentation/translations/zh_CN/security/secrets/index.rst b/Documentation/translations/zh_CN/security/secrets/index.rst index 5ea78713f10e..38464dcb2c3c 100644 --- a/Documentation/translations/zh_CN/security/secrets/index.rst +++ b/Documentation/translations/zh_CN/security/secrets/index.rst @@ -5,13 +5,10 @@ :翻译: -===================== +======== 密钥文档 -===================== +======== .. toctree:: - -TODOLIST: - -* coco + coco diff --git a/Documentation/translations/zh_CN/subsystem-apis.rst b/Documentation/translations/zh_CN/subsystem-apis.rst index 8b646c1010be..830217140fb6 100644 --- a/Documentation/translations/zh_CN/subsystem-apis.rst +++ b/Documentation/translations/zh_CN/subsystem-apis.rst @@ -71,12 +71,11 @@ TODOList: :maxdepth: 1 filesystems/index + scsi/index TODOList: -* block/index * cdrom/index -* scsi/index * target/index **Fixme**: 这里还需要更多的分类组织工作。 diff --git a/Documentation/translations/zh_TW/admin-guide/README.rst b/Documentation/translations/zh_TW/admin-guide/README.rst index 0b038074d9d1..c8b7ccfaa656 100644 --- a/Documentation/translations/zh_TW/admin-guide/README.rst +++ b/Documentation/translations/zh_TW/admin-guide/README.rst @@ -291,5 +291,5 @@ Documentation/translations/zh_CN/admin-guide/bug-hunting.rst 。 更多用GDB調試內核的信息,請參閱: Documentation/translations/zh_CN/dev-tools/gdb-kernel-debugging.rst -和 Documentation/dev-tools/kgdb.rst 。 +和 Documentation/process/debugging/kgdb.rst 。 diff --git a/Documentation/translations/zh_TW/dev-tools/gdb-kernel-debugging.rst b/Documentation/translations/zh_TW/dev-tools/gdb-kernel-debugging.rst index b595af59ba78..4fd1757c3036 100644 --- a/Documentation/translations/zh_TW/dev-tools/gdb-kernel-debugging.rst +++ b/Documentation/translations/zh_TW/dev-tools/gdb-kernel-debugging.rst @@ -2,7 +2,7 @@ .. include:: ../disclaimer-zh_TW.rst -:Original: Documentation/dev-tools/gdb-kernel-debugging.rst +:Original: Documentation/process/debugging/gdb-kernel-debugging.rst :Translator: 高超 gao chao <gaochao49@huawei.com> 通過gdb調試內核和模塊 |
