code:
.. _cn_volatile_considered_harmful:

.. include:: ../disclaimer-zh_CN.rst

:Original: :ref:`Documentation/process/volatile-considered-harmful.rst
           <volatile_considered_harmful>`

如果想评论或更新本文的内容，请直接联系原文档的维护者。如果你使用英文
交流有困难的话，也可以向中文版维护者求助。如果本翻译更新不及时或者翻
译存在问题，请联系中文版维护者::

        英文版维护者： Jonathan Corbet <corbet@lwn.net>
        中文版维护者： 伍鹏  Bryan Wu <bryan.wu@analog.com>
        中文版翻译者： 伍鹏  Bryan Wu <bryan.wu@analog.com>
        中文版校译者： 张汉辉  Eugene Teo <eugeneteo@kernel.sg>
                       杨瑞  Dave Young <hidave.darkstar@gmail.com>
                       时奎亮 Alex Shi <alex.shi@linux.alibaba.com>

为什么不应该使用“volatile”类型
==============================

C程序员通常认为volatile表示某个变量可以在当前执行的线程之外被改变；因此，在内核
中用到共享数据结构时，常常会有C程序员喜欢使用volatile这类变量。换句话说，他们经
常会把volatile类型看成某种简易的原子变量，当然它们不是。在内核中使用volatile几
乎总是错误的；本文档将解释为什么这样。

理解volatile的关键是知道它的目的是用来消除优化，实际上很少有人真正需要这样的应
用。在内核中，程序员必须防止意外的并发访问破坏共享的数据结构，这其实是一个完全
不同的任务。用来防止意外并发访问的保护措施，可以更加高效的避免大多数优化相关的
问题。

像volatile一样，内核提供了很多原语来保证并发访问时的数据安全（自旋锁, 互斥量,内
存屏障等等），同样可以防止意外的优化。如果可以正确使用这些内核原语，那么就没有
必要再使用volatile。如果仍然必须使用volatile，那么几乎可以肯定在代码的某处有一
个bug。在正确设计的内核代码中，volatile能带来的仅仅是使事情变慢。

思考一下这段典型的内核代码::

    spin_lock(&the_lock);
    do_something_on(&shared_data);
    do_something_else_with(&shared_data);
    spin_unlock(&the_lock);

如果所有的代码都遵循加锁规则，当持有the_lock的时候，不可能意外的改变shared_data的
值。任何可能访问该数据的其他代码都会在这个锁上等待。自旋锁原语跟内存屏障一样—— 它
们显式的用来书写成这样 —— 意味着数据访问不会跨越它们而被优化。所以本来编译器认为
它知道在shared_data里面将有什么，但是因为spin_lock()调用跟内存屏障一样，会强制编
译器忘记它所知道的一切。那么在访问这些数据时不会有优化的问题。

如果shared_data被声名为volatile，锁操作将仍然是必须的。就算我们知道没有其他人正在
使用它，编译器也将被阻止优化对临界区内shared_data的访问。在锁有效的同时，
shared_data不是volatile的。在处理共享数据的时候，适当的锁操作可以不再需要
volatile —— 并且是有潜在危害的。

volatile的存储类型最初是为那些内存映射的I/O寄存器而定义。在内核里，寄存器访问也应
该被锁保护，但是人们也不希望编译器“优化”临界区内的寄存器访问。内核里I/O的内存访问
是通过访问函数完成的；不赞成通过指针对I/O内存的直接访问，并且不是在所有体系架构上
都能工作。那些访问函数正是为了防止意外优化而写的，因此，再说一次，volatile类型不
是必需的。

另一种引起用户可能使用volatile的情况是当处理器正忙着等待一个变量的值。正确执行一
个忙等待的方法是::

    while (my_variable != what_i_want)
        cpu_relax();

cpu_relax()调用会降低CPU的能量消耗或者让位于超线程双处理器；它也作为内存屏障一样出
现，所以，再一次，volatile不是必需的。当然，忙等待一开始就是一种反常规的做法。

在内核中，一些稀少的情况下volatile仍然是有意义的：

  - 在一些体系架构的系统上，允许直接的I/0内存访问，那么前面提到的访问函数可以使用
    volatile。基本上，每一个访问函数调用它自己都是一个小的临界区域并且保证了按照
    程序员期望的那样发生访问操作。

  - 某些会改变内存的内联汇编代码虽然没有什么其他明显的附作用，但是有被GCC删除的可
    能性。在汇编声明中加上volatile关键字可以防止这种删除操作。

  - Jiffies变量是一种特殊情况，虽然每次引用它的时候都可以有不同的值，但读jiffies
    变量时不需要任何特殊的加锁保护。所以jiffies变量可以使用volatile，但是不赞成
    其他跟jiffies相同类型变量使用volatile。Jiffies被认为是一种“愚蠢的遗留物"
    （Linus的话）因为解决这个问题比保持现状要麻烦的多。

  - 由于某些I/0设备可能会修改连续一致的内存,所以有时,指向连续一致内存的数据结构
    的指针需要正确的使用volatile。网络适配器使用的环状缓存区正是这类情形的一个例
    子，其中适配器用改变指针来表示哪些描述符已经处理过了。

对于大多代码，上述几种可以使用volatile的情况都不适用。所以，使用volatile是一种
bug并且需要对这样的代码额外仔细检查。那些试图使用volatile的开发人员需要退一步想想
他们真正想实现的是什么。

非常欢迎删除volatile变量的补丁 － 只要证明这些补丁完整的考虑了并发问题。

注释
----

[1] https://lwn.net/Articles/233481/
[2] https://lwn.net/Articles/233482/

致谢
----

最初由Randy Dunlap推动并作初步研究
由Jonathan Corbet撰写
参考Satyam Sharma，Johannes Stezenbach，Jesper Juhl，Heikki Orsila，
H. Peter Anvin，Philipp Hahn和Stefan Richter的意见改善了本档。


code:
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/riscv/boot-image-header.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

.. _cn_boot-image-header.rst:

==========================
RISC-V Linux启动镜像文件头
==========================

:Author: Atish Patra <atish.patra@wdc.com>
:Date:   20 May 2019

此文档仅描述RISC-V Linux 启动文件头的详情。

TODO:
  写一个完整的启动指南。

在解压后的Linux内核镜像中存在以下64字节的文件头::

	u32 code0;		  /* Executable code */
	u32 code1;		  /* Executable code */
	u64 text_offset;	  /* Image load offset, little endian */
	u64 image_size;		  /* Effective Image size, little endian */
	u64 flags;		  /* kernel flags, little endian */
	u32 version;		  /* Version of this header */
	u32 res1 = 0;		  /* Reserved */
	u64 res2 = 0;		  /* Reserved */
	u64 magic = 0x5643534952; /* Magic number, little endian, "RISCV" */
	u32 magic2 = 0x05435352;  /* Magic number 2, little endian, "RSC\x05" */
	u32 res3;		  /* Reserved for PE COFF offset */

这种头格式与PE/COFF文件头兼容，并在很大程度上受到ARM64文件头的启发。因此，ARM64
和RISC-V文件头可以在未来合并为一个共同的头。

注意
====

- 将来也可以复用这个文件头，用来对RISC-V的EFI桩提供支持。为了使内核镜像如同一个
  EFI应用程序一样加载，EFI规范中规定在内核镜像的开始需要PE/COFF镜像文件头。为了
  支持EFI桩，应该用“MZ”魔术字符替换掉code0，并且res3（偏移量未0x3c）应指向PE/COFF
  文件头的其余部分.

- 表示文件头版本号的Drop-bit位域

	==========  ==========
	Bits 0:15   次要  版本
	Bits 16:31  主要  版本
	==========  ==========

  这保持了新旧版本之间的兼容性。
  当前版本被定义为0.2。

- 从版本0.2开始，结构体成员“magic”就已经被弃用，在之后的版本中，可能会移除掉它。
  最初，该成员应该与ARM64头的“magic”成员匹配，但遗憾的是并没有。
  “magic2”成员代替“magic”成员与ARM64头相匹配。

- 在当前的文件头，标志位域只剩下了一个位。

	=====  ==============================
	Bit 0  内核字节序。1 if BE, 0 if LE.
	=====  ==============================

- 对于引导加载程序加载内核映像来说，image_size成员对引导加载程序而言是必须的，否
  则将引导失败。

repo_name: angery/linux


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/riscv/index.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

.. _cn_riscv_index:

===============
RISC-V 体系结构
===============

.. toctree::
    :maxdepth: 1

    boot-image-header
    vm-layout
    patch-acceptance

.. only::  subproject and html

   目录
   ====

   * :ref:`genindex`


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/riscv/patch-acceptance.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

.. _cn_riscv_patch-acceptance:

arch/riscv 开发者维护指南
=========================

概述
----
RISC-V指令集体系结构是公开开发的：
正在进行的草案可供所有人查看和测试实现。新模块或者扩展草案可能会在开发过程中发
生更改---有时以不兼容的方式对以前的草案进行更改。这种灵活性可能会给RISC-V Linux
维护者带来挑战。Linux开发过程更喜欢经过良好检查和测试的代码，而不是试验代码。我
们希望推广同样的规则到即将被内核合并的RISC-V相关代码。

附加的提交检查单
----------------
我们仅接受相关标准已经被RISC-V基金会标准为“已批准”或“已冻结”的扩展或模块的补丁。
（开发者当然可以维护自己的Linux内核树，其中包含所需代码扩展草案的代码。）

此外，RISC-V规范允许爱好者创建自己的自定义扩展。这些自定义拓展不需要通过RISC-V
基金会的任何审核或批准。为了避免将爱好者一些特别的RISC-V拓展添加进内核代码带来
的维护复杂性和对性能的潜在影响，我们将只接受RISC-V基金会正式冻结或批准的的扩展
补丁。（开发者当然可以维护自己的Linux内核树，其中包含他们想要的任何自定义扩展
的代码。）


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/rust/arch-support.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

架构支持
========

目前，Rust编译器（``rustc``）使用LLVM进行代码生成，这限制了可以支持的目标架构。此外，对
使用LLVM/Clang构建内核的支持也有所不同（请参见 Documentation/kbuild/llvm.rst ）。这
种支持对于使用 ``libclang`` 的 ``bindgen`` 来说是必需的。

下面是目前可以工作的架构的一般总结。支持程度与 ``MAINTAINERS`` 文件中的``S`` 值相对应:

============  ================  ==============================================
架构          支持水平          限制因素
============  ================  ==============================================
``x86``       Maintained        只有 ``x86_64``
============  ================  ==============================================


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/rust/coding-guidelines.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

编码指南
========

本文档描述了如何在内核中编写Rust代码。

风格和格式化
------------

代码应该使用 ``rustfmt`` 进行格式化。这样一来，一个不时为内核做贡献的人就不需要再去学
习和记忆一个样式指南了。更重要的是，审阅者和维护者不需要再花时间指出风格问题，这样就可以
减少补丁落地所需的邮件往返。

.. note::  ``rustfmt`` 不检查注释和文档的约定。因此，这些仍然需要照顾到。

使用 ``rustfmt`` 的默认设置。这意味着遵循Rust的习惯性风格。例如，缩进时使用4个空格而
不是制表符。

在输入、保存或提交时告知编辑器/IDE进行格式化是很方便的。然而，如果因为某些原因需要在某
个时候重新格式化整个内核Rust的源代码，可以运行以下程序::

	make LLVM=1 rustfmt

也可以检查所有的东西是否都是格式化的（否则就打印一个差异），例如对于一个CI，用::

	make LLVM=1 rustfmtcheck

像内核其他部分的 ``clang-format`` 一样， ``rustfmt`` 在单个文件上工作，并且不需要
内核配置。有时，它甚至可以与破碎的代码一起工作。

注释
----

“普通”注释（即以 ``//`` 开头，而不是 ``///`` 或 ``//!`` 开头的代码文档）的写法与文
档注释相同，使用Markdown语法，尽管它们不会被渲染。这提高了一致性，简化了规则，并允许在
这两种注释之间更容易地移动内容。比如说:

.. code-block:: rust

	// `object` is ready to be handled now.
	f(object);

此外，就像文档一样，注释在句子的开头要大写，并以句号结束（即使是单句）。这包括 ``// SAFETY:``,
``// TODO:`` 和其他“标记”的注释，例如:

.. code-block:: rust

	// FIXME: The error should be handled properly.

注释不应该被用于文档的目的：注释是为了实现细节，而不是为了用户。即使源文件的读者既是API
的实现者又是用户，这种区分也是有用的。事实上，有时同时使用注释和文档是很有用的。例如，用
于 ``TODO`` 列表或对文档本身的注释。对于后一种情况，注释可以插在中间；也就是说，离要注
释的文档行更近。对于其他情况，注释会写在文档之后，例如:

.. code-block:: rust

	/// Returns a new [`Foo`].
	///
	/// # Examples
	///
	// TODO: Find a better example.
	/// ```
	/// let foo = f(42);
	/// ```
	// FIXME: Use fallible approach.
	pub fn f(x: i32) -> Foo {
	    // ...
	}

一种特殊的注释是 ``// SAFETY:`` 注释。这些注释必须出现在每个 ``unsafe`` 块之前，它们
解释了为什么该块内的代码是正确/健全的，即为什么它在任何情况下都不会触发未定义行为，例如:

.. code-block:: rust

	// SAFETY: `p` is valid by the safety requirements.
	unsafe { *p = 0; }

``// SAFETY:`` 注释不能与代码文档中的 ``# Safety`` 部分相混淆。 ``# Safety`` 部
分指定了（函数）调用者或（特性）实现者需要遵守的契约。
``// SAFETY:`` 注释显示了为什么一个（函数）调用者或（特性）实现者实际上尊重了
``# Safety`` 部分或语言参考中的前提条件。

代码文档
--------

Rust内核代码不像C内核代码那样被记录下来（即通过kernel-doc）。取而代之的是用于记录Rust
代码的常用系统：rustdoc工具，它使用Markdown（一种轻量级的标记语言）。

要学习Markdown，外面有很多指南。例如：

https://commonmark.org/help/

一个记录良好的Rust函数可能是这样的:

.. code-block:: rust

	/// Returns the contained [`Some`] value, consuming the `self` value,
	/// without checking that the value is not [`None`].
	///
	/// # Safety
	///
	/// Calling this method on [`None`] is *[undefined behavior]*.
	///
	/// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
	///
	/// # Examples
	///
	/// ```
	/// let x = Some("air");
	/// assert_eq!(unsafe { x.unwrap_unchecked() }, "air");
	/// ```
	pub unsafe fn unwrap_unchecked(self) -> T {
	    match self {
	        Some(val) => val,

	        // SAFETY: The safety contract must be upheld by the caller.
	        None => unsafe { hint::unreachable_unchecked() },
	    }
	}

这个例子展示了一些 ``rustdoc`` 的特性和内核中遵循的一些惯例:

  - 第一段必须是一个简单的句子，简要地描述被记录的项目的作用。进一步的解释必须放在额
    外的段落中。

  - 不安全的函数必须在 ``# Safety`` 部分记录其安全前提条件。

  - 虽然这里没有显示，但如果一个函数可能会恐慌，那么必须在 ``# Panics`` 部分描述发
    生这种情况的条件。

    请注意，恐慌应该是非常少见的，只有在有充分理由的情况下才会使用。几乎在所有的情况下，
    都应该使用一个可失败的方法，通常是返回一个 ``Result``。

  - 如果提供使用实例对读者有帮助的话，必须写在一个叫做``# Examples``的部分。

  - Rust项目（函数、类型、常量……）必须有适当的链接(``rustdoc`` 会自动创建一个
    链接)。

  - 任何 ``unsafe`` 的代码块都必须在前面加上一个 ``// SAFETY:`` 的注释，描述里面
    的代码为什么是正确的。

    虽然有时原因可能看起来微不足道，但写这些注释不仅是记录已经考虑到的问题的好方法，
    最重要的是，它提供了一种知道没有额外隐含约束的方法。

要了解更多关于如何编写Rust和拓展功能的文档，请看看 ``rustdoc`` 这本书，网址是:

	https://doc.rust-lang.org/rustdoc/how-to-write-documentation.html

命名
----

Rust内核代码遵循通常的Rust命名空间:

	https://rust-lang.github.io/api-guidelines/naming.html

当现有的C语言概念（如宏、函数、对象......）被包装成Rust抽象时，应该使用尽可能接近C语
言的名称，以避免混淆，并在C语言和Rust语言之间来回切换时提高可读性。例如，C语言中的
``pr_info`` 这样的宏在Rust中的命名是一样的。

说到这里，应该调整大小写以遵循Rust的命名惯例，模块和类型引入的命名间隔不应该在项目名称
中重复。例如，在包装常量时，如:

.. code-block:: c

	#define GPIO_LINE_DIRECTION_IN	0
	#define GPIO_LINE_DIRECTION_OUT	1

在Rust中的等价物可能是这样的（忽略文档）。:

.. code-block:: rust

	pub mod gpio {
	    pub enum LineDirection {
	        In = bindings::GPIO_LINE_DIRECTION_IN as _,
	        Out = bindings::GPIO_LINE_DIRECTION_OUT as _,
	    }
	}

也就是说， ``GPIO_LINE_DIRECTION_IN`` 的等价物将被称为 ``gpio::LineDirection::In`` 。
特别是，它不应该被命名为 ``gpio::gpio_line_direction::GPIO_LINE_DIRECTION_IN`` 。


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/rust/general-information.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

基本信息
========

本文档包含了在内核中使用Rust支持时需要了解的有用信息。

代码文档
--------

Rust内核代码使用其内置的文档生成器 ``rustdoc`` 进行记录。

生成的HTML文档包括集成搜索、链接项（如类型、函数、常量）、源代码等。它们可以在以下地址阅读
（TODO：当在主线中时链接，与其他文档一起生成）：

	http://kernel.org/

这些文档也可以很容易地在本地生成和阅读。这相当快（与编译代码本身的顺序相同），而且不需要特
殊的工具或环境。这有一个额外的好处，那就是它们将根据所使用的特定内核配置进行定制。要生成它
们，请使用 ``rustdoc`` 目标，并使用编译时使用的相同调用，例如::

	make LLVM=1 rustdoc

要在你的网络浏览器中本地阅读该文档，请运行如::

	xdg-open rust/doc/kernel/index.html

要了解如何编写文档，请看 coding-guidelines.rst 。

额外的lints
-----------

虽然 ``rustc`` 是一个非常有用的编译器，但一些额外的lints和分析可以通过 ``clippy``
（一个Rust linter）来实现。要启用它，请将CLIPPY=1传递到用于编译的同一调用中，例如::

	make LLVM=1 CLIPPY=1

请注意，Clippy可能会改变代码生成，因此在构建产品内核时不应该启用它。

抽象和绑定
----------

抽象是用Rust代码包装来自C端的内核功能。

为了使用来自C端的函数和类型，需要创建绑定。绑定是Rust对那些来自C端的函数和类型的声明。

例如，人们可以在Rust中写一个 ``Mutex`` 抽象，它从C端包装一个 ``Mutex结构体`` ，并
通过绑定调用其函数。

抽象并不能用于所有的内核内部API和概念，但随着时间的推移，我们打算扩大覆盖范围。“Leaf”
模块（例如，驱动程序）不应该直接使用C语言的绑定。相反，子系统应该根据需要提供尽可能安
全的抽象。

有条件的编译
------------

Rust代码可以访问基于内核配置的条件性编译:

.. code-block:: rust

	#[cfg(CONFIG_X)]       // Enabled               (`y` or `m`)
	#[cfg(CONFIG_X="y")]   // Enabled as a built-in (`y`)
	#[cfg(CONFIG_X="m")]   // Enabled as a module   (`m`)
	#[cfg(not(CONFIG_X))]  // Disabled


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/rust/index.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

Rust
====

与内核中的Rust有关的文档。若要开始在内核中使用Rust，请阅读quick-start.rst指南。

.. toctree::
    :maxdepth: 1

    quick-start
    general-information
    coding-guidelines
    arch-support

.. only::  subproject and html

   Indices
   =======

   * :ref:`genindex`


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/rust/quick-start.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

快速入门
========

本文介绍了如何开始使用Rust进行内核开发。

构建依赖
--------

本节描述了如何获取构建所需的工具。

其中一些依赖也许可以从Linux发行版中获得，包名可能是 ``rustc`` , ``rust-src`` ,
``rust-bindgen`` 等。然而，在写这篇文章的时候，它们很可能还不够新，除非发行版跟踪最
新的版本。

为了方便检查是否满足要求，可以使用以下目标::

	make LLVM=1 rustavailable

这会触发与Kconfig用来确定是否应该启用 ``RUST_IS_AVAILABLE`` 相同的逻辑；不过，如
果Kconfig认为不该启用，它会列出未满足的条件。

rustc
*****

需要一个特定版本的Rust编译器。较新的版本可能会也可能不会工作，因为就目前而言，内核依赖
于一些不稳定的Rust特性。

如果使用的是 ``rustup`` ，请进入检出的源代码目录并运行::

	rustup override set $(scripts/min-tool-version.sh rustc)

或者从以下网址获取一个独立的安装程序或安装 ``rustup`` ：

	https://www.rust-lang.org

Rust标准库源代码
****************

Rust标准库的源代码是必需的，因为构建系统会交叉编译 ``core`` 和 ``alloc`` 。

如果正在使用 ``rustup`` ，请运行::

	rustup component add rust-src

这些组件是按工具链安装的，因此以后升级Rust编译器版本需要重新添加组件。

否则，如果使用独立的安装程序，可以将Rust仓库克隆到工具链的安装文件夹中::

	git clone --recurse-submodules \
		--branch $(scripts/min-tool-version.sh rustc) \
		https://github.com/rust-lang/rust \
		$(rustc --print sysroot)/lib/rustlib/src/rust

在这种情况下，以后升级Rust编译器版本需要手动更新这个克隆的仓库。

libclang
********

``bindgen`` 使用 ``libclang`` （LLVM的一部分）来理解内核中的C代码，这意味着需要安
装LLVM；同在开启 ``CC=clang`` 或 ``LLVM=1`` 时编译内核一样。

Linux发行版中可能会有合适的包，所以最好先检查一下。

适用于部分系统和架构的二进制文件也可到以下网址下载：

	https://releases.llvm.org/download.html

或者自行构建LLVM，这需要相当长的时间，但并不是一个复杂的过程：

	https://llvm.org/docs/GettingStarted.html#getting-the-source-code-and-building-llvm

请参阅 Documentation/kbuild/llvm.rst 了解更多信息，以及获取预构建版本和发行包
的进一步方法。

bindgen
*******

内核的C端绑定是在构建时使用 ``bindgen`` 工具生成的。这需要特定的版本。

通过以下方式安装它（注意，这将从源码下载并构建该工具）::

	cargo install --locked --version $(scripts/min-tool-version.sh bindgen) bindgen

开发依赖
--------

本节解释了如何获取开发所需的工具。也就是说，在构建内核时不需要这些工具。

rustfmt
*******

``rustfmt`` 工具被用来自动格式化所有的Rust内核代码，包括生成的C绑定（详情请见
coding-guidelines.rst ）。

如果使用的是 ``rustup`` ，它的 ``默认`` 配置文件已经安装了这个工具，因此不需要做什么。
如果使用的是其他配置文件，可以手动安装该组件::

	rustup component add rustfmt

独立的安装程序也带有 ``rustfmt`` 。

clippy
******

``clippy`` 是一个Rust linter。运行它可以为Rust代码提供额外的警告。它可以通过向 ``make``
传递 ``CLIPPY=1`` 来运行（关于细节，详见 general-information.rst ）。

如果正在使用 ``rustup`` ，它的 ``默认`` 配置文件已经安装了这个工具，因此不需要做什么。
如果使用的是另一个配置文件，该组件可以被手动安装::

	rustup component add clippy

独立的安装程序也带有 ``clippy`` 。

cargo
*****

``cargo`` 是Rust的本地构建系统。目前需要它来运行测试，因为它被用来构建一个自定义的标准
库，其中包含了内核中自定义 ``alloc`` 所提供的设施。测试可以使用 ``rusttest`` Make 目标
来运行。

如果使用的是 ``rustup`` ，所有的配置文件都已经安装了该工具，因此不需要再做什么。

独立的安装程序也带有 ``cargo`` 。

rustdoc
*******

``rustdoc`` 是Rust的文档工具。它为Rust代码生成漂亮的HTML文档（详情请见 general-information.rst ）。

``rustdoc`` 也被用来测试文档化的Rust代码中提供的例子（称为doctests或文档测试）。
``rusttest`` 是本功能的Make目标。

如果使用的是 ``rustup`` ，所有的配置文件都已经安装了这个工具，因此不需要做什么。

独立的安装程序也带有 ``rustdoc`` 。

rust-analyzer
*************

`rust-analyzer <https://rust-analyzer.github.io/>`_ 语言服务器可以和许多编辑器
一起使用，以实现语法高亮、补全、转到定义和其他功能。

``rust-analyzer`` 需要一个配置文件， ``rust-project.json``, 它可以由 ``rust-analyzer``
Make 目标生成。

配置
----

Rust支持（CONFIG_RUST）需要在 ``General setup`` 菜单中启用。在其他要求得到满足的情
况下，该选项只有在找到合适的Rust工具链时才会显示（见上文）。相应的，这将使依赖Rust的其
他选项可见。

之后，进入::

	Kernel hacking
	    -> Sample kernel code
	        -> Rust samples

并启用一些内置或可加载的样例模块。

构建
----

用完整的LLVM工具链构建内核是目前支持的最佳设置。即::

	make LLVM=1

对于不支持完整LLVM工具链的架构，使用::

	make CC=clang

使用GCC对某些配置也是可行的，但目前它是非常试验性的。

折腾
----

要想深入了解，请看 ``samples/rust/`` 下的样例源代码、 ``rust/`` 下的Rust支持代码和
``Kernel hacking`` 下的 ``Rust hacking`` 菜单。

如果使用的是GDB/Binutils，而Rust符号没有被demangled，原因是工具链还不支持Rust的新v0
mangling方案。有几个办法可以解决：

  - 安装一个较新的版本（GDB >= 10.2, Binutils >= 2.36）。

  - 一些版本的GDB（例如vanilla GDB 10.1）能够使用嵌入在调试信息(``CONFIG_DEBUG_INFO``)
    中的pre-demangled的名字。


code:
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/scheduler/completion.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

 唐艺舟 Tang Yizhou <tangyeechou@gmail.com>

=======================================
完成 - "等待完成" 屏障应用程序接口(API)
=======================================

简介:
-----

如果你有一个或多个线程必须等待某些内核活动达到某个点或某个特定的状态，完成可以为这
个问题提供一个无竞争的解决方案。从语义上讲，它们有点像pthread_barrier()，并且使
用的案例类似

完成是一种代码同步机制，它比任何滥用锁/信号量和忙等待循环的行为都要好。当你想用yield()
或一些古怪的msleep(1)循环来允许其它代码继续运行时，你可能想用wait_for_completion*()
调用和completion()来代替。

使用“完成”的好处是，它们有一个良好定义、聚焦的目标，这不仅使得我们很容易理解代码的意图，
而且它们也会生成更高效的代码，因为所有线程都可以继续执行，直到真正需要结果的时刻。而且等
待和信号都高效的使用了低层调度器的睡眠/唤醒设施。

完成是建立在Linux调度器的等待队列和唤醒基础设施之上的。等待队列中的线程所等待的
事件被简化为 ``struct completion`` 中的一个简单标志，被恰如其名地称为‘done’。

由于完成与调度有关，代码可以在kernel/sched/completion.c中找到。

用法:
-----

使用完成需要三个主要部分:

 - 'struct completion' 同步对象的初始化
 - 通过调用wait_for_completion()的一个变体来实现等待部分。
 - 通过调用complete()或complete_all()实现发信端。

也有一些辅助函数用于检查完成的状态。请注意，虽然必须先做初始化，但等待和信号部分可以
按任何时间顺序出现。也就是说，一个线程在另一个线程检查是否需要等待它之前，已经将一个
完成标记为 "done"，这是完全正常的。

要使用完成API，你需要#include <linux/completion.h>并创建一个静态或动态的
``struct completion`` 类型的变量，它只有两个字段::

	struct completion {
		unsigned int done;
		wait_queue_head_t wait;
	};

结构体提供了->wait等待队列来放置任务进行等待（如果有的话），以及->done完成标志来表明它
是否完成。

完成的命名应当与正在被同步的事件名一致。一个好的例子是::

	wait_for_completion(&early_console_added);

	complete(&early_console_added);

好的、直观的命名（一如既往地）有助于代码的可读性。将一个完成命名为 ``complete``
是没有帮助的，除非其目的是超级明显的...

初始化完成:
-----------

动态分配的完成对象最好被嵌入到数据结构中，以确保在函数/驱动的生命周期内存活，以防
止与异步complete()调用发生竞争。

在使用wait_for_completion()的_timeout()或_killable()/_interruptible()变体
时应特别小心，因为必须保证在所有相关活动（complete()或reinit_completion()）发生
之前不会发生内存解除分配，即使这些等待函数由于超时或信号触发而过早返回。

动态分配的完成对象的初始化是通过调用init_completion()来完成的::

	init_completion(&dynamic_object->done);

在这个调用中，我们初始化 waitqueue 并将 ->done 设置为 0，即“not completed”或
“not done”。

重新初始化函数reinit_completion()，只是将->done字段重置为0（“not done”），而
不触及等待队列。这个函数的调用者必须确保没有任何令人讨厌的wait_for_completion()
调用在并行进行。

在同一个完成对象上调用init_completion()两次很可能是一个bug，因为它将队列重新初始
化为一个空队列，已排队的任务可能会“丢失”--在这种情况下使用reinit_completion()，但
要注意其他竞争。

对于静态声明和初始化，可以使用宏。

对于文件范围内的静态（或全局）声明，你可以使用 DECLARE_COMPLETION()::

	static DECLARE_COMPLETION(setup_done);
	DECLARE_COMPLETION(setup_done);

注意，在这种情况下，完成在启动时（或模块加载时）被初始化为“not done”，不需要调用
init_completion()。

当完成被声明为一个函数中的局部变量时，那么应该总是明确地使用
DECLARE_COMPLETION_ONSTACK()来初始化，这不仅仅是为了让lockdep正确运行，也是明确表
名它有限的使用范围是有意为之并被仔细考虑的::

	DECLARE_COMPLETION_ONSTACK(setup_done)

请注意，当使用完成对象作为局部变量时，你必须敏锐地意识到函数堆栈的短暂生命期：在所有
活动（如等待的线程）停止并且完成对象完全未被使用之前，函数不得返回到调用上下文。

再次强调这一点：特别是在使用一些具有更复杂结果的等待API变体时，比如超时或信号
（_timeout(), _killable()和_interruptible()）变体，等待可能会提前完成，而对象可
能仍在被其他线程使用 - 从wait_on_completion*()调用者函数的返回会取消分配函数栈，如
果complete()在其它某线程中完成调用，会引起微小的数据损坏。简单的测试可能不会触发这
些类型的竞争。

如果不确定的话，使用动态分配的完成对象， 最好是嵌入到其它一些生命周期长的对象中，长到
超过使用完成对象的任何辅助线程的生命周期，或者有一个锁或其他同步机制来确保complete()
不会在一个被释放的对象中调用。

在堆栈上单纯地调用DECLARE_COMPLETION()会触发一个lockdep警告。

等待完成:
---------

对于一个线程来说，要等待一些并发活动的完成，它要在初始化的完成结构体上调用
wait_for_completion()::

	void wait_for_completion(struct completion *done)

一个典型的使用场景是::

	CPU#1					CPU#2

	struct completion setup_done;

	init_completion(&setup_done);
	initialize_work(...,&setup_done,...);

	/* run non-dependent code */		/* do setup */

	wait_for_completion(&setup_done);	complete(setup_done);

这并不意味着调用wait_for_completion()和complete()有任何特定的时间顺序--如果调
用complete()发生在调用wait_for_completion()之前，那么等待方将立即继续执行，因为
所有的依赖都得到了满足；如果没有，它将阻塞，直到complete()发出完成的信号。

注意，wait_for_completion()是在调用spin_lock_irq()/spin_unlock_irq()，所以
只有当你知道中断被启用时才能安全地调用它。从IRQs-off的原子上下文中调用它将导致难以检
测的错误的中断启用。

默认行为是不带超时的等待，并将任务标记为“UNINTERRUPTIBLE”状态。wait_for_completion()
及其变体只有在进程上下文中才是安全的（因为它们可以休眠），但在原子上下文、中断上下文、IRQ
被禁用或抢占被禁用的情况下是不安全的--关于在原子/中断上下文中处理完成的问题，还请看下面的
try_wait_for_completion()。

由于wait_for_completion()的所有变体都可能（很明显）阻塞很长时间，这取决于它们所等
待的活动的性质，所以在大多数情况下，你可能不想在持有mutex锁的情况下调用它。

wait_for_completion*()可用的变体:
---------------------------------

下面的变体都会返回状态，在大多数(/所有)情况下都应该检查这个状态--在故意不检查状态的情
况下，你可能要做一个说明(例如，见arch/arm/kernel/smp.c:__cpu_up())。

一个常见的问题是不准确的返回类型赋值，所以要注意将返回值赋值给适当类型的变量。

检查返回值的具体含义也可能被发现是相当不准确的，例如，像这样的构造::

	if (!wait_for_completion_interruptible_timeout(...))

...会在成功完成和中断的情况下执行相同的代码路径--这可能不是你想要的结果::

	int wait_for_completion_interruptible(struct completion *done)

这个函数在任务等待时标记为TASK_INTERRUPTIBLE。如果在等待期间收到信号，它将返回
-ERESTARTSYS；否则为0::

	unsigned long wait_for_completion_timeout(struct completion *done, unsigned long timeout)

该任务被标记为TASK_UNINTERRUPTIBLE，并将最多超时等待“timeout”个jiffies。如果超时发生，则
返回0，否则返回剩余的时间（但至少是1）。

超时最好用msecs_to_jiffies()或usecs_to_jiffies()计算，以使代码在很大程度上不受
HZ的影响。

如果返回的超时值被故意忽略，那么注释应该解释原因
（例如，见drivers/mfd/wm8350-core.c wm8350_read_auxadc()::

	long wait_for_completion_interruptible_timeout(struct completion *done, unsigned long timeout)

这个函数传递一个以jiffies为单位的超时，并将任务标记为TASK_INTERRUPTIBLE。如果收到
信号，则返回-ERESTARTSYS；否则，如果完成超时，则返回0；如果完成了，则返回剩余的时间
（jiffies）。

更多的变体包括_killable，它使用TASK_KILLABLE作为指定的任务状态，如果它被中断，将返
回-ERESTARTSYS，如果完成了，则返回0。它也有一个_timeout变体::

	long wait_for_completion_killable(struct completion *done)
	long wait_for_completion_killable_timeout(struct completion *done, unsigned long timeout)

wait_for_completion_io()的_io变体的行为与非_io变体相同，只是将等待时间计为“IO等待”，
这对任务在调度/IO统计中的计算方式有影响::

	void wait_for_completion_io(struct completion *done)
	unsigned long wait_for_completion_io_timeout(struct completion *done, unsigned long timeout)

对完成发信号:
-------------

一个线程想要发出信号通知继续的条件已经达到，就会调用complete()，向其中一个等待者发出信
号表明它可以继续::

	void complete(struct completion *done)

... or calls complete_all() to signal all current and future waiters::

	void complete_all(struct completion *done)

即使在线程开始等待之前就发出了完成的信号，信号传递也会继续进行。这是通过等待者
“consuming”（递减）“struct completion” 的完成字段来实现的。等待的线程唤醒的顺序
与它们被排队的顺序相同（FIFO顺序）。

如果多次调用complete()，那么这将允许该数量的等待者继续进行--每次调用complete()将
简单地增加已完成的字段。但多次调用complete_all()是一个错误。complete()和
complete_all()都可以在IRQ/atomic上下文中安全调用。

在任何时候，只能有一个线程在一个特定的 “struct completion”上调用 complete() 或
complete_all() - 通过等待队列自旋锁进行序列化。任何对 complete() 或
complete_all() 的并发调用都可能是一个设计错误。

从IRQ上下文中发出完成信号 是可行的，因为它将正确地用
spin_lock_irqsave()/spin_unlock_irqrestore()执行锁操作

try_wait_for_completion()/completion_done():
--------------------------------------------

try_wait_for_completion()函数不会将线程放在等待队列中，而是在需要排队（阻塞）线
程时返回false，否则会消耗一个已发布的完成并返回true::

	bool try_wait_for_completion(struct completion *done)

最后，为了在不以任何方式改变完成的情况下检查完成的状态，可以调用completion_done()，
如果没有发布的完成尚未被等待者消耗，则返回false（意味着存在等待者），否则返回true::

	bool completion_done(struct completion *done)

try_wait_for_completion()和completion_done()都可以在IRQ或原子上下文中安全调用。


code:
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/scheduler/index.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>
 唐艺舟 Tang Yizhou <tangyeechou@gmail.com>

:校译:

===============
Linux调度器
===============

.. toctree::
    :maxdepth: 1

    completion
    sched-arch
    sched-bwc
    sched-design-CFS
    sched-domains
    sched-capacity
    sched-energy
    schedutil
    sched-nice-design
    sched-stats
    sched-debug

TODOList:

    sched-deadline
    sched-rt-group

    text_files

.. only::  subproject and html

   Indices
   =======

   * :ref:`genindex`


code:
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/scheduler/sched-arch.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

===============================
架构特定代码的CPU调度器实现提示
===============================

	Nick Piggin, 2005

上下文切换
==========
1. 运行队列锁
默认情况下，switch_to arch函数在调用时锁定了运行队列。这通常不是一个问题，除非
switch_to可能需要获取运行队列锁。这通常是由于上下文切换中的唤醒操作造成的。见
arch/ia64/include/asm/switch_to.h的例子。

为了要求调度器在运行队列解锁的情况下调用switch_to，你必须在头文件
中`#define __ARCH_WANT_UNLOCKED_CTXSW`(通常是定义switch_to的那个文件）。

在CONFIG_SMP的情况下，解锁的上下文切换对核心调度器的实现只带来了非常小的性能损
失。

CPU空转
=======
你的cpu_idle程序需要遵守以下规则：

1. 现在抢占应该在空闲的例程上禁用。应该只在调用schedule()时启用，然后再禁用。

2. need_resched/TIF_NEED_RESCHED 只会被设置，并且在运行任务调用 schedule()
   之前永远不会被清除。空闲线程只需要查询need_resched，并且永远不会设置或清除它。

3. 当cpu_idle发现（need_resched() == 'true'），它应该调用schedule()。否则
   它不应该调用schedule()。

4. 在检查need_resched时，唯一需要禁用中断的情况是，我们要让处理器休眠到下一个中
   断（这并不对need_resched提供任何保护，它可以防止丢失一个中断）:

	4a. 这种睡眠类型的常见问题似乎是::

	        local_irq_disable();
	        if (!need_resched()) {
	                local_irq_enable();
	                *** resched interrupt arrives here ***
	                __asm__("sleep until next interrupt");
	        }

5. 当need_resched变为高电平时，TIF_POLLING_NRFLAG可以由不需要中断来唤醒它们
   的空闲程序设置。换句话说，它们必须定期轮询need_resched，尽管做一些后台工作或
   进入低CPU优先级可能是合理的。

      - 5a. 如果TIF_POLLING_NRFLAG被设置，而我们确实决定进入一个中断睡眠，那
            么需要清除它，然后发出一个内存屏障（接着测试need_resched，禁用中断，如3中解释）。

arch/x86/kernel/process.c有轮询和睡眠空闲函数的例子。

可能出现的arch/问题
===================

我发现的可能的arch问题（并试图解决或没有解决）。:

ia64 - safe_halt的调用与中断相比，是否很荒谬？ (它睡眠了吗) (参考 #4a)

sh64 - 睡眠与中断相比，是否很荒谬？ (参考 #4a)

sparc - 在这一点上，IRQ是开着的（？），把local_irq_save改为_disable。
      - 待办事项: 需要第二个CPU来禁用抢占 (参考 #1)


code:
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/scheduler/sched-bwc.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

============
CFS 带宽控制
============

.. note::
   本文只讨论了SCHED_NORMAL的CPU带宽控制。
   SCHED_RT的情况在Documentation/scheduler/sched-rt-group.rst中有涉及。

CFS带宽控制是一个CONFIG_FAIR_GROUP_SCHED扩展，它允许指定一个组或层次的最大CPU带宽。

一个组允许的带宽是用配额和周期指定的。在每个给定的”周期“（微秒）内，一个任务组被分配多
达“配额”微秒的CPU时间。当cgroup中的线程可运行时，该配额以时间片段的方式被分配到每个cpu
运行队列中。一旦所有的配额被分配，任何额外的配额请求将导致这些线程被限流。被限流的线程将不
能再次运行，直到下一个时期的配额得到补充。

一个组的未分配配额是全局跟踪的，在每个周期边界被刷新为cfs_quota单元。当线程消耗这个带宽时，
它以需求为基础被转移到cpu-local“筒仓”，在每次更新中转移的数量是可调整的，被描述为“片“（时
间片）。

突发特性
--------
现在这个功能借来的时间是用于防范我们对未来的低估，代价是对其他系统用户的干扰增加。所有这些都
有很好的限制。

传统的（UP-EDF）带宽控制是这样的:

  (U = \Sum u_i) <= 1

这既保证了每个最后期限的实现，也保证了系统的稳定。毕竟，如果U>1，那么每一秒钟的壁钟时间，我
们就必须运行超过一秒钟的程序时间，显然会错过我们的最后期限，但下一个最后期限会更远，永远没有
时间赶上，无边无界的失败。

突发特性观察到工作负载并不总是执行全部配额；这使得人们可以将u_i描述为一个统计分布。

例如，让u_i = {x,e}_i，其中x是p(95)和x+e p(100)（传统的WCET）。这实际上允许u更小，提
高了效率（我们可以在系统中打包更多的任务），但代价是当所有的概率都一致时，会错过最后期限。然
而，它确实保持了稳定性，因为只要我们的x高于平均水平，每一次超限都必须与低估相匹配。

也就是说，假设我们有两个任务，都指定了一个p(95)值，那么我们有一个p(95)*p(95)=90.25%的机
会，两个任务都在他们的配额内，一切都很好。同时，我们有一个p(5)p(5)=0.25%的机会，两个任务同
时超过他们的配额（保证最后期限失败）。在这两者之间有一个阈值，其中一个超过了，而另一个没有不足，
无法补偿；这取决于具体的CDFs。

同时，我们可以说，最坏的情况下的截止日期失败，将是Sum e_i；也就是说，有一个有界的迟延（在假
设x+e确实是WCET的情况下）。

使用突发时的干扰是由错过最后期限的可能性和平均WCET来评价的。测试结果表明，当有许多cgroup或
CPU未被充分利用时，干扰是有限的。更多的细节显示在：
https://lore.kernel.org/lkml/5371BD36-55AE-4F71-B9D7-B86DC32E3D2B@linux.alibaba.com/

管理
----
配额、周期和突发是在cpu子系统内通过cgroupfs管理的。

.. note::
   本节描述的cgroupfs文件只适用于cgroup v1.对于cgroup v2，请参阅Control Group v2。
   :ref:`Documentation/admin-guide/cgroup-v2.rst <cgroup-v2-cpu>`.

- cpu.cfs_quota_us：在一个时期内补充的运行时间（微秒）。
- cpu.cfs_period_us：一个周期的长度（微秒）。
- cpu.stat: 输出节流统计数据[下面进一步解释]
- cpu.cfs_burst_us：最大累积运行时间（微秒）。

默认值是::

	cpu.cfs_period_us=100ms
	cpu.cfs_quota_us=-1
	cpu.cfs_burst_us=0

cpu.cfs_quota_us的值为-1表示该组没有任何带宽限制，这样的组被描述为无限制的带宽组。这代表
了CFS的传统工作保护行为。

写入不小于cpu.cfs_burst_us的任何（有效的）正值将配发指定的带宽限制。该配额或周期允许的最
小配额是1ms。周期长度也有一个1s的上限。当带宽限制以分层方式使用时，存在额外的限制，这些在下
面有更详细的解释。

向cpu.cfs_quota_us写入任何负值都会移除带宽限制，并使组再次回到无限制的状态。

cpu.cfs_burst_us的值为0表示该组不能积累任何未使用的带宽。它使得CFS的传统带宽控制行为没有
改变。将不大于 cpu.cfs_quota_us 的任何(有效的)正值写入 cpu.cfs_burst_us 将配发未使用
带宽累积的上限。

如果一个组处于受限状态，对该组带宽规格的任何更新都将导致其成为无限流状态。

系统范围设置
------------
为了提高效率，运行时间在全局池和CPU本地“筒仓”之间以批处理方式转移。这大大减少了大型系统的全
局核算压力。每次需要进行这种更新时，传输的数量被描述为 "片"。

这是可以通过procfs调整的::

	/proc/sys/kernel/sched_cfs_bandwidth_slice_us (default=5ms)

较大的时间片段值将减少传输开销，而较小的值则允许更精细的消费。

统计
----
一个组的带宽统计数据通过cpu.stat的5个字段导出。

cpu.stat:

- nr_periods：已经过去的执行间隔的数量。
- nr_throttled: 该组已被节流/限制的次数。
- throttled_time: 该组的实体被限流的总时间长度（纳秒）。
- nr_bursts：突发发生的周期数。
- burst_time: 任何CPU在各个时期使用超过配额的累计壁钟时间（纳秒）。

这个接口是只读的。

分层考虑
--------
该接口强制要求单个实体的带宽总是可以达到的，即：max(c_i) <= C。然而，在总体情况下，是明确
允许过度订阅的，以便在一个层次结构中实现工作保护语义:

  例如，Sum (c_i)可能超过C

[ 其中C是父方的带宽，c_i是其子方的带宽。 ]

.. note::
   译文中的父亲/孩子指的是cgroup parent, cgroup children。

有两种方式可以使一个组变得限流:

	a. 它在一段时期内完全消耗自己的配额
	b. 父方的配额在其期间内全部用完

在上述b)情况下，即使孩子可能有剩余的运行时间，它也不会被允许，直到父亲的运行时间被刷新。

CFS带宽配额的注意事项
---------------------
一旦一个片断被分配给一个cpu，它就不会过期。然而，如果该cpu上的所有线程都无法运行，那么除了
1ms以外的所有时间片都可以返回到全局池中。这是在编译时由min_cfs_rq_runtime变量配置的。这
是一个性能调整，有助于防止对全局锁的额外争夺。

cpu-local分片不会过期的事实导致了一些有趣的罕见案例，应该被理解。

对于cgroup cpu限制的应用程序来说，这是一个相对有意义的问题，因为他们自然会消耗他们的全部配
额，以及每个cpu-本地片在每个时期的全部。因此，预计nr_periods大致等于nr_throttled，并且
cpuacct.用量的增加大致等于cfs_quota_us在每个周期的增加。

对于高线程、非cpu绑定的应用程序，这种非过期的细微差别允许应用程序短暂地突破他们的配额限制，
即任务组正在运行的每个cpu上未使用的片断量（通常每个cpu最多1ms或由min_cfs_rq_runtime定
义）。这种轻微的突发只适用于配额已经分配给cpu，然后没有完全使用或在以前的时期返回。这个突发
量不会在核心之间转移。因此，这种机制仍然严格限制任务组的配额平均使用量，尽管是在比单一时期更
长的时间窗口。这也限制了突发能力，每个cpu不超过1ms。这为在高核数机器上有小配额限制的高线程
应用提供了更好的更可预测的用户体验。它还消除了在使用低于配额的cpu时对这些应用进行节流的倾向。
另一种说法是，通过允许一个片断的未使用部分在不同时期保持有效，我们减少了在不需要整个片断的cpu
时间的cpu-local 筒仓上浪费配额的可能性。

绑定cpu和非绑定cpu的交互式应用之间的互动也应该被考虑，特别是当单核使用率达到100%时。如果你
给了这些应用程序一半的cpu-core，并且它们都被安排在同一个CPU上，理论上非cpu绑定的应用程序有
可能在某些时期使用多达1ms的额外配额，从而阻止cpu绑定的应用程序完全使用其配额，这也是同样的数
量。在这些情况下，将由CFS算法（见CFS调度器）来决定选择哪个应用程序来运行，因为它们都是可运行
的，并且有剩余的配额。这个运行时间的差异将在接下来的交互式应用程序空闲期间得到弥补。

例子
----
1. 限制一个组的运行时间为1个CPU的价值::

	如果周期是250ms，配额也是250ms，那么该组将每250ms获得价值1个CPU的运行时间。

	# echo 250000 > cpu.cfs_quota_us /* quota = 250ms */
	# echo 250000 > cpu.cfs_period_us /* period = 250ms */

2. 在多CPU机器上，将一个组的运行时间限制为2个CPU的价值

   在500ms周期和1000ms配额的情况下，该组每500ms可以获得2个CPU的运行时间::

	# echo 1000000 > cpu.cfs_quota_us /* quota = 1000ms */
	# echo 500000 > cpu.cfs_period_us /* period = 500ms */

	这里较大的周期允许增加突发能力。

3. 将一个组限制在1个CPU的20%。

   在50ms周期内，10ms配额将相当于1个CPU的20%。::

	# echo 10000 > cpu.cfs_quota_us /* quota = 10ms */
	# echo 50000 > cpu.cfs_period_us /* period = 50ms */

   通过在这里使用一个小的周期，我们以牺牲突发容量为代价来确保稳定的延迟响应。

4. 将一个组限制在1个CPU的40%，并允许累积到1个CPU的20%，如果已经累积了的话。

   在50ms周期内，20ms配额将相当于1个CPU的40%。而10毫秒的突发将相当于1个
   CPU的20%::

	# echo 20000 > cpu.cfs_quota_us /* quota = 20ms */
	# echo 50000 > cpu.cfs_period_us /* period = 50ms */
	# echo 10000 > cpu.cfs_burst_us /* burst = 10ms */

   较大的缓冲区设置（不大于配额）允许更大的突发容量。


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/scheduler/sched-capacity.rst

:翻译:

  唐艺舟 Tang Yizhou <tangyeechou@gmail.com>

:校译:

  时奎亮 Alex Shi <alexs@kernel.org>

=============
算力感知调度
=============

1. CPU算力
==========

1.1 简介
--------

一般来说，同构的SMP平台由完全相同的CPU构成。异构的平台则由性能特征不同的CPU构成，在这样的
平台中，CPU不能被认为是相同的。

我们引入CPU算力（capacity）的概念来测量每个CPU能达到的性能，它的值相对系统中性能最强的CPU
做过归一化处理。异构系统也被称为非对称CPU算力系统，因为它们由不同算力的CPU组成。

最大可达性能（换言之，最大CPU算力）的差异有两个主要来源:

- 不是所有CPU的微架构都相同。
- 在动态电压频率升降（Dynamic Voltage and Frequency Scaling，DVFS）框架中，不是所有的CPU都
  能达到一样高的操作性能值（Operating Performance Points，OPP。译注，也就是“频率-电压”对）。

Arm大小核（big.LITTLE）系统是同时具有两种差异的一个例子。相较小核，大核面向性能（拥有更多的
流水线层级，更大的缓存，更智能的分支预测器等），通常可以达到更高的操作性能值。

CPU性能通常由每秒百万指令（Millions of Instructions Per Second，MIPS）表示，也可表示为
per Hz能执行的指令数，故::

  capacity(cpu) = work_per_hz(cpu) * max_freq(cpu)

1.2 调度器术语
--------------

调度器使用了两种不同的算力值。CPU的 ``capacity_orig`` 是它的最大可达算力，即最大可达性能等级。
CPU的 ``capacity`` 是 ``capacity_orig`` 扣除了一些性能损失（比如处理中断的耗时）的值。

注意CPU的 ``capacity`` 仅仅被设计用于CFS调度类，而 ``capacity_orig`` 是不感知调度类的。为
简洁起见，本文档的剩余部分将不加区分的使用术语 ``capacity`` 和 ``capacity_orig`` 。

1.3 平台示例
------------

1.3.1 操作性能值相同
~~~~~~~~~~~~~~~~~~~~

考虑一个假想的双核非对称CPU算力系统，其中

- work_per_hz(CPU0) = W
- work_per_hz(CPU1) = W/2
- 所有CPU以相同的固定频率运行

根据上文对算力的定义:

- capacity(CPU0) = C
- capacity(CPU1) = C/2

若这是Arm大小核系统，那么CPU0是大核，而CPU1是小核。

考虑一种周期性产生固定工作量的工作负载，你将会得到类似下图的执行轨迹::

 CPU0 work ^
           |     ____                ____                ____
           |    |    |              |    |              |    |
           +----+----+----+----+----+----+----+----+----+----+-> time

 CPU1 work ^
           |     _________           _________           ____
           |    |         |         |         |         |
           +----+----+----+----+----+----+----+----+----+----+-> time

CPU0在系统中具有最高算力（C），它使用T个单位时间完成固定工作量W。另一方面，CPU1只有CPU0一半
算力，因此在T个单位时间内仅完成工作量W/2。

1.3.2 最大操作性能值不同
~~~~~~~~~~~~~~~~~~~~~~~~

具有不同算力值的CPU，通常来说最大操作性能值也不同。考虑上一小节提到的CPU（也就是说，
work_per_hz()相同）:

- max_freq(CPU0) = F
- max_freq(CPU1) = 2/3 * F

这将推出：

- capacity(CPU0) = C
- capacity(CPU1) = C/3

执行1.3.1节描述的工作负载，每个CPU按最大频率运行，结果为::

 CPU0 work ^
           |     ____                ____                ____
           |    |    |              |    |              |    |
           +----+----+----+----+----+----+----+----+----+----+-> time

                            workload on CPU1
 CPU1 work ^
           |     ______________      ______________      ____
           |    |              |    |              |    |
           +----+----+----+----+----+----+----+----+----+----+-> time

1.4 关于计算方式的注意事项
--------------------------

需要注意的是，使用单一值来表示CPU性能的差异是有些争议的。两个不同的微架构的相对性能差异应该
描述为：X%整数运算差异，Y%浮点数运算差异，Z%分支跳转差异，等等。尽管如此，使用简单计算方式
的结果目前还是令人满意的。

2. 任务使用率
=============

2.1 简介
--------

算力感知调度要求描述任务需求，描述方式要和CPU算力相关。每个调度类可以用不同的方式描述它。
任务使用率是CFS独有的描述方式，不过在这里介绍它有助于引入更多一般性的概念。

任务使用率是一种用百分比来描述任务吞吐率需求的方式。一个简单的近似是任务的占空比，也就是说::

  task_util(p) = duty_cycle(p)

在频率固定的SMP系统中，100%的利用率意味着任务是忙等待循环。反之，10%的利用率暗示这是一个
小周期任务，它在睡眠上花费的时间比执行更多。

2.2 频率不变性
--------------

一个需要考虑的议题是，工作负载的占空比受CPU正在运行的操作性能值直接影响。考虑以给定的频率F
执行周期性工作负载::

  CPU work ^
           |     ____                ____                ____
           |    |    |              |    |              |    |
           +----+----+----+----+----+----+----+----+----+----+-> time

可以算出 duty_cycle(p) == 25%。

现在，考虑以给定频率F/2执行 *同一个* 工作负载::

  CPU work ^
           |     _________           _________           ____
           |    |         |         |         |         |
           +----+----+----+----+----+----+----+----+----+----+-> time

可以算出 duty_cycle(p) == 50%，尽管两次执行中，任务的行为完全一致（也就是说，执行的工作量
相同）。

任务利用率信号可按下面公式处理成频率不变的（译注：这里的术语用到了信号与系统的概念）::

  task_util_freq_inv(p) = duty_cycle(p) * (curr_frequency(cpu) / max_frequency(cpu))

对上面两个例子运用该公式，可以算出频率不变的任务利用率均为25%。

2.3 CPU不变性
-------------

CPU算力与任务利用率具有类型的效应，在算力不同的CPU上执行完全相同的工作负载，将算出不同的
占空比。

考虑1.3.2节提到的系统，也就是说::

- capacity(CPU0) = C
- capacity(CPU1) = C/3

每个CPU按最大频率执行指定周期性工作负载，结果为::

 CPU0 work ^
           |     ____                ____                ____
           |    |    |              |    |              |    |
           +----+----+----+----+----+----+----+----+----+----+-> time

 CPU1 work ^
           |     ______________      ______________      ____
           |    |              |    |              |    |
           +----+----+----+----+----+----+----+----+----+----+-> time

也就是说，

- duty_cycle(p) == 25%，如果任务p在CPU0上按最大频率运行。
- duty_cycle(p) == 75%，如果任务p在CPU1上按最大频率运行。

任务利用率信号可按下面公式处理成CPU算力不变的::

  task_util_cpu_inv(p) = duty_cycle(p) * (capacity(cpu) / max_capacity)

其中 ``max_capacity`` 是系统中最高的CPU算力。对上面的例子运用该公式，可以算出CPU算力不变
的任务利用率均为25%。

2.4 任务利用率不变量
--------------------

频率和CPU算力不变性都需要被应用到任务利用率的计算中，以便求出真正的不变信号。
任务利用率的伪计算公式是同时具备CPU和频率不变性的，也就是说，对于指定任务p::

                                     curr_frequency(cpu)   capacity(cpu)
  task_util_inv(p) = duty_cycle(p) * ------------------- * -------------
                                     max_frequency(cpu)    max_capacity

也就是说，任务利用率不变量假定任务在系统中最高算力CPU上以最高频率运行，以此描述任务的行为。

在接下来的章节中提到的任何任务利用率，均是不变量的形式。

2.5 利用率估算
--------------

由于预测未来的水晶球不存在，当任务第一次变成可运行时，任务的行为和任务利用率均不能被准确预测。
CFS调度类基于实体负载跟踪机制（Per-Entity Load Tracking, PELT）维护了少量CPU和任务信号，
其中之一可以算出平均利用率（与瞬时相反）。

这意味着，尽管运用“真实的”任务利用率（凭借水晶球）写出算力感知调度的准则，但是它的实现将只能
用任务利用率的估算值。

3. 算力感知调度的需求
=====================

3.1 CPU算力
-----------

当前，Linux无法凭自身算出CPU算力，因此必须要有把这个信息传递给Linux的方式。每个架构必须为此
定义arch_scale_cpu_capacity()函数。

arm和arm64架构直接把这个信息映射到arch_topology驱动的CPU scaling数据中（译注：参考
arch_topology.h的percpu变量cpu_scale），它是从capacity-dmips-mhz CPU binding中衍生计算
出来的。参见Documentation/devicetree/bindings/arm/cpu-capacity.txt。

3.2 频率不变性
--------------

如2.2节所述，算力感知调度需要频率不变的任务利用率。每个架构必须为此定义
arch_scale_freq_capacity(cpu)函数。

实现该函数要求计算出每个CPU当前以什么频率在运行。实现它的一种方式是利用硬件计数器（x86的
APERF/MPERF，arm64的AMU），它能按CPU当前频率动态可扩展地升降递增计数器的速率。另一种方式是
在cpufreq频率变化时直接使用钩子函数，内核此时感知到将要被切换的频率（也被arm/arm64实现了）。

4. 调度器拓扑结构
=================

在构建调度域时，调度器将会发现系统是否表现为非对称CPU算力。如果是，那么：

- sched_asym_cpucapacity静态键（static key）将使能。
- SD_ASYM_CPUCAPACITY_FULL标志位将在尽量最低调度域层级中被设置，同时要满足条件：调度域恰好
  完整包含某个CPU算力值的全部CPU。
- SD_ASYM_CPUCAPACITY标志将在所有包含非对称CPU的调度域中被设置。

sched_asym_cpucapacity静态键的设计意图是，保护为非对称CPU算力系统所准备的代码。不过要注意的
是，这个键是系统范围可见的。想象下面使用了cpuset的步骤::

  capacity    C/2          C
            ________    ________
           /        \  /        \
  CPUs     0  1  2  3  4  5  6  7
           \__/  \______________/
  cpusets   cs0         cs1

可以通过下面的方式创建：

.. code-block:: sh

  mkdir /sys/fs/cgroup/cpuset/cs0
  echo 0-1 > /sys/fs/cgroup/cpuset/cs0/cpuset.cpus
  echo 0 > /sys/fs/cgroup/cpuset/cs0/cpuset.mems

  mkdir /sys/fs/cgroup/cpuset/cs1
  echo 2-7 > /sys/fs/cgroup/cpuset/cs1/cpuset.cpus
  echo 0 > /sys/fs/cgroup/cpuset/cs1/cpuset.mems

  echo 0 > /sys/fs/cgroup/cpuset/cpuset.sched_load_balance

由于“这是”非对称CPU算力系统，sched_asym_cpucapacity静态键将使能。然而，CPU 0--1对应的
调度域层级，算力值仅有一个，该层级中SD_ASYM_CPUCAPACITY未被设置，它描述的是一个SMP区域，也
应该被以此处理。

因此，“典型的”保护非对称CPU算力代码路径的代码模式是：

- 检查sched_asym_cpucapacity静态键
- 如果它被使能，接着检查调度域层级中SD_ASYM_CPUCAPACITY标志位是否出现

5. 算力感知调度的实现
=====================

5.1 CFS
-------

5.1.1 算力适应性（fitness）
~~~~~~~~~~~~~~~~~~~~~~~~~~~

CFS最主要的算力调度准则是::

  task_util(p) < capacity(task_cpu(p))

它通常被称为算力适应性准则。也就是说，CFS必须保证任务“适合”在某个CPU上运行。如果准则被违反，
任务将要更长地消耗该CPU，任务是CPU受限的（CPU-bound）。

此外，uclamp允许用户空间指定任务的最小和最大利用率，要么以sched_setattr()的方式，要么以
cgroup接口的方式（参阅Documentation/admin-guide/cgroup-v2.rst）。如其名字所暗示，uclamp
可以被用在前一条准则中限制task_util()。

5.1.2 被唤醒任务的CPU选择
~~~~~~~~~~~~~~~~~~~~~~~~~

CFS任务唤醒的CPU选择，遵循上面描述的算力适应性准则。在此之上，uclamp被用来限制任务利用率，
这令用户空间对CFS任务的CPU选择有更多的控制。也就是说，CFS被唤醒任务的CPU选择，搜索满足以下
条件的CPU::

  clamp(task_util(p), task_uclamp_min(p), task_uclamp_max(p)) < capacity(cpu)

通过使用uclamp，举例来说，用户空间可以允许忙等待循环（100%使用率）在任意CPU上运行，只要给
它设置低的uclamp.max值。相反，uclamp能强制一个小的周期性任务（比如，10%利用率）在最高性能
的CPU上运行，只要给它设置高的uclamp.min值。

.. note::

  CFS的被唤醒的任务的CPU选择，可被能耗感知调度（Energy Aware Scheduling，EAS）覆盖，在
  Documentation/scheduler/sched-energy.rst中描述。

5.1.3 负载均衡
~~~~~~~~~~~~~~

被唤醒任务的CPU选择的一个病理性的例子是，任务几乎不睡眠，那么也几乎不发生唤醒。考虑::

  w == wakeup event

  capacity(CPU0) = C
  capacity(CPU1) = C / 3

                           workload on CPU0
  CPU work ^
           |     _________           _________           ____
           |    |         |         |         |         |
           +----+----+----+----+----+----+----+----+----+----+-> time
                w                   w                   w

                           workload on CPU1
  CPU work ^
           |     ____________________________________________
           |    |
           +----+----+----+----+----+----+----+----+----+----+->
                w

该工作负载应该在CPU0上运行，不过如果任务满足以下条件之一：

- 一开始发生不合适的调度（不准确的初始利用率估计）
- 一开始调度正确，但突然需要更多的处理器功率

则任务可能变为CPU受限的，也就是说 ``task_util(p) > capacity(task_cpu(p))`` ；CPU算力
调度准则被违反，将不会有任何唤醒事件来修复这个错误的CPU选择。

这种场景下的任务被称为“不合适的”（misfit）任务，处理这个场景的机制同样也以此命名。Misfit
任务迁移借助CFS负载均衡器，更明确的说，是主动负载均衡的部分（用来迁移正在运行的任务）。
当发生负载均衡时，如果一个misfit任务可以被迁移到一个相较当前运行的CPU具有更高算力的CPU上，
那么misfit任务的主动负载均衡将被触发。

5.2 实时调度
------------

5.2.1 被唤醒任务的CPU选择
~~~~~~~~~~~~~~~~~~~~~~~~~

实时任务唤醒时的CPU选择，搜索满足以下条件的CPU::

  task_uclamp_min(p) <= capacity(task_cpu(cpu))

同时仍然允许接着使用常规的优先级限制。如果没有CPU能满足这个算力准则，那么将使用基于严格
优先级的调度，CPU算力将被忽略。

5.3 最后期限调度
----------------

5.3.1 被唤醒任务的CPU选择
~~~~~~~~~~~~~~~~~~~~~~~~~

最后期限任务唤醒时的CPU选择，搜索满足以下条件的CPU::

  task_bandwidth(p) < capacity(task_cpu(p))

同时仍然允许接着使用常规的带宽和截止期限限制。如果没有CPU能满足这个算力准则，那么任务依然
在当前CPU队列中。


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/scheduler/sched-debug.rst

:翻译:

  唐艺舟 Tang Yizhou <tangyeechou@gmail.com>

=============
调度器debugfs
=============

用配置项CONFIG_SCHED_DEBUG=y启动内核后，将可以访问/sys/kernel/debug/sched
下的调度器专用调试文件。其中一些文件描述如下。

numa_balancing
==============

`numa_balancing` 目录用来存放控制非统一内存访问（NUMA）平衡特性的相关文件。
如果该特性导致系统负载太高，那么可以通过 `scan_period_min_ms, scan_delay_ms,
scan_period_max_ms, scan_size_mb` 文件控制NUMA缺页的内核采样速率。

scan_period_min_ms, scan_delay_ms, scan_period_max_ms, scan_size_mb
-------------------------------------------------------------------

自动NUMA平衡会扫描任务地址空间，检测页面是否被正确放置，或者数据是否应该被
迁移到任务正在运行的本地内存结点，此时需解映射页面。每个“扫描延迟”（scan delay）
时间之后，任务扫描其地址空间中下一批“扫描大小”（scan size）个页面。若抵达
内存地址空间末尾，扫描器将从头开始重新扫描。

结合来看，“扫描延迟”和“扫描大小”决定扫描速率。当“扫描延迟”减小时，扫描速率
增加。“扫描延迟”和每个任务的扫描速率都是自适应的，且依赖历史行为。如果页面被
正确放置，那么扫描延迟就会增加；否则扫描延迟就会减少。“扫描大小”不是自适应的，
“扫描大小”越大，扫描速率越高。

更高的扫描速率会产生更高的系统开销，因为必须捕获缺页异常，并且潜在地必须迁移
数据。然而，当扫描速率越高，若工作负载模式发生变化，任务的内存将越快地迁移到
本地结点，由于远程内存访问而产生的性能影响将降到最低。下面这些文件控制扫描延迟
的阈值和被扫描的页面数量。

``scan_period_min_ms`` 是扫描一个任务虚拟内存的最小时间，单位是毫秒。它有效地
控制了每个任务的最大扫描速率。

``scan_delay_ms`` 是一个任务初始化创建（fork）时，第一次使用的“扫描延迟”。

``scan_period_max_ms`` 是扫描一个任务虚拟内存的最大时间，单位是毫秒。它有效地
控制了每个任务的最小扫描速率。

``scan_size_mb`` 是一次特定的扫描中，要扫描多少兆字节（MB）对应的页面数。


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/scheduler/sched-design-CFS.rst

:翻译:

  唐艺舟 Tang Yizhou <tangyeechou@gmail.com>

===============
完全公平调度器
===============

1. 概述
=======

CFS表示“完全公平调度器”，它是为桌面新设计的进程调度器，由Ingo Molnar实现并合入Linux
2.6.23。它替代了之前原始调度器中SCHED_OTHER策略的交互式代码。

CFS 80%的设计可以总结为一句话：CFS在真实硬件上建模了一个“理想的，精确的多任务CPU”。

“理想的多任务CPU”是一种（不存在的 :-)）具有100%物理算力的CPU，它能让每个任务精确地以
相同的速度并行运行，速度均为1/nr_running。举例来说，如果有两个任务正在运行，那么每个
任务获得50%物理算力。 --- 也就是说，真正的并行。

在真实的硬件上，一次只能运行一个任务，所以我们需要介绍“虚拟运行时间”的概念。任务的虚拟
运行时间表明，它的下一个时间片将在上文描述的理想多任务CPU上开始执行。在实践中，任务的
虚拟运行时间由它的真实运行时间相较正在运行的任务总数归一化计算得到。

2. 一些实现细节
===============

在CFS中，虚拟运行时间由每个任务的p->se.vruntime（单位为纳秒）的值表达和跟踪。因此，
精确地计时和测量一个任务应得的“预期的CPU时间”是可能的。

  一些细节：在“理想的”硬件上，所有的任务在任何时刻都应该具有一样的p->se.vruntime值，
  --- 也就是说，任务应当同时执行，没有任务会在“理想的”CPU分时中变得“不平衡”。

CFS的任务选择逻辑基于p->se.vruntime的值，因此非常简单：总是试图选择p->se.vruntime值
最小的任务运行（也就是说，至今执行时间最少的任务）。CFS总是尽可能尝试按“理想多任务硬件”
那样将CPU时间在可运行任务中均分。

CFS剩下的其它设计，一般脱离了这个简单的概念，附加的设计包括nice级别，多处理，以及各种
用来识别已睡眠任务的算法变体。

3. 红黑树
=========

CFS的设计非常激进：它不使用运行队列的旧数据结构，而是使用按时间排序的红黑树，构建出
任务未来执行的“时间线”。因此没有任何“数组切换”的旧包袱（之前的原始调度器和RSDL/SD都
被它影响）。

CFS同样维护了rq->cfs.min_vruntime值，它是单调递增的，跟踪运行队列中的所有任务的最小
虚拟运行时间值。系统做的全部工作是：使用min_vruntime跟踪，然后用它的值将新激活的调度
实体尽可能地放在红黑树的左侧。

运行队列中正在运行的任务的总数由rq->cfs.load计数，它是运行队列中的任务的权值之和。

CFS维护了一个按时间排序的红黑树，所有可运行任务以p->se.vruntime为键值排序。CFS从这颗
树上选择“最左侧”的任务并运行。系统继续运行，被执行过的任务越来越被放到树的右侧 --- 缓慢，
但很明确每个任务都有成为“最左侧任务”的机会，因此任务将确定性地获得一定量CPU时间。

总结一下，CFS工作方式像这样：它运行一个任务一会儿，当任务发生调度（或者调度器时钟滴答
tick产生），就会考虑任务的CPU使用率：任务刚刚花在物理CPU上的（少量）时间被加到
p->se.vruntime。一旦p->se.vruntime变得足够大，其它的任务将成为按时间排序的红黑树的
“最左侧任务”（相较最左侧的任务，还要加上一个很小的“粒度”量，使得我们不会对任务过度调度，
导致缓存颠簸），然后新的最左侧任务将被选中，当前任务被抢占。

4. CFS的一些特征
================

CFS使用纳秒粒度的计时，不依赖于任何jiffies或HZ的细节。因此CFS并不像之前的调度器那样
有“时间片”的概念，也没有任何启发式的设计。唯一可调的参数（你需要打开CONFIG_SCHED_DEBUG）是：

   /sys/kernel/debug/sched/min_granularity_ns

它可以用来将调度器从“桌面”模式（也就是低时延）调节为“服务器”（也就是高批处理）模式。
它的默认设置是适合桌面的工作负载。SCHED_BATCH也被CFS调度器模块处理。

CFS的设计不易受到当前存在的任何针对stock调度器的“攻击”的影响，包括fiftyp.c，thud.c，
chew.c，ring-test.c，massive_intr.c，它们都能很好地运行，不会影响交互性，将产生
符合预期的行为。

CFS调度器处理nice级别和SCHED_BATCH的能力比之前的原始调度器更强：两种类型的工作负载
都被更激进地隔离了。

SMP负载均衡被重做/清理过：遍历运行队列的假设已经从负载均衡的代码中移除，使用调度模块
的迭代器。结果是，负载均衡代码变得简单不少。

5. 调度策略
===========

CFS实现了三种调度策略：

  - SCHED_NORMAL：（传统被称为SCHED_OTHER）：该调度策略用于普通任务。

  - SCHED_BATCH：抢占不像普通任务那样频繁，因此允许任务运行更长时间，更好地利用缓存，
    不过要以交互性为代价。它很适合批处理工作。

  - SCHED_IDLE：它比nice 19更弱，不过它不是真正的idle定时器调度器，因为要避免给机器
    带来死锁的优先级反转问题。

SCHED_FIFO/_RR被实现在sched/rt.c中，它们由POSIX具体说明。

util-linux-ng 2.13.1.1中的chrt命令可以设置以上所有策略，除了SCHED_IDLE。

6. 调度类
=========

新的CFS调度器被设计成支持“调度类”，一种调度模块的可扩展层次结构。这些模块封装了调度策略
细节，由调度器核心代码处理，且无需对它们做太多假设。

sched/fair.c 实现了上文描述的CFS调度器。

sched/rt.c 实现了SCHED_FIFO和SCHED_RR语义，且比之前的原始调度器更简洁。它使用了100个
运行队列（总共100个实时优先级，替代了之前调度器的140个），且不需要过期数组（expired
array）。

调度类由sched_class结构体实现，它包括一些函数钩子，当感兴趣的事件发生时，钩子被调用。

这是（部分）钩子的列表：

 - enqueue_task(...)

   当任务进入可运行状态时，被调用。它将调度实体（任务）放到红黑树中，增加nr_running变量
   的值。

 - dequeue_task(...)

   当任务不再可运行时，这个函数被调用，对应的调度实体被移出红黑树。它减少nr_running变量
   的值。

 - yield_task(...)

   这个函数的行为基本上是出队，紧接着入队，除非compat_yield sysctl被开启。在那种情况下，
   它将调度实体放在红黑树的最右端。

 - check_preempt_curr(...)

   这个函数检查进入可运行状态的任务能否抢占当前正在运行的任务。

 - pick_next_task(...)

   这个函数选择接下来最适合运行的任务。

 - set_curr_task(...)

   这个函数在任务改变调度类或改变任务组时被调用。

 - task_tick(...)

   这个函数最常被时间滴答函数调用，它可能导致进程切换。这驱动了运行时抢占。

7. CFS的组调度扩展
==================

通常，调度器操作粒度为任务，努力为每个任务提供公平的CPU时间。有时可能希望将任务编组，
并为每个组提供公平的CPU时间。举例来说，可能首先希望为系统中的每个用户提供公平的CPU
时间，接下来才是某个用户的每个任务。

CONFIG_CGROUP_SCHED 力求实现它。它将任务编组，并为这些组公平地分配CPU时间。

CONFIG_RT_GROUP_SCHED 允许将实时（也就是说，SCHED_FIFO和SCHED_RR）任务编组。

CONFIG_FAIR_GROUP_SCHED 允许将CFS（也就是说，SCHED_NORMAL和SCHED_BATCH）任务编组。

   这些编译选项要求CONFIG_CGROUPS被定义，然后管理员能使用cgroup伪文件系统任意创建任务组。
   关于该文件系统的更多信息，参见Documentation/admin-guide/cgroup-v1/cgroups.rst

当CONFIG_FAIR_GROUP_SCHED被定义后，通过伪文件系统，每个组被创建一个“cpu.shares”文件。
参见下面的例子来创建任务组，并通过“cgroup”伪文件系统修改它们的CPU份额::

	# mount -t tmpfs cgroup_root /sys/fs/cgroup
	# mkdir /sys/fs/cgroup/cpu
	# mount -t cgroup -ocpu none /sys/fs/cgroup/cpu
	# cd /sys/fs/cgroup/cpu

	# mkdir multimedia	# 创建 "multimedia" 任务组
	# mkdir browser		# 创建 "browser" 任务组

	# #配置multimedia组，令其获得browser组两倍CPU带宽

	# echo 2048 > multimedia/cpu.shares
	# echo 1024 > browser/cpu.shares

	# firefox &	# 启动firefox并把它移到 "browser" 组
	# echo <firefox_pid> > browser/tasks

	# #启动gmplayer（或者你最喜欢的电影播放器）
	# echo <movie_player_pid> > multimedia/tasks


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/scheduler/sched-domains.rst

:翻译:

  唐艺舟 Tang Yizhou <tangyeechou@gmail.com>

:校译:

  司延腾 Yanteng Si <siyanteng@loongson.cn>

======
调度域
======

每个CPU有一个“基”调度域（struct sched_domain）。调度域层次结构从基调度域构建而来，可
通过->parent指针自下而上遍历。->parent必须以NULL结尾，调度域结构体必须是per-CPU的，
因为它们无锁更新。

每个调度域管辖数个CPU（存储在->span字段中）。一个调度域的span必须是它的子调度域span的
超集（如有需求出现，这个限制可以放宽）。CPU i的基调度域必须至少管辖CPU i。每个CPU的
顶层调度域通常将会管辖系统中的全部CPU，尽管严格来说这不是必须的，假如是这样，会导致某些
CPU出现永远不会被指定任务运行的情况，直到允许的CPU掩码被显式设定。调度域的span字段意味
着“在这些CPU中做进程负载均衡”。

每个调度域必须具有一个或多个CPU调度组（struct sched_group），它们以单向循环链表的形式
组织，存储在->groups指针中。这些组的CPU掩码的并集必须和调度域span字段一致。->groups
指针指向的这些组包含的CPU，必须被调度域管辖。组包含的是只读数据，被创建之后，可能被多个
CPU共享。任意两个组的CPU掩码的交集不一定为空，如果是这种情况，对应调度域的SD_OVERLAP
标志位被设置，它管辖的调度组可能不能在多个CPU中共享。

调度域中的负载均衡发生在调度组中。也就是说，每个组被视为一个实体。组的负载被定义为它
管辖的每个CPU的负载之和。仅当组的负载不均衡后，任务才在组之间发生迁移。

在kernel/sched/core.c中，trigger_load_balance()在每个CPU上通过scheduler_tick()
周期执行。在当前运行队列下一个定期调度再平衡事件到达后，它引发一个软中断。负载均衡真正
的工作由run_rebalance_domains()->rebalance_domains()完成，在软中断上下文中执行
（SCHED_SOFTIRQ）。

后一个函数有两个入参：当前CPU的运行队列、它在scheduler_tick()调用时是否空闲。函数会从
当前CPU所在的基调度域开始迭代执行，并沿着parent指针链向上进入更高层级的调度域。在迭代
过程中，函数会检查当前调度域是否已经耗尽了再平衡的时间间隔，如果是，它在该调度域运行
load_balance()。接下来它检查父调度域（如果存在），再后来父调度域的父调度域，以此类推。

起初，load_balance()查找当前调度域中最繁忙的调度组。如果成功，在该调度组管辖的全部CPU
的运行队列中找出最繁忙的运行队列。如能找到，对当前的CPU运行队列和新找到的最繁忙运行
队列均加锁，并把任务从最繁忙队列中迁移到当前CPU上。被迁移的任务数量等于在先前迭代执行
中计算出的该调度域的调度组的不均衡值。

实现调度域
==========

基调度域会管辖CPU层次结构中的第一层。对于超线程（SMT）而言，基调度域将会管辖同一个物理
CPU的全部虚拟CPU，每个虚拟CPU对应一个调度组。

在SMP中，基调度域的父调度域将会管辖同一个结点中的全部物理CPU，每个调度组对应一个物理CPU。
接下来，如果是非统一内存访问（NUMA）系统，SMP调度域的父调度域将管辖整个机器，一个结点的
CPU掩码对应一个调度组。亦或，你可以使用多级NUMA；举例来说Opteron处理器，可能仅用一个
调度域来覆盖它的一个NUMA层级。

实现者需要阅读include/linux/sched/sd_flags.h的注释：读SD_*来了解具体情况以及调度域的
SD标志位调节了哪些东西。

体系结构可以把指定的拓扑层级的通用调度域构建器和默认的SD标志位覆盖掉，方法是创建一个
sched_domain_topology_level数组，并以该数组作为入参调用set_sched_topology()。

调度域调试基础设施可以通过CONFIG_SCHED_DEBUG开启，并在开机启动命令行中增加
“sched_verbose”。如果你忘记调整开机启动命令行了，也可以打开
/sys/kernel/debug/sched/verbose开关。这将开启调度域错误检查的解析，它应该能捕获（上文
描述过的）绝大多数错误，同时以可视化格式打印调度域的结构。


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/scheduler/sched-energy.rst

:翻译:

  唐艺舟 Tang Yizhou <tangyeechou@gmail.com>

============
能量感知调度
============

1. 简介
-------

能量感知调度（EAS）使调度器有能力预测其决策对CPU所消耗的能量的影响。EAS依靠
一个能量模型（EM）来为每个任务选择一个节能的CPU，同时最小化对吞吐率的影响。
本文档致力于介绍介绍EAS是如何工作的，它背后的主要设计决策是什么，以及使其运行
所需的条件细节。

在进一步阅读之前，请注意，在撰写本文时::

   /!\ EAS不支持对称CPU拓扑的平台 /!\

EAS只在异构CPU拓扑结构（如Arm大小核，big.LITTLE）上运行。因为在这种情况下，
通过调度来节约能量的潜力是最大的。

EAS实际使用的EM不是由调度器维护的，而是一个专门的框架。关于这个框架的细节和
它提供的内容，请参考其文档（见Documentation/power/energy-model.rst）。

2. 背景和术语
-------------

从一开始就说清楚定义:
 - 能量 = [焦耳] （比如供电设备上的电池提供的资源）
 - 功率 = 能量/时间 = [焦耳/秒] = [瓦特]

 EAS的目标是最小化能量消耗，同时仍能将工作完成。也就是说，我们要最大化::

	性能 [指令数/秒]
	----------------
	   功率 [瓦特]

它等效于最小化::

	能量 [焦耳]
	-----------
	   指令数

同时仍然获得“良好”的性能。当前调度器只考虑性能目标，因此该式子本质上是一个
可选的优化目标，它同时考虑了两个目标：能量效率和性能。

引入EM的想法是为了让调度器评估其决策的影响，而不是盲目地应用可能仅在部分
平台有正面效果的节能技术。同时，EM必须尽可能的简单，以最小化调度器的时延
影响。

简而言之，EAS改变了CFS任务分配给CPU的方式。当调度器决定一个任务应该在哪里
运行时（在唤醒期间），EM被用来在不损害系统吞吐率的情况下，从几个较好的候选
CPU中挑选一个经预测能量消耗最优的CPU。EAS的预测依赖于对平台拓扑结构特定元素
的了解，包括CPU的“算力”，以及它们各自的能量成本。

3. 拓扑信息
-----------

EAS（以及调度器的剩余部分）使用“算力”的概念来区分不同计算吞吐率的CPU。一个
CPU的“算力”代表了它在最高频率下运行时能完成的工作量，且这个值是相对系统中
算力最大的CPU而言的。算力值被归一化为1024以内，并且可与由实体负载跟踪
（PELT）机制算出的利用率信号做对比。由于有算力值和利用率值，EAS能够估计一个
任务/CPU有多大/有多忙，并在评估性能与能量时将其考虑在内。CPU算力由特定体系
结构实现的arch_scale_cpu_capacity()回调函数提供。

EAS使用的其余平台信息是直接从能量模型（EM）框架中读取的。一个平台的EM是一张
表，表中每项代表系统中一个“性能域”的功率成本。（若要了解更多关于性能域的细节，
见Documentation/power/energy-model.rst）

当调度域被建立或重新建立时，调度器管理对拓扑代码中EM对象的引用。对于每个根域
（rd），调度器维护一个与当前rd->span相交的所有性能域的单向链表。链表中的每个
节点都包含一个指向EM框架所提供的结构体em_perf_domain的指针。

链表被附加在根域上，以应对独占的cpuset的配置。由于独占的cpuset的边界不一定与
性能域的边界一致，不同根域的链表可能包含重复的元素。

示例1
    让我们考虑一个有12个CPU的平台，分成3个性能域，（pd0，pd4和pd8），按以下
    方式组织::

	          CPUs:   0 1 2 3 4 5 6 7 8 9 10 11
	          PDs:   |--pd0--|--pd4--|---pd8---|
	          RDs:   |----rd1----|-----rd2-----|

    现在，考虑用户空间决定将系统分成两个独占的cpusets，因此创建了两个独立的根域，
    每个根域包含6个CPU。这两个根域在上图中被表示为rd1和rd2。由于pd4与rd1和rd2
    都有交集，它将同时出现于附加在这两个根域的“->pd”链表中:

       * rd1->pd: pd0 -> pd4
       * rd2->pd: pd4 -> pd8

    请注意，调度器将为pd4创建两个重复的链表节点（每个链表中各一个）。然而这
    两个节点持有指向同一个EM框架的共享数据结构的指针。

由于对这些链表的访问可能与热插拔及其它事件并发发生，因此它们受RCU锁保护，就像
被调度器操控的拓扑结构体中剩下字段一样。

EAS同样维护了一个静态键（sched_energy_present），当至少有一个根域满足EAS
启动的所有条件时，这个键就会被启动。在第6节中总结了这些条件。

4. 能量感知任务放置
-------------------

EAS覆盖了CFS的任务唤醒平衡代码。在唤醒平衡时，它使用平台的EM和PELT信号来选择节能
的目标CPU。当EAS被启用时，select_task_rq_fair()调用find_energy_efficient_cpu()
来做任务放置决定。这个函数寻找在每个性能域中寻找具有最高剩余算力（CPU算力 - CPU
利用率）的CPU，因为它能让我们保持最低的频率。然后，该函数检查将任务放在新CPU相较
依然放在之前活动的prev_cpu是否可以节省能量。

如果唤醒的任务被迁移，find_energy_efficient_cpu()使用compute_energy()来估算
系统将消耗多少能量。compute_energy()检查各CPU当前的利用率情况，并尝试调整来
“模拟”任务迁移。EM框架提供了API em_pd_energy()计算每个性能域在给定的利用率条件
下的预期能量消耗。

下面详细介绍一个优化能量消耗的任务放置决策的例子。

示例2
    让我们考虑一个有两个独立性能域的（伪）平台，每个性能域含有2个CPU。CPU0和CPU1
    是小核，CPU2和CPU3是大核。

    调度器必须决定将任务P放在哪个CPU上，这个任务的util_avg = 200（平均利用率），
    prev_cpu = 0（上一次运行在CPU0）。

    目前CPU的利用率情况如下图所示。CPU 0-3的util_avg分别为400、100、600和500。
    每个性能域有三个操作性能值（OPP）。与每个OPP相关的CPU算力和功率成本列在能量
    模型表中。P的util_avg在图中显示为"PP"::

     CPU util.
      1024                 - - - - - - -              Energy Model
                                               +-----------+-------------+
                                               |  Little   |     Big     |
       768                 =============       +-----+-----+------+------+
                                               | Cap | Pwr | Cap  | Pwr  |
                                               +-----+-----+------+------+
       512  ===========    - ##- - - - -       | 170 | 50  | 512  | 400  |
                             ##     ##         | 341 | 150 | 768  | 800  |
       341  -PP - - - -      ##     ##         | 512 | 300 | 1024 | 1700 |
             PP              ##     ##         +-----+-----+------+------+
       170  -## - - - -      ##     ##
             ##     ##       ##     ##
           ------------    -------------
            CPU0   CPU1     CPU2   CPU3

      Current OPP: =====       Other OPP: - - -     util_avg (100 each): ##

    find_energy_efficient_cpu()将首先在两个性能域中寻找具有最大剩余算力的CPU。
    在这个例子中是CPU1和CPU3。然后，它将估算，当P被放在它们中的任意一个时，系统的
    能耗，并检查这样做是否会比把P放在CPU0上节省一些能量。EAS假定OPPs遵循利用率
    （这与CPUFreq监管器schedutil的行为一致。关于这个问题的更多细节，见第6节）。

    **情况1. P被迁移到CPU1**::

      1024                 - - - - - - -

                                            Energy calculation:
       768                 =============     * CPU0: 200 / 341 * 150 = 88
                                             * CPU1: 300 / 341 * 150 = 131
                                             * CPU2: 600 / 768 * 800 = 625
       512  - - - - - -    - ##- - - - -     * CPU3: 500 / 768 * 800 = 520
                             ##     ##          => total_energy = 1364
       341  ===========      ##     ##
                    PP       ##     ##
       170  -## - - PP-      ##     ##
             ##     ##       ##     ##
           ------------    -------------
            CPU0   CPU1     CPU2   CPU3

    **情况2. P被迁移到CPU3**::

      1024                 - - - - - - -

                                            Energy calculation:
       768                 =============     * CPU0: 200 / 341 * 150 = 88
                                             * CPU1: 100 / 341 * 150 = 43
                                    PP       * CPU2: 600 / 768 * 800 = 625
       512  - - - - - -    - ##- - -PP -     * CPU3: 700 / 768 * 800 = 729
                             ##     ##          => total_energy = 1485
       341  ===========      ##     ##
                             ##     ##
       170  -## - - - -      ##     ##
             ##     ##       ##     ##
           ------------    -------------
            CPU0   CPU1     CPU2   CPU3

    **情况3. P依旧留在prev_cpu/CPU0**::

      1024                 - - - - - - -

                                            Energy calculation:
       768                 =============     * CPU0: 400 / 512 * 300 = 234
                                             * CPU1: 100 / 512 * 300 = 58
                                             * CPU2: 600 / 768 * 800 = 625
       512  ===========    - ##- - - - -     * CPU3: 500 / 768 * 800 = 520
                             ##     ##          => total_energy = 1437
       341  -PP - - - -      ##     ##
             PP              ##     ##
       170  -## - - - -      ##     ##
             ##     ##       ##     ##
           ------------    -------------
            CPU0   CPU1     CPU2   CPU3

    从这些计算结果来看，情况1的总能量最低。所以从节约能量的角度看，CPU1是最佳候选
    者。

大核通常比小核更耗电，因此主要在任务不适合在小核运行时使用。然而，小核并不总是比
大核节能。举例来说，对于某些系统，小核的高OPP可能比大核的低OPP能量消耗更高。因此，
如果小核在某一特定时间点刚好有足够的利用率，在此刻被唤醒的小任务放在大核执行可能
会更节能，尽管它在小核上运行也是合适的。

即使在大核所有OPP都不如小核OPP节能的情况下，在某些特定条件下，令小任务运行在大核
上依然可能节能。事实上，将一个任务放在一个小核上可能导致整个性能域的OPP提高，这将
增加已经在该性能域运行的任务的能量成本。如果唤醒的任务被放在一个大核上，它的执行
成本可能比放在小核上更高，但这不会影响小核上的其它任务，这些任务将继续以较低的OPP
运行。因此，当考虑CPU消耗的总能量时，在大核上运行一个任务的额外成本可能小于为所有
其它运行在小核的任务提高OPP的成本。

上面的例子几乎不可能以一种通用的方式得到正确的结果；同时，对于所有平台，在不知道
系统所有CPU每个不同OPP的运行成本时，也无法得到正确的结果。得益于基于EM的设计，
EAS应该能够正确处理这些问题而不会引发太多麻烦。然而，为了确保对高利用率场景的
吞吐率造成的影响最小化，EAS同样实现了另外一种叫“过度利用率”的机制。

5. 过度利用率
-------------

从一般的角度来看，EAS能提供最大帮助的是那些涉及低、中CPU利用率的使用场景。每当CPU
密集型的长任务运行，它们将需要所有的可用CPU算力，调度器将没有什么办法来节省能量同时
又不损害吞吐率。为了避免EAS损害性能，一旦CPU被使用的算力超过80%，它将被标记为“过度
利用”。只要根域中没有CPU是过度利用状态，负载均衡被禁用，而EAS将覆盖唤醒平衡代码。
EAS很可能将负载放置在系统中能量效率最高的CPU而不是其它CPU上，只要不损害吞吐率。
因此，负载均衡器被禁用以防止它打破EAS发现的节能任务放置。当系统未处于过度利用状态时，
这样做是安全的，因为低于80%的临界点意味着：

    a. 所有的CPU都有一些空闲时间，所以EAS使用的利用率信号很可能准确地代表各种任务
       的“大小”。
    b. 所有任务，不管它们的nice值是多大，都应该被提供了足够多的CPU算力。
    c. 既然有多余的算力，那么所有的任务都必须定期阻塞/休眠，在唤醒时进行平衡就足够
       了。

只要一个CPU利用率超过80%的临界点，上述三个假设中至少有一个是不正确的。在这种情况下，
整个根域的“过度利用”标志被设置，EAS被禁用，负载均衡器被重新启用。通过这样做，调度器
又回到了在CPU密集的条件下基于负载的算法做负载均衡。这更好地尊重了任务的nice值。

由于过度利用率的概念在很大程度上依赖于检测系统中是否有一些空闲时间，所以必须考虑
（比CFS）更高优先级的调度类（以及中断）“窃取”的CPU算力。像这样，对过度使用率的检测
不仅要考虑CFS任务使用的算力，还需要考虑其它调度类和中断。

6. EAS的依赖和要求
------------------

能量感知调度依赖系统的CPU具有特定的硬件属性，以及内核中的其它特性被启用。本节列出
了这些依赖，并对如何满足这些依赖提供了提示。

6.1 - 非对称CPU拓扑
^^^^^^^^^^^^^^^^^^^

如简介所提，目前只有非对称CPU拓扑结构的平台支持EAS。通过在运行时查询
SD_ASYM_CPUCAPACITY_FULL标志位是否在创建调度域时已设置来检查这一要求是否满足。

参阅Documentation/scheduler/sched-capacity.rst以了解在sched_domain层次结构
中设置此标志位所需满足的要求。

请注意，EAS并非从根本上与SMP不兼容，但在SMP平台上还没有观察到明显的节能。这一
限制可以在将来进行修改，如果被证明不是这样的话。

6.2 - 当前的能量模型
^^^^^^^^^^^^^^^^^^^^

EAS使用一个平台的EM来估算调度决策对能量的影响。因此，你的平台必须向EM框架提供
能量成本表，以启动EAS。要做到这一点，请参阅文档
Documentation/power/energy-model.rst中的独立EM框架部分。

另请注意，调度域需要在EM注册后重建，以便启动EAS。

EAS使用EM对能量使用率进行预测决策，因此它在检查任务放置的可能选项时更加注重
差异。对于EAS来说，EM的功率值是以毫瓦还是以“抽象刻度”为单位表示并不重要。

6.3 - 能量模型复杂度
^^^^^^^^^^^^^^^^^^^^

任务唤醒路径是时延敏感的。当一个平台的EM太复杂（太多CPU，太多性能域，太多状态
等），在唤醒路径中使用它的成本就会升高到不可接受。能量感知唤醒算法的复杂度为：

	C = Nd * (Nc + Ns)

其中：Nd是性能域的数量；Nc是CPU的数量；Ns是OPP的总数（例如：对于两个性能域，
每个域有4个OPP，则Ns = 8）。

当调度域建立时，复杂性检查是在根域上进行的。如果一个根域的复杂度C恰好高于完全
主观设定的EM_MAX_COMPLEXITY阈值（在本文写作时，是2048），则EAS不会在此根域
启动。

如果你的平台的能量模型的复杂度太高，EAS无法在这个根域上使用，但你真的想用，
那么你就只剩下两个可能的选择：

    1. 将你的系统拆分成分离的、较小的、使用独占cpuset的根域，并在每个根域局部
       启用EAS。这个方案的好处是开箱即用，但缺点是无法在根域之间实现负载均衡，
       这可能会导致总体系统负载不均衡。
    2. 提交补丁以降低EAS唤醒算法的复杂度，从而使其能够在合理的时间内处理更大
       的EM。

6.4 - Schedutil监管器
^^^^^^^^^^^^^^^^^^^^^

EAS试图预测CPU在不久的将来会在哪个OPP下运行，以估算它们的能量消耗。为了做到
这一点，它假定CPU的OPP跟随CPU利用率变化而变化。

尽管在实践中很难对这一假设的准确性提供硬性保证（因为，举例来说硬件可能不会做
它被要求做的事情），相对于其他CPUFreq监管器，schedutil至少_请求_使用利用率
信号计算的频率。因此，与EAS一起使用的唯一合理的监管器是schedutil，因为它是
唯一一个在频率请求和能量预测之间提供某种程度的一致性的监管器。

不支持将EAS与schedutil之外的任何其它监管器一起使用。

6.5 刻度不变性使用率信号
^^^^^^^^^^^^^^^^^^^^^^^^

为了对不同的CPU和所有的性能状态做出准确的预测，EAS需要频率不变的和CPU不变的
PELT信号。这些信号可以通过每个体系结构定义的arch_scale{cpu,freq}_capacity()
回调函数获取。

不支持在没有实现这两个回调函数的平台上使用EAS。

6.6 多线程（SMT）
^^^^^^^^^^^^^^^^^

当前实现的EAS是不感知SMT的，因此无法利用多线程硬件节约能量。EAS认为线程是独立的
CPU，这实际上对性能和能量消耗都是不利的。

不支持在SMT上使用EAS。


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/scheduler/sched-nice-design.rst

:翻译:

  唐艺舟 Tang Yizhou <tangyeechou@gmail.com>

=====================
调度器nice值设计
=====================

本文档解释了新的Linux调度器中修改和精简后的nice级别的实现思路。

Linux的nice级别总是非常脆弱，人们持续不断地缠着我们，让nice +19的任务占用
更少的CPU时间。

不幸的是，在旧的调度器中，这不是那么容易实现的（否则我们早就做到了），因为对
nice级别的支持在历史上是与时间片长度耦合的，而时间片单位是由HZ滴答驱动的，
所以最小的时间片是1/HZ。

在O(1)调度器中（2003年），我们改变了负的nice级别，使它们比2.4内核更强
（人们对这一变化很满意），而且我们还故意校正了线性时间片准则，使得nice +19
的级别 _正好_ 是1 jiffy。为了让大家更好地理解它，时间片的图会是这样的（质量
不佳的ASCII艺术提醒！）::

                   A
             \     | [timeslice length]
              \    |
               \   |
                \  |
                 \ |
                  \|___100msecs
                   |^ . _
                   |      ^ . _
                   |            ^ . _
 -*----------------------------------*-----> [nice level]
 -20               |                +19
                   |
                   |

因此，如果有人真的想renice任务，相较线性规则，+19会给出更大的效果（改变
ABI来扩展优先级的解决方案在早期就被放弃了）。

这种方法在一定程度上奏效了一段时间，但后来HZ=1000时，它导致1 jiffy为
1ms，这意味着0.1%的CPU使用率，我们认为这有点过度。过度 _不是_ 因为它表示
的CPU使用率过小，而是因为它引发了过于频繁（每毫秒1次）的重新调度（因此会
破坏缓存，等等。请记住，硬件更弱、cache更小是很久以前的事了，当时人们在
nice +19级别运行数量颇多的应用程序）。

因此，对于HZ=1000，我们将nice +19改为5毫秒，因为这感觉像是正确的最小
粒度——这相当于5%的CPU利用率。但nice +19的根本的HZ敏感属性依然保持不变，
我们没有收到过关于nice +19在CPU利用率方面太 _弱_ 的任何抱怨，我们只收到
过它（依然）太 _强_ 的抱怨 :-)。

总结一下：我们一直想让nice各级别一致性更强，但在HZ和jiffies的限制下，以及
nice级别与时间片、调度粒度耦合是令人讨厌的设计，这一目标并不真正可行。

第二个关于Linux nice级别支持的抱怨是（不那么频繁，但仍然定期发生），它
在原点周围的不对称性（你可以在上面的图片中看到），或者更准确地说：事实上
nice级别的行为取决于 _绝对的_ nice级别，而nice应用程序接口本身从根本上
说是“相对”的：

   int nice(int inc);

   asmlinkage long sys_nice(int increment)

（第一个是glibc的应用程序接口，第二个是syscall的应用程序接口）
注意，“inc”是相对当前nice级别而言的，类似bash的“nice”命令等工具是这个
相对性应用程序接口的镜像。

在旧的调度器中，举例来说，如果你以nice +1启动一个任务，并以nice +2启动
另一个任务，这两个任务的CPU分配将取决于父外壳程序的nice级别——如果它是
nice -10，那么CPU的分配不同于+5或+10。

第三个关于Linux nice级别支持的抱怨是，负数nice级别“不够有力”，以很多人
不得不诉诸于实时调度优先级来运行音频（和其它多媒体）应用程序，比如
SCHED_FIFO。但这也造成了其它问题：SCHED_FIFO未被证明是免于饥饿的，一个
有问题的SCHED_FIFO应用程序也会锁住运行良好的系统。

v2.6.23版内核的新调度器解决了这三种类型的抱怨：

为了解决第一个抱怨（nice级别不够“有力”），调度器与“时间片”、HZ的概念
解耦（调度粒度被处理成一个和nice级别独立的概念），因此有可能实现更好、
更一致的nice +19支持：在新的调度器中，nice +19的任务得到一个HZ无关的
1.5%CPU使用率，而不是旧版调度器中3%-5%-9%的可变范围。

为了解决第二个抱怨（nice各级别不一致），新调度器令调用nice(1)对各任务的
CPU利用率有相同的影响，无论其绝对nice级别如何。所以在新调度器上，运行一个
nice +10和一个nice +11的任务会与运行一个nice -5和一个nice -4的任务的
CPU利用率分割是相同的（一个会得到55%的CPU，另一个会得到45%）。这是为什么
nice级别被改为“乘法”（或指数）——这样的话，不管你从哪个级别开始，“相对”
结果将总是一样的。

第三个抱怨（负数nice级别不够“有力”，并迫使音频应用程序在更危险的
SCHED_FIFO调度策略下运行）几乎被新的调度器自动解决了：更强的负数级别
具有重新校正nice级别动态范围的自动化副作用。


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/scheduler/sched-stats.rst

:翻译:

  唐艺舟 Tang Yizhou <tangyeechou@gmail.com>

==============
调度器统计数据
==============

第15版schedstats去掉了sched_yield的一些计数器：yld_exp_empty，yld_act_empty
和yld_both_empty。在其它方面和第14版完全相同。

第14版schedstats包括对sched_domains（译注：调度域）的支持，该特性进入内核
主线2.6.20，不过这一版schedstats与2.6.13-2.6.19内核的版本12的统计数据是完全
相同的（内核未发布第13版）。有些计数器按每个运行队列统计是更有意义的，其它则
按每个调度域统计是更有意义的。注意，调度域（以及它们的附属信息）仅在开启
CONFIG_SMP的机器上是相关的和可用的。

在第14版schedstat中，每个被列出的CPU至少会有一级域统计数据，且很可能有一个
以上的域。在这个实现中，域没有特别的名字，但是编号最高的域通常在机器上所有的
CPU上仲裁平衡，而domain0是最紧密聚焦的域，有时仅在一对CPU之间进行平衡。此时，
没有任何体系结构需要3层以上的域。域统计数据中的第一个字段是一个位图，表明哪些
CPU受该域的影响。

这些字段是计数器，而且只能递增。使用这些字段的程序将需要从基线观测开始，然后在
后续每一个观测中计算出计数器的变化。一个能以这种方式处理其中很多字段的perl脚本
可见

    http://eaglet.pdxhosts.com/rick/linux/schedstat/

请注意，任何这样的脚本都必须是特定于版本的，改变版本的主要原因是输出格式的变化。
对于那些希望编写自己的脚本的人，可以参考这里描述的各个字段。

CPU统计数据
-----------
cpu<N> 1 2 3 4 5 6 7 8 9

第一个字段是sched_yield()的统计数据：

     1) sched_yield()被调用了#次

接下来的三个是schedule()的统计数据：

     2) 这个字段是一个过时的数组过期计数，在O(1)调度器中使用。为了ABI兼容性，
	我们保留了它，但它总是被设置为0。
     3) schedule()被调用了#次
     4) 调用schedule()导致处理器变为空闲了#次

接下来的两个是try_to_wake_up()的统计数据：

     5) try_to_wake_up()被调用了#次
     6) 调用try_to_wake_up()导致本地CPU被唤醒了#次

接下来的三个统计数据描述了调度延迟：

     7) 本处理器运行任务的总时间，单位是纳秒
     8) 本处理器任务等待运行的时间，单位是纳秒
     9) 本CPU运行了#个时间片

域统计数据
----------

对于每个被描述的CPU，和它相关的每一个调度域均会产生下面一行数据（注意，如果
CONFIG_SMP没有被定义，那么*没有*调度域被使用，这些行不会出现在输出中）。

domain<N> <cpumask> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

第一个字段是一个位掩码，表明该域在操作哪些CPU。

接下来的24个字段是load_balance()函数的各个统计数据，按空闲类型分组（空闲，
繁忙，新空闲）：

    1)  当CPU空闲时，load_balance()在这个调度域中被调用了#次
    2)  当CPU空闲时，load_balance()在这个调度域中被调用，但是发现负载无需
        均衡#次
    3)  当CPU空闲时，load_balance()在这个调度域中被调用，试图迁移1个或更多
        任务且失败了#次
    4)  当CPU空闲时，load_balance()在这个调度域中被调用，发现不均衡（如果有）
        #次
    5)  当CPU空闲时，pull_task()在这个调度域中被调用#次
    6)  当CPU空闲时，尽管目标任务是热缓存状态，pull_task()依然被调用#次
    7)  当CPU空闲时，load_balance()在这个调度域中被调用，未能找到更繁忙的
        队列#次
    8)  当CPU空闲时，在调度域中找到了更繁忙的队列，但未找到更繁忙的调度组
        #次
    9)  当CPU繁忙时，load_balance()在这个调度域中被调用了#次
    10) 当CPU繁忙时，load_balance()在这个调度域中被调用，但是发现负载无需
        均衡#次
    11) 当CPU繁忙时，load_balance()在这个调度域中被调用，试图迁移1个或更多
        任务且失败了#次
    12) 当CPU繁忙时，load_balance()在这个调度域中被调用，发现不均衡（如果有）
        #次
    13) 当CPU繁忙时，pull_task()在这个调度域中被调用#次
    14) 当CPU繁忙时，尽管目标任务是热缓存状态，pull_task()依然被调用#次
    15) 当CPU繁忙时，load_balance()在这个调度域中被调用，未能找到更繁忙的
        队列#次
    16) 当CPU繁忙时，在调度域中找到了更繁忙的队列，但未找到更繁忙的调度组
        #次
    17) 当CPU新空闲时，load_balance()在这个调度域中被调用了#次
    18) 当CPU新空闲时，load_balance()在这个调度域中被调用，但是发现负载无需
        均衡#次
    19) 当CPU新空闲时，load_balance()在这个调度域中被调用，试图迁移1个或更多
        任务且失败了#次
    20) 当CPU新空闲时，load_balance()在这个调度域中被调用，发现不均衡（如果有）
        #次
    21) 当CPU新空闲时，pull_task()在这个调度域中被调用#次
    22) 当CPU新空闲时，尽管目标任务是热缓存状态，pull_task()依然被调用#次
    23) 当CPU新空闲时，load_balance()在这个调度域中被调用，未能找到更繁忙的
        队列#次
    24) 当CPU新空闲时，在调度域中找到了更繁忙的队列，但未找到更繁忙的调度组
        #次

接下来的3个字段是active_load_balance()函数的各个统计数据：

    25) active_load_balance()被调用了#次
    26) active_load_balance()被调用，试图迁移1个或更多任务且失败了#次
    27) active_load_balance()被调用，成功迁移了#次任务

接下来的3个字段是sched_balance_exec()函数的各个统计数据：

    28) sbe_cnt不再被使用
    29) sbe_balanced不再被使用
    30) sbe_pushed不再被使用

接下来的3个字段是sched_balance_fork()函数的各个统计数据：

    31) sbf_cnt不再被使用
    32) sbf_balanced不再被使用
    33) sbf_pushed不再被使用

接下来的3个字段是try_to_wake_up()函数的各个统计数据：

    34) 在这个调度域中调用try_to_wake_up()唤醒任务时，任务在调度域中一个
        和上次运行不同的新CPU上运行了#次
    35) 在这个调度域中调用try_to_wake_up()唤醒任务时，任务被迁移到发生唤醒
        的CPU次数为#，因为该任务在原CPU是冷缓存状态
    36) 在这个调度域中调用try_to_wake_up()唤醒任务时，引发被动负载均衡#次

/proc/<pid>/schedstat
---------------------
schedstats还添加了一个新的/proc/<pid>/schedstat文件，来提供一些进程级的
相同信息。这个文件中，有三个字段与该进程相关：

     1) 在CPU上运行花费的时间(单位是纳秒)
     2) 在运行队列上等待的时间(单位是纳秒)
     3) 在CPU上运行了#个时间片

可以很容易地编写一个程序，利用这些额外的字段来报告一个特定的进程或一组进程在
调度器策略下的表现如何。这样的程序的一个简单版本可在下面的链接找到

    http://eaglet.pdxhosts.com/rick/linux/schedstat/v12/latency.c


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/scheduler/schedutil.rst

:翻译:

  唐艺舟 Tang Yizhou <tangyeechou@gmail.com>

=========
Schedutil
=========

.. note::

   本文所有内容都假设频率和工作算力之间存在线性关系。我们知道这是有瑕疵的，
   但这是最可行的近似处理。

PELT（实体负载跟踪，Per Entity Load Tracking）
==============================================

通过PELT，我们跟踪了各种调度器实体的一些指标，从单个任务到任务组分片到CPU
运行队列。我们使用指数加权移动平均数（Exponentially Weighted Moving Average，
EWMA）作为其基础，每个周期（1024us）都会衰减，衰减速率满足y^32 = 0.5。
也就是说，最近的32ms贡献负载的一半，而历史上的其它时间则贡献另一半。

具体而言：

  ewma_sum(u) := u_0 + u_1*y + u_2*y^2 + ...

  ewma(u) = ewma_sum(u) / ewma_sum(1)

由于这本质上是一个无限几何级数的累加，结果是可组合的，即ewma(A) + ewma(B) = ewma(A+B)。
这个属性是关键，因为它提供了在任务迁移时重新组合平均数的能力。

请注意，阻塞态的任务仍然对累加值（任务组分片和CPU运行队列）有贡献，这反映了
它们在恢复运行后的预期贡献。

利用这一点，我们跟踪2个关键指标：“运行”和“可运行”。“运行”反映了一个调度实体
在CPU上花费的时间，而“可运行”反映了一个调度实体在运行队列中花费的时间。当只有
一个任务时，这两个指标是相同的，但一旦出现对CPU的争用，“运行”将减少以反映每个
任务在CPU上花费的时间，而“可运行”将增加以反映争用的激烈程度。

更多细节见：kernel/sched/pelt.c

频率 / CPU不变性
================

因为CPU频率在1GHz时利用率为50%和CPU频率在2GHz时利用率为50%是不一样的，同样
在小核上运行时利用率为50%和在大核上运行时利用率为50%是不一样的，我们允许架构
以两个比率来伸缩时间差，其中一个是动态电压频率升降（Dynamic Voltage and
Frequency Scaling，DVFS）比率，另一个是微架构比率。

对于简单的DVFS架构（软件有完全控制能力），我们可以很容易地计算该比率为::

            f_cur
  r_dvfs := -----
            f_max

对于由硬件控制DVFS的更多动态系统，我们使用硬件计数器（Intel APERF/MPERF，
ARMv8.4-AMU）来计算这一比率。具体到Intel，我们使用::

           APERF
  f_cur := ----- * P0
           MPERF

             4C-turbo;  如果可用并且使能了turbo
  f_max := { 1C-turbo;  如果使能了turbo
             P0;        其它情况

                    f_cur
  r_dvfs := min( 1, ----- )
                    f_max

我们选择4C turbo而不是1C turbo，以使其更持久性略微更强。

r_cpu被定义为当前CPU的最高性能水平与系统中任何其它CPU的最高性能水平的比率。

  r_tot = r_dvfs * r_cpu

其结果是，上述“运行”和“可运行”的指标变成DVFS无关和CPU型号无关了。也就是说，
我们可以在CPU之间转移和比较它们。

更多细节见:

 - kernel/sched/pelt.h:update_rq_clock_pelt()
 - arch/x86/kernel/smpboot.c:"APERF/MPERF frequency ratio computation."
 - Documentation/translations/zh_CN/scheduler/sched-capacity.rst:"1. CPU Capacity + 2. Task utilization"

UTIL_EST / UTIL_EST_FASTUP
==========================

由于周期性任务的平均数在睡眠时会衰减，而在运行时其预期利用率会和睡眠前相同，
因此它们在再次运行后会面临（DVFS）的上涨。

为了缓解这个问题，（一个默认使能的编译选项）UTIL_EST驱动一个无限脉冲响应
（Infinite Impulse Response，IIR）的EWMA，“运行”值在出队时是最高的。
另一个默认使能的编译选项UTIL_EST_FASTUP修改了IIR滤波器，使其允许立即增加，
仅在利用率下降时衰减。

进一步，运行队列的（可运行任务的）利用率之和由下式计算：

  util_est := \Sum_t max( t_running, t_util_est_ewma )

更多细节见: kernel/sched/fair.c:util_est_dequeue()

UCLAMP
======

可以在每个CFS或RT任务上设置有效的u_min和u_max clamp值（译注：clamp可以理解
为类似滤波器的能力，它定义了有效取值范围的最大值和最小值）；运行队列为所有正在
运行的任务保持这些clamp的最大聚合值。

更多细节见: include/uapi/linux/sched/types.h

Schedutil / DVFS
================

每当调度器的负载跟踪被更新时（任务唤醒、任务迁移、时间流逝），我们都会调用
schedutil来更新硬件DVFS状态。

其基础是CPU运行队列的“运行”指标，根据上面的内容，它是CPU的频率不变的利用率
估计值。由此我们计算出一个期望的频率，如下::

             max( running, util_est );  如果使能UTIL_EST
  u_cfs := { running;                   其它情况

               clamp( u_cfs + u_rt, u_min, u_max );  如果使能UCLAMP_TASK
  u_clamp := { u_cfs + u_rt;                         其它情况

  u := u_clamp + u_irq + u_dl;		[估计值。更多细节见源代码]

  f_des := min( f_max, 1.25 u * f_max )

关于IO-wait的说明：当发生更新是因为任务从IO完成中唤醒时，我们提升上面的“u”。

然后，这个频率被用来选择一个P-state或OPP，或者直接混入一个发给硬件的CPPC式
请求。

关于截止期限调度器的说明: 截止期限任务（偶发任务模型）使我们能够计算出满足
工作负荷所需的硬f_min值。

因为这些回调函数是直接来自调度器的，所以DVFS的硬件交互应该是“快速”和非阻塞的。
在硬件交互缓慢和昂贵的时候，schedutil支持DVFS请求限速，不过会降低效率。

更多信息见: kernel/sched/cpufreq_schedutil.c

注意
====

 - 在低负载场景下，DVFS是最相关的，“运行”的值将密切反映利用率。

 - 在负载饱和的场景下，任务迁移会导致一些瞬时性的使用率下降。假设我们有一个
   CPU，有4个任务占用导致其饱和，接下来我们将一个任务迁移到另一个空闲CPU上，
   旧的CPU的“运行”值将为0.75，而新的CPU将获得0.25。这是不可避免的，而且随着
   时间流逝将自动修正。另注，由于没有空闲时间，我们还能保证f_max值吗？

 - 上述大部分内容是关于避免DVFS下滑，以及独立的DVFS域发生负载迁移时不得不
   重新学习/提升频率。


code:
.. SPDX-License-Identifier: GPL-2.0

Chinese translator: Huang Jianghui <huangjianghui@uniontech.com>
---------------------------------------------------------------------
.. include:: ../../disclaimer-zh_CN.rst
以下为正文
---------------------------------------------------------------------
======================================
高清音频编解码器特定混音器控件
======================================

此文件解释特定于编解码器的混音器控件.

瑞昱编解码器
------------

声道模式
  这是一个用于更改环绕声道设置的枚举控件,仅在环绕声道打开时显示出现。
  它给出要使用的通道数:"2ch","4ch","6ch"，和"8ch"。根据配置，这还控
  制多I/O插孔的插孔重分配。

自动静音模式
  这是一个枚举控件，用于更改耳机和线路输出插孔的自动静音行为。如果内
  置扬声器、耳机和/或线路输出插孔在机器上可用，则显示该控件。当只有
  耳机或者线路输出的时候，它给出”禁用“和”启用“状态。当启用后，插孔插
  入后扬声器会自动静音。

  当耳机和线路输出插孔都存在时，它给出”禁用“、”仅扬声器“和”线路输出+扬
  声器“。当”仅扬声器“被选择，插入耳机或者线路输出插孔可使扬声器静音，
  但不会使线路输出静音。当线路输出+扬声器被选择，插入耳机插孔会同时使扬
  声器和线路输出静音。

矽玛特编解码器
--------------

模拟环回
   此控件启用/禁用模拟环回电路。只有在编解码器提示中将”lookback“设置为真
   时才会出现(见HD-Audio.txt)。请注意，在某些编解码器上，模拟环回和正常
   PCM播放是独占的,即当此选项打开时，您将听不到任何PCM流。

交换中置/低频
   交换中置和低频通道顺序，通常情况下，左侧对应中置，右侧对应低频,启动此
   项后，左边低频，右边中置。

耳机作为线路输出
   当此控制开启时，将耳机视为线路输出插孔。也就是说，耳机不会自动静音其他
   线路输出，没有耳机放大器被设置到引脚上。

麦克风插口模式、线路插孔模式等
   这些枚举控制输入插孔引脚的方向和偏置。根据插孔类型，它可以设置为”麦克风
   输入“和”线路输入“以确定输入偏置,或者当引脚是环绕声道的多I/O插孔时，它
   可以设置为”线路输出“。

威盛编解码器
------------

智能5.1
   一个枚举控件，用于为环绕输出重新分配多个I/O插孔的任务。当它打开时，相应
   的输入插孔（通常是线路输入和麦克风输入）被切换为环绕和中央低频输出插孔。

独立耳机
   启用此枚举控制时，耳机输出从单个流（第三个PCM，如hw:0,2）而不是主流路由。
   如果耳机DAC与侧边或中央低频通道DAC共享，则DAC将自动切换到耳机。

环回混合
   一个用于确定是否启动了模拟环回路由的枚举控件。当它启用后，模拟环回路由到
   前置通道。同样，耳机与扬声器输出也采用相同的路径。作为一个副作用，当设置
   此模式后，单个音量控制将不再适用于耳机和扬声器，因为只有一个DAC连接到混
   音器小部件。

动态电源控制
   此控件决定是否启动每个插孔的动态电源控制检测。启用时，根据插孔的插入情况
   动态更改组件的电源状态（D0/D3）以节省电量消耗。但是，如果您的系统没有提
   供正确的插孔检测，这将无法工作;在这种情况下，请关闭此控件。

插孔检测
   此控件仅为VT1708编解码器提供，它不会为每个插孔插拔提供适当的未请求事件。
   当此控件打开，驱动将轮询插孔检测，以便耳机自动静音可以工作，而关闭此控
   件将降低功耗。

科胜讯编解码器
--------------

自动静音模式
   见瑞昱解码器

模拟编解码器
------------

通道模式
   这是一个用于更改环绕声道设置的枚举控件,仅在环绕声道可用时显示。它提供了能
   被使用的通道数:”2ch“、”4ch“和”6ch“。根据配置，这还控制多I/O插孔的插孔重
   分配。

独立耳机
   启动此枚举控制后，耳机输出从单个流（第三个PCM，如hw:0,2）而不是主流路由。


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../../disclaimer-zh_CN.rst

:Original: :doc:`../../../../sound/hd-audio/index`
:Translator: Huang Jianghui <huangjianghui@uniontech.com>

高清音频
========

.. toctree::
   :maxdepth: 2

   controls


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: :doc:`../../../sound/index`
:Translator: Huang Jianghui <huangjianghui@uniontech.com>

====================
Linux 声音子系统文档
====================

.. toctree::
   :maxdepth: 2

   hd-audio/index

.. only::  subproject and html

   Indices
   =======

   * :ref:`genindex`


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/staging/index.rst

:翻译:

 李睿 Rui Li <me@lirui.org>

未分类文档
==========

.. toctree::
   :maxdepth: 2

   xz

TODOList:

* crc32
* lzo
* remoteproc
* rpmsg
* speculation
* static-keys
* tee


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/staging/xz.rst

:翻译:

 李睿 Rui Li <me@lirui.org>

===================
Linux中的XZ数据压缩
===================

介绍
====

XZ是一种通用的数据压缩格式，其具有高压缩率和相对快的解压速度。主要的压缩算法（
过滤器）是LZMA2。额外的过滤器可以被用来进一步提高压缩率，比如用来提高可执行数据
压缩率的Branch/Call/Jump (BCJ)过滤器。

XZ解压器在Linux中被称作XZ Embedded。它支持LZMA2过滤器和可选的BCJ过滤器，并支持
CRC32完整性校验。你可以在XZ Embedded的主页<https://tukaani.org/xz/embedded.html>
中找到最新版本和关于在Linux内核之外使用源码的信息。

对于用户空间来说，XZ Utils提供了类似于zlib的压缩库和类似于gzip的命令行工具。
XZ Utils可以从<https://tukaani.org/xz/>下载。

内核中的XZ相关组件
==================

xz_dec模块为XZ解压器提供了单次调用（缓冲区到缓冲区）和多次调用（有状态）的
API。xz_dec模块的用法记录在include/linux/xz.h中。

xz_dec_test模块用于测试xz_dec。除非你想魔改XZ解压器，否则xz_dec_test是
没有用的。xz_dec_test会动态分配一个字符设备主设备号，你可以从用户空间向它
写入.xz文件，解压的输出会被丢弃。关注dmesg可以找到xz_dec_test输出的诊断信息。
详细内容请查看xz_dec_test的源码。

为了解压内核镜像、初始ram文件系统和初始ram磁盘，lib/decompress_unxz.c实现
了一个包装函数。它的API与其他 decompress_*.c 文件相同，那些API定义在
include/linux/decompress/generic.h中。

scripts/xz_wrap.sh是一个XZ Utils中的xz命令行工具包装器。这个包装器会
设置合适的压缩选项来压缩内核镜像。

在内核的makefiles中，提供了使用$(call if_needed)的两个命令。内核镜像应该
使用$(call if_needed,xzkern)来压缩，它会使用BCJ过滤器和一个大LZMA2字典。
它还会附加一个四字节的包含源文件大小的预告，这会在启动代码中被用到。其他文件
应该使用$(call if_needed,xzmisc)来压缩，它会使用1 MiB的LZMA2字典并禁用
BCJ过滤器。

关于压缩选项的说明
==================

因为XZ Embedded只支持没有完整性校验的数据流或者CRC32，请确保你在编码未来将被
内核解码的文件时没有使用其他完整性校验方式。使用liblzma时，你需要使用LZMA_CHECK_NONE
或LZMA_CHECK_CRC32。使用xz命令行工具时，使用--check=none或--check=crc32。

除非有其他环节会验证解压数据的完整性，否则强烈使用CRC32。双重验证可能会浪费
CPU周期。请注意头部总是会包含用于解压器验证的CRC32，你只能修改或禁用解压后数据
的完整性校验方式。

在用户中间中，LZMA2通常使用几兆字节大小的字典。解码器需要在RAM中放置字典，
因此大字典不能被用于那些意在被内核解码的文件。1 MiB在内核中大概是可接受的最大
字典大小（可能对初始ram文件系统也适用）。XZ Utils中的预设值可能并不适合创建
内核文件，所以请别犹豫使用自定义设置。比如::

	xz --check=crc32 --lzma2=dict=512KiB inputfile

使用上面字典大小的一个例外是在单一调用模式下使用解码器。解压内核自身就是一个例
子。在单一调用模式下，内存用量并不和字典大小有关，这种情况就是使用大字典的好地
方：为了最大化压缩，字典至少应该和解压后的数据一样大。

未来计划
========

如果有人认为有用的话，可能会考虑创建一个受限的XZ编码器。LZMA2的压缩速率比Deflate
或LZO等要慢，即使在最快的配置选项下。所以并不清楚LZMA2编码器是否需要并入内核。

有计划在解压代码中支持有限的随机访问读数据。不知道这能否在内核中有任何用，但是我
知道这会在一些Linux内核以外的嵌入式项目中有用。

.xz文件格式规范的一致性
=======================

在一些边缘情况下，为了简化事情牺牲了尽早地检测错误。因为并不会导致安全问题，实际
上是没有关系的。但在测试代码的时候知道这一点很好，比如测试来自XZ Utils的文件。

报告错误
========

请在报告错误前确认是否已经在上游修复。可以从<https://tukaani.org/xz/embedded.html>
获取最新的源码。

可以通过联系<lasse.collin@tukaani.org>或者访问Freenode上的#tukaani
联系Larhzu。我并不经常阅读LKML或者其他内核相关的邮件列表，所以如果要告知我什么事情
，你应该通过我的私人邮箱或者IRC联系我。

请不要因为内核中XZ的实现或关于XZ Utils的问题打扰Igor Pavlov。虽然这两种实现
包含了建立在Igor Pavlov的代码上的重要源码，但并不由他维护和提供支持。


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../../disclaimer-zh_CN.rst

:Original: Documentation/userspace-api/accelerators/ocxl.rst

:翻译:

 李睿 Rui Li <me@lirui.org>

=====================================
OpenCAPI （开放相干加速器处理器接口）
=====================================

*OpenCAPI: Open Coherent Accelerator Processor Interface*

OpenCAPI是处理器和加速器之间的一个接口，致力于达到低延迟和高带宽。该规范
由 `OpenCAPI Consortium <http://opencapi.org/>`_ 开发。

它允许加速器（可以是FPGA、ASIC等）使用虚拟地址连贯地访问主机内存。一个OpenCAPI
设备也可以托管它自己的内存，并可以由主机访问。

OpenCAPI在Linux中称为“ocxl”，它作为“cxl”（用于powerpc的IBM CAPI接口的驱动）的
开放、处理器无关的演进，这么命名是为了避免与ISDN CAPI子系统相混淆。

高层视角
========

OpenCAPI定义了一个在物理链路层上实现的数据链路层（TL）和传输层（TL）。任何
实现DL和TL的处理器或者设备都可以开始共享内存。

::

  +-----------+                         +-------------+
  |           |                         |             |
  |           |                         | Accelerated |
  | Processor |                         |  Function   |
  |           |  +--------+             |    Unit     |  +--------+
  |           |--| Memory |             |    (AFU)    |--| Memory |
  |           |  +--------+             |             |  +--------+
  +-----------+                         +-------------+
       |                                       |
  +-----------+                         +-------------+
  |    TL     |                         |    TLX      |
  +-----------+                         +-------------+
       |                                       |
  +-----------+                         +-------------+
  |    DL     |                         |    DLX      |
  +-----------+                         +-------------+
       |                                       |
       |                   PHY                 |
       +---------------------------------------+

  Processor：处理器
  Memory：内存
  Accelerated Function Unit：加速函数单元

设备发现
========

OpenCAPI依赖一个在设备上实现的与PCI类似的配置空间。因此主机可以通过查询
配置空间来发现AFU。

OpenCAPI设备在Linux中被当作类PCI设备（有一些注意事项）。固件需要对硬件进行
抽象，就好像它是一个PCI链路。许多已有的PCI架构被重用：在模拟标准PCI时，
设备被扫描并且BAR（基址寄存器）被分配。像“lspci”的命令因此可以被用于查看
哪些设备可用。

配置空间定义了可以在物理适配器上可以被找到的AFU，比如它的名字、支持多少内
存上下文、内存映射IO（MMIO）区域的大小等。

MMIO
====

OpenCAPI为每个AFU定义了两个MMIO区域：

* 全局MMIO区域，保存和整个AFU相关的寄存器。
* 每个进程的MMIO区域，对于每个上下文固定大小。

AFU中断
=======

OpenCAPI拥有AFU向主机进程发送中断的可能性。它通过定义在传输层的“intrp_req”
来完成，指定一个定义中断的64位对象句柄。

驱动允许一个进程分配中断并获取可以传递给AFU的64位对象句柄。

字符设备
========

驱动为每个在物理设备上发现的AFU创建一个字符设备。一个物理设备可能拥有多个
函数，一个函数可以拥有多个AFU。不过编写这篇文档之时，只对导出一个AFU的设备
测试过。

字符设备可以在 /dev/ocxl/ 中被找到，其命名为：
/dev/ocxl/<AFU 名称>.<位置>.<索引>

<AFU 名称> 是一个最长20个字符的名称，和在AFU配置空间中找到的相同。
<位置>由驱动添加，可在系统有不止一个相同的OpenCAPI设备时帮助区分设备。
<索引>也是为了在少见情况下帮助区分AFU，即设备携带多个同样的AFU副本时。

Sysfs类
=======

添加了代表AFU的ocxl类。查看/sys/class/ocxl。布局在
Documentation/ABI/testing/sysfs-class-ocxl 中描述。

用户API
=======

打开
----

基于在配置空间中找到的AFU定义，AFU可能支持在多个内存上下文中工作，这种情况
下相关的字符设备可以被不同进程多次打开。

ioctl
-----

OCXL_IOCTL_ATTACH:

  附加调用进程的内存上下文到AFU，以允许AFU访问其内存。

OCXL_IOCTL_IRQ_ALLOC:

  分配AFU中断，返回标识符。

OCXL_IOCTL_IRQ_FREE:

  释放之前分配的AFU中断。

OCXL_IOCTL_IRQ_SET_FD:

  将一个事件文件描述符和AFU中断关联，因此用户进程可以在AFU发送中断时收到通
  知。

OCXL_IOCTL_GET_METADATA:

  从卡中获取配置信息，比如内存映射IO区域的大小、AFU版本和当前上下文的进程
  地址空间ID（PASID）。

OCXL_IOCTL_ENABLE_P9_WAIT:

  允许AFU唤醒执行“等待”的用户空间进程。返回信息给用户空间，允许其配置AFU。
  注意这只在POWER9上可用。

OCXL_IOCTL_GET_FEATURES:

  报告用户空间可用的影响OpenCAPI的CPU特性。

mmap
----

一个进程可以mmap每个进程的MMIO区域来和AFU交互。


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../../disclaimer-zh_CN.rst

:Original: Documentation/userspace-api/ebpf/index.rst

:翻译:

 李睿 Rui Li <me@lirui.org>

eBPF 用户空间API
================

eBPF是一种在Linux内核中提供沙箱化运行环境的机制，它可以在不改变内核源码或加载
内核模块的情况下扩展运行时和编写工具。eBPF程序能够被附加到各种内核子系统中，包
括网络，跟踪和Linux安全模块(LSM)等。

关于eBPF的内部内核文档，请查看 Documentation/bpf/index.rst 。

.. toctree::
   :maxdepth: 1

   syscall


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../../disclaimer-zh_CN.rst

:Original: Documentation/userspace-api/ebpf/syscall.rst

:翻译:

 李睿 Rui Li <me@lirui.org>

eBPF Syscall
------------

:作者:
    - Alexei Starovoitov <ast@kernel.org>
    - Joe Stringer <joe@wand.net.nz>
    - Michael Kerrisk <mtk.manpages@gmail.com>

bpf syscall的主要信息可以在 `man-pages`_ 中的 `bpf(2)`_ 找到。

bpf() 子命令参考
~~~~~~~~~~~~~~~~

子命令在以下内核代码中：

include/uapi/linux/bpf.h

.. Links:
.. _man-pages: https://www.kernel.org/doc/man-pages/
.. _bpf(2): https://man7.org/linux/man-pages/man2/bpf.2.html


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/userspace-api/futex2.rst

:翻译:

 李睿 Rui Li <me@lirui.org>

======
futex2
======

:作者: André Almeida <andrealmeid@collabora.com>

futex，或者称为快速用户互斥锁（fast user mutex），是一组允许用户空间创建高性能同步
机制的系统调用，比如用户空间中的互斥锁，信号量和条件变量。C标准库，如glibc，使用它作
为实现更多高级接口的方式，如pthreads。

futex2是初代futex系统调用的后续版本，旨在克服原有接口的限制。

用户API
=======

``futex_waitv()``
-----------------

等待一个futex数组，可由其中任意一个唤醒::

  futex_waitv(struct futex_waitv *waiters, unsigned int nr_futexes,
              unsigned int flags, struct timespec *timeout, clockid_t clockid)

  struct futex_waitv {
        __u64 val;
        __u64 uaddr;
        __u32 flags;
        __u32 __reserved;
  };

用户空间设置一个struct futex_waitv数组（最多128项），设置 ``uaddr`` 为等待的
地址， ``val`` 为期望值， ``flags`` 为指定的类型（如private）和futex的大小。
``__reserved`` 需要置为0，但是它可用作未来扩展。指向数组第一个元素的指针作为
``waiters`` 传递。如果 ``waiters`` 或任何的  ``uaddr`` 地址无效，将返回 ``-EFAULT`` 。

如果用户空间拥有32位的指针，那么需要做显式转换来保证高位清零。 ``uintptr_t`` 设计
得很精巧，在32/64位的指针上都正常工作。

``nr_futexes`` 指定了数组的大小。不在[1,128]区间内的值会使系统调用返回 ``-EINVAL`` 。

系统调用的 ``flags`` 参数需要置0，但可用作未来扩展。

对于每个 ``waiters`` 数组中的项，在 ``uaddr`` 的当前值会和 ``val`` 比较。如果
不一致，系统调用会撤销截至目前完成的所有工作，并返回 ``-EAGAIN`` 。如果所有测试
和验证都通过，系统调用会等待直到以下情况之一发生：

- 指定的timeout超时，返回 ``-ETIMEOUT`` 。
- 一个信号被传递给睡眠中的任务，返回 ``-ERESTARTSYS`` 。
- 某个列表中的futex被唤醒，返回那个被唤醒的futex的索引。

关于如何使用接口的例子可以在 ``tools/testing/selftests/futex/functional/futex_waitv.c``
中找到。

超时
----

``struct timespec *timeout`` 是一个指向绝对超时时间的可选参数。你需要在 ``clockid``
参数中指定要使用的时钟类型。支持 ``CLOCK_MONOTONIC`` 和 ``CLOCK_REALTIME`` 。这个
系统调用只接受64位的timespec结构体。

futex的类型
-----------

futex既可以是私有的也可以是共享的。私有用于多个进程共享同样的内存空间，并且futex的虚拟
地址对所有进程都是一样的。这允许在内核中进行优化。要使用私有futex，需要在futex标志中指定
``FUTEX_PRIVATE_FLAG`` 。对于那些不在同一内存空间共享的进程，可以让同一个futex拥有不同
的虚拟地址（例如使用基于文件的共享内存），这需要不同的内部机制来使得正确进入队列。这是默认
的行为，而且对私有futex和共享futex都适用。

futex可以是不同的大小：8，16，32或64位。目前只支持32位大小的futex，并且需要通过 ``FUTEX_32``
标志指定。


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/userspace-api/index.rst

:翻译:

 李睿 Rui Li <me@lirui.org>

=========================
Linux 内核用户空间API指南
=========================

.. _man-pages: https://www.kernel.org/doc/man-pages/

尽管许多用户空间API的文档被记录在别处（特别是在 man-pages_ 项目中），
在代码树中仍然可以找到有关用户空间的部分信息。这个手册意在成为这些信息
聚集的地方。

.. class:: toc-title

	   目录

.. toctree::
   :maxdepth: 2

   no_new_privs
   seccomp_filter
   accelerators/ocxl
   ebpf/index
   sysfs-platform_profile
   futex2

TODOList:

* landlock
* unshare
* spec_ctrl
* ioctl/index
* iommu
* media/index
* netlink/index
* vduse

.. only::  subproject and html

   Indices
   =======

   * :ref:`genindex`


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/userspace-api/no_new_privs.rst

:翻译:

 李睿 Rui Li <me@lirui.org>

============
无新权限标志
============

execve系统调用可以给一个新启动的程序授予它的父程序本没有的权限。最明显的两个
例子就是setuid/setgid控制程序和文件的能力。为了避免父程序也获得这些权限，内
核和用户代码必须小心避免任何父程序可以颠覆子程序的情况。比如：

 - 程序在setuid后，动态装载器处理 ``LD_*`` 环境变量的不同方式。

 - 对于非特权程序，chroot是不允许的，因为这会允许 ``/etc/passwd`` 在继承
   chroot的程序眼中被替换。

 - 执行代码对ptrace有特殊处理。

这些都是临时性的修复。 ``no_new_privs`` 位（从 Linux 3.5 起）是一个新的通
用的机制来保证一个进程安全地修改其执行环境并跨execve持久化。任何任务都可以设
置 ``no_new_privs`` 。一旦该位被设置，它会在fork、clone和execve中继承下去
，并且不能被撤销。在 ``no_new_privs`` 被设置的情况下， ``execve()`` 将保证
不会授予权限去做任何没有execve调用就不能做的事情。比如， setuid 和 setgid
位不会再改变 uid 或 gid；文件能力不会被添加到授权集合中，并且Linux安全模块（
LSM）不会在execve调用后放松限制。

设置 ``no_new_privs`` 使用::

    prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);

不过要小心，Linux安全模块（LSM）也可能不会在 ``no_new_privs`` 模式下收紧约束。
（这意味着一个一般的服务启动器在执行守护进程前就去设置 ``no_new_privs`` 可能
会干扰基于LSM的沙箱。）

请注意， ``no_new_privs`` 并不能阻止不涉及 ``execve()`` 的权限变化。一个拥有
适当权限的任务仍然可以调用 ``setuid(2)`` 并接收 SCM_RIGHTS 数据报。

目前来说， ``no_new_privs`` 有两大使用场景：

 - 为seccomp模式2沙箱安装的过滤器会跨execve持久化，并能够改变新执行程序的行为。
   非特权用户因此在 ``no_new_privs`` 被设置的情况下只允许安装这样的过滤器。

 - ``no_new_privs`` 本身就能被用于减少非特权用户的攻击面。如果所有以某个 uid
   运行的程序都设置了 ``no_new_privs`` ，那么那个 uid 将无法通过攻击 setuid，
   setgid 和使用文件能力的二进制来提权；它需要先攻击一些没有被设置 ``no_new_privs``
   位的东西。

将来，其他潜在的危险的内核特性可能被非特权任务利用，即使 ``no_new_privs`` 被置位。
原则上，当 ``no_new_privs`` 被置位时， ``unshare(2)`` 和 ``clone(2)`` 的几个选
项将是安全的，并且 ``no_new_privs`` 加上 ``chroot`` 是可以被认为比 chroot本身危
险性小得多的。


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/userspace-api/seccomp_filter.rst

:翻译:

 李睿 Rui Li <me@lirui.org>

==================================
Seccomp BPF (基于过滤器的安全计算)
==================================

*Seccomp: SECure COMPuting*

介绍
====

大量系统调用被暴露给每个用户空间进程，但其中又有许多系统调用在进程的整个生命
周期中都没被使用。随着系统调用的改变和成熟，缺陷被找到并消除。允许某一部分应
用程序仅能访问一部分系统调用是有好处的，这会缩小内核暴露给应用程序的面积。
系统调用过滤器就是为这些应用程序而生的。

Seccomp过滤提供了一种为进程指定一个处理系统调用的过滤器的方法。这个过滤器体
现为一个伯克利包过滤器（BPF）程序，就像套接字过滤器一样，不同在于前者处理的
数据和正在进行的系统调用有关：系统调用号和系统调用参数。这样使用一种长期与
用户空间和直接数据打交道的语言来表达系统调用过滤成为了可能。

此外，BPF让seccomp用户不再成为在系统调用干预框架（system call interposition
frameworks）中常见的检查-使用竞态攻击（TOCTOU）的受害者。BPF程序可能无法解引
用指针，这就限制了所有过滤器仅能直接评估系统调用参数。

这不是什么
==========

系统调用过滤并不是一个沙箱。它提供了一种明确定义的机制来最小化内核暴露面。它
旨在成为一个沙箱开发者使用的工具。除此之外，逻辑行为和信息流的策略应该结合其他
系统加固手段或者可能由你选择的内核安全模块（LSM）来管理。易于表达的动态过滤器
为这条路提供了更多选择（比如避免病态的大小或者选择允许 socketcall() 中的多路
系统调用），但将其理解为更完整的沙箱解决方案是错误的。

用法
====

添加了一个额外的seccomp模式，它可以使用和严格seccomp相同的 prctl(2) 调用来启用。
如果架构有 ``CONFIG_HAVE_ARCH_SECCOMP_FILTER`` 标志，那么可以添加以下过滤器：

``PR_SET_SECCOMP``:
	现在需要添加一个额外的参数来指定使用BPF程序的新过滤器。
	BPF程序将在反应系统调用号、参数和其他元数据的seccomp_data结构体之上执行。
	BPF程序必须返回允许的值之一来告知内核应该采取什么行动。

	用法::

		prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, prog);

	'prog' 参数是一个指向 sock_fprog 结构体的指针，其中包含了过滤器程序。如
	果程序是无效的，该调用会返回 -1 并设置 errno 为 ``EINVAL`` 。

	如果 ``fork`` / ``clone`` 和 ``execve`` 被 @prog 所允许，任何子进程都将
	受到和父进程相同的过滤器和系统调用ABI的约束。

	在调用之前，进程必须调用 ``prctl(PR_SET_NO_NEW_PRIVS, 1)`` 或者在它的
	命名空间内以 ``CAP_SYS_ADMIN`` 权限运行。如果以上条件不满足，会返回
	``-EACCES`` 。这一要求保证了过滤器程序不能用于比安装过滤器的进程拥有更高
	权限的子进程。

	另外，如果 ``prctl(2)`` 被安装的过滤器所允许，就可以叠加额外的过滤器。这会增
	加评估时间，但是可以进一步降低执行进程时的攻击面。

以上调用在成功时返回0，失败时返回一个非零的值。

返回值
======

一个seccomp过滤器可能返回下列任意值。如果多个过滤器存在，评估一个指定系统调用的
返回值总会使用最高优先级的值。（比如 ``SECCOMP_RET_KILL_PROCESS`` 总是被优先
返回。）

按照优先级排序，它们是：

``SECCOMP_RET_KILL_PROCESS``:
	使得整个进程立即结束而不执行系统调用。进程的退出状态 (``status & 0x7f``) 将
	是 ``SIGSYS`` ，而不是 ``SIGKILL`` 。

``SECCOMP_RET_KILL_THREAD``:
	使得线程立即结束而不执行系统调用。线程的退出状态 (``status & 0x7f``) 将是
	是 ``SIGSYS`` ，而不是 ``SIGKILL`` 。

``SECCOMP_RET_TRAP``:
	使得内核向触发进程发送一个 ``SIGSYS`` 信号而不执行系统调用。
	``siginfo->si_call_addr`` 会展示系统调用指令的位置， ``siginfo->si_syscall``
	和 ``siginfo->si_arch`` 会指出试图进行的系统调用。程序计数器会和发生了系统
	调用的一样（即它不会指向系统调用指令）。返回值寄存器会包含一个与架构相关的值——
	如果恢复执行，需要将其设为合理的值。（架构依赖性是因为将其替换为 ``-ENOSYS``
	会导致一些有用的信息被覆盖。）

	返回值的 ``SECCOMP_RET_DATA`` 部分会作为 ``si_errno`` 传递。

	由seccomp触发的 ``SIGSYS`` 会有一个 ``SYS_SECCOMP`` 的 si_code 。

``SECCOMP_RET_ERRNO``:
	使得返回值的低16位作为errno传递给用户空间，而不执行系统调用。

``SECCOMP_RET_USER_NOTIF``:
	使得一个 ``struct seccomp_notif`` 消息被发送到已附加的用户空间通知文件描述
	符。如果没有被附加则返回 ``-ENOSYS`` 。下面会讨论如何处理用户通知。

``SECCOMP_RET_TRACE``:
	当返回的时候，这个值会使得内核在执行系统调用前尝试去通知一个基于 ``ptrace()``
	的追踪器。如果没有追踪器， ``-ENOSYS`` 会返回给用户空间，并且系统调用不会执行。

	如果追踪器通过 ``ptrace(PTRACE_SETOPTIONS)`` 请求了 ``PTRACE_O_TRACESECCOMP``，
	那么它会收到 ``PTRACE_EVENT_SECCOMP`` 通知。BPF程序返回值的 ``SECCOMP_RET_DATA``
	部分会通过 ``PTRACE_GETEVENTMSG`` 提供给追踪器。

	追踪器可以通过改变系统调用号到-1来跳过系统调用。或者追踪器可以改变系统调用号到
	一个有效值来改变请求的系统调用。如果追踪器请求跳过系统调用，那么系统调用将返回
	追踪器放在返回值寄存器中的值。

	在追踪器被通知后，seccomp检查不会再次运行。（这意味着基于seccomp的沙箱必须禁止
	ptrace的使用，甚至其他沙箱进程也不行，除非非常小心；ptrace可以通过这个机制来逃
	逸。）

``SECCOMP_RET_LOG``:
	使得系统调用在被记录之后再运行。这应该被应用开发者用来检查他们的程序需要哪些
	系统调用，而不需要反复通过多个测试和开发周期来建立清单。

	只有在 actions_logged sysctl 字符串中出现 "log" 时，这个操作才会被记录。

``SECCOMP_RET_ALLOW``:
	使得系统调用被执行。

如果多个追踪器存在，评估系统调用的返回值将永远使用最高优先级的值。

优先级只通过 ``SECCOMP_RET_ACTION`` 掩码来决定。当多个过滤器返回相同优先级的返回
值时，只有来自最近安装的过滤器的 ``SECCOMP_RET_DATA`` 会被返回。

隐患
====

最需要避免的隐患是在过滤系统调用号时却不检查架构值。因为在任何支持多个系统调用
约定的架构上，系统调用号可能根据具体调用而不同。如果不同调用约定中的调用号有重叠，
那么过滤器的检查可能被滥用。请总是检查架构值！

例子
====

``samples/seccomp/`` 文件夹包含了x86专用和更通用的使用高层宏接口来生成BPF程序的
例子。

用户空间通知
============

``SECCOMP_RET_USER_NOTIF`` 返回值会让seccomp过滤器传递一个特定的系统调用给用户
空间处理。这可能会对像容器管理器的程序有用，它们希望拦截特定的系统调用（如 ``mount()``，
``finit_module()`` 等等）并改变其行为。

传递 ``SECCOMP_FILTER_FLAG_NEW_LISTENER`` 参数给 ``seccomp()`` 系统调用可以取
得通知文件描述符：

.. code-block:: c

    fd = seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, &prog);

成功情况下会返回一个对过滤器监听的文件描述符，然后可以通过 ``SCM_RIGHTS`` 或类似
的方式传递。需要注意的是，过滤器文件描述符针对的是一个特定的过滤器而不是特定的进程。
所以如果这个进程后来fork了，来自两个进程的通知都会出现在同一个过滤器文件描述符中。
对于过滤器文件描述符的读写也是同步的，所以一个过滤器文件描述符可以安全地拥有多个读者。

seccomp通知文件描述符由两个结构体组成：

.. code-block:: c

    struct seccomp_notif_sizes {
        __u16 seccomp_notif;
        __u16 seccomp_notif_resp;
        __u16 seccomp_data;
    };

    struct seccomp_notif {
        __u64 id;
        __u32 pid;
        __u32 flags;
        struct seccomp_data data;
    };

    struct seccomp_notif_resp {
        __u64 id;
        __s64 val;
        __s32 error;
        __u32 flags;
    };

``struct seccomp_notif_sizes`` 结构体可以用于确定seccomp通知中各种结构体的大小。
``struct seccomp_data`` 的大小可能未来会改变，所以需要使用下面的代码：

.. code-block:: c

    struct seccomp_notif_sizes sizes;
    seccomp(SECCOMP_GET_NOTIF_SIZES, 0, &sizes);

来决定需要分配的多种结构体的大小。请查看 samples/seccomp/user-trap.c 中的例子。

用户可以通过 ``ioctl(SECCOMP_IOCTL_NOTIF_RECV)`` (或 ``poll()``) 读取seccomp
通知文件描述符来接收一个 ``struct seccomp_notif`` ，其中包含五个成员：结构体的
输入长度，每个过滤器唯一的 ``id`` ， 触发请求进程的 ``pid`` （如果进程的pid命名空
间对于监听者的pid命名空间不可见的话，可能为0）。通知还包含传递给seccomp的 ``data``
和一个过滤器标志。在调用ioctl前结构体应该被清空。

之后用户空间可以根据这些信息决定做什么，并通过 ``ioctl(SECCOMP_IOCTL_NOTIF_SEND)``
发送一个响应，表示应该给用户空间返回什么。 ``struct seccomp_notif_resp`` 的 ``id``
成员应该和 ``struct seccomp_notif`` 的 ``id`` 一致。

用户空间也可以通过 ``ioctl(SECCOMP_IOCTL_NOTIF_ADDFD)`` 向通知进程添加文件描述
符。 ``struct seccomp_notif_addfd`` 的 ``id`` 成员应该和 ``struct seccomp_notif``
的 ``id`` 保持一致。 ``newfd_flags`` 标志可以被用于在通知进程的文件描述符上设置
O_CLOEXEC 等标志。如果监督者（supervisor）向文件描述符注入一个特定的数字，可以使用
``SECCOMP_ADDFD_FLAG_SETFD`` 标志，并设置 ``newfd`` 成员为要使用的特定数字。
如果文件描述符已经在通知进程中打开，那它将被替换。监督者也可以添加一个文件描述符，
并使用 ``SECCOMP_ADDFD_FLAG_SEND`` 标志原子响应，返回值将是注入的文件描述符编号。

通知进程可以被抢占，导致通知被终止。这可能在尝试代表通知进程执行长时间且通常可重试
（如挂载文件系统）的操作时导致问题。另外，在安装过滤器的时候，
``SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV`` 可以被设置。这个标志使得当监督者收到用
户通知时，通知进程会忽略非致命信号，直到响应被发送。在用户空间收到通知之前发出的信号
将被正常处理。

值得注意的是， ``struct seccomp_data`` 包含了系统调用寄存器参数的值，但是不包含指向
内存的指针。进程的内存可以通过  ``ptrace()`` 或 ``/proc/pid/mem`` 由合适的特权跟踪
访问。但是，需要注意避免之前提到的TOCTOU攻击：所有从被跟踪者内存中读到的参数都应该先
读到追踪器的内存中，再做出策略决定。这样就可以对系统调用的参数做原子决定。

Sysctls
=======

Seccomp的sysctl文件可以在 ``/proc/sys/kernel/seccomp/`` 文件夹中找到。这里有对文件
夹中每个文件的描述：

``actions_avail``:
	以字符串形式保存seccomp返回值（参考上文的 ``SECCOMP_RET_*`` 宏）的只读有序
	列表。从左往右按照最少许可返回值到最多许可返回值排序。

	这个列表代表了内核支持的seccomp返回值集合。一个用户空间程序可以使用这个列表来在
	程序建立时确定在 ``seccomp.h`` 中找到的动作是否和当前运行内核实际支持的动作有所
	不同。

``actions_logged``:
	允许被记录的seccomp返回值（参考上文的 ``SECCOMP_RET_*`` 宏）的可读写有序列表。
	对文件写入不需要是有序的，但从文件读取将采用与actions_avail sysctl一致的方式排序。

	``allow`` 字符串在 ``actions_logged`` sysctl中不被接收，因为不可能记录
	``SECCOMP_RET_ALLOW`` 动作。尝试向sysctl写入 ``allow`` 会导致返回一个EINVAL。

添加架构支持
============

请查看 ``arch/Kconfig`` 了解权威要求。总的来说如果一个架构同时支持ptrace_event和
seccomp，那么它将可以通过较小的修改支持seccomp过滤器： ``SIGSYS`` 支持和seccomp
返回值检查。然后必须将 ``CONFIG_HAVE_ARCH_SECCOMP_FILTER`` 添加到它的架构特定
的Kconfig中。

注意事项
========

vDSO可能导致一些系统调用完全在用户空间中运行，当你在不同机器上跑程序时可能导致回退
到真正系统调用的意外发生。为了在x86上最小化这些意外的发生，请确保你在测试时把
``/sys/devices/system/clocksource/clocksource0/current_clocksource`` 设置为
``acpi_pm`` 之类的值。

在x86-64上，vsyscall模拟默认开启。（vsyscalls是vDSO调用的传统变体。）目前，模拟
vsyscalls会遵守seccomp，但是有一些奇怪情况：

- ``SECCOMP_RET_TRAP`` 的返回值会设置一个指向给定vsyscall入口的 ``si_call_addr``，
  而不是'syscall'指令之后的地址。任何想重新开始调用的代码都需要注意 (a) ret指令
  已被模拟，(b) 试图恢复系统调用将再次触发标准vsyscall模拟安全检查，使得恢复系统
  调用在大部分情况下没有意义。

- ``SECCOMP_RET_TRACE`` 的返回值将像往常一样给追踪器发出信号，但是系统调用可能不能
  使用orig_rax寄存器改变为另一个系统调用。可能只能改变为-1来跳过当前模拟的调用。
  任何其他改变都可能终止进程。追踪器看到的rip值将是系统调用的入口地址；这和正常行为
  不同。追踪器禁止修改rip或者rsp。（不要依赖其他改变来终止进程，它们可能正常工作。
  比如在一些内核中，选择一个只存在于未来内核中的系统调用将被正确模拟，返回一个
  ``-ENOSYS`` 。）

要检测这个古怪的行为，可以检查 ``addr & ~0x0C00 == 0xFFFFFFFFFF600000``。（对于
``SECCOMP_RET_TRACE`` ，使用rip。对于 ``SECCOMP_RET_TRAP`` ，使用
``siginfo->si_call_addr`` 。）不要检测其他条件：未来内核可能会改进vsyscall模拟，
当前内核在vsyscall=native模式下会有不同表现，但在这些情况下， ``0xF...F600{0,4,8,C}00``
处的指令将不是系统调用。

请注意，现代系统根本不可能使用vsyscalls —— 它们是一种遗留功能，而且比标准系统调用
慢得多。 新的代码将使用vDSO，而vDSO发出的系统调用与正常的系统调用没有区别。


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/userspace-api/sysfs-platform_profile.rst

:翻译:

 李睿 Rui Li <me@lirui.org>

==========================================================
平台配置文件选择（如 /sys/firmware/acpi/platform_profile）
==========================================================

现代系统中平台性能、温度、风扇和其他硬件相关的特性通常是可以动态配置的。平台
配置通常会根据当前的状态由一些自动机制（很可能存在于内核之外）来自动调整。

这些平台自动调整机制通常能够被配置成多个平台配置文件中的一个，要么偏向节能运
行，要么偏向性能运行。

platform_profile属性的目的是提供一个通用的sysfs API来选择这些平台自动配置
机制的配置文件。

需要注意的是，这个API只能用作选择平台配置文件，用来监测所产生的性能特征并不
是其目标。监测性能最好使用设备/供应商提供的工具，比如turbostat。

具体来说，当选择高性能配置文件时，真实能达到的性能可能受制于多种因素，比如：
其他组件的发热，房间温度，笔记本底部的自由空气流动等。让用户空间知道任何阻碍
达到要求性能水平的局部最优条件，显然不是这个API的目标。

由于数字本身并不能代表一个配置文件会调整的多个变量（功耗，发热等），这个API
使用字符串来描述多种配置文件。为了保证用户空间能够获得一致的体验，
sysfs-platform_profile ABI 文档定义了一个固定的配置文件名集合。驱动程序
*必须* 将它们内置的配置文件表示映射到这个固定的集合中。

如果映射时没有很好的匹配，可以添加一个新的配置文件名称。驱动希望引入的新配置
文件名称时必须：

 1. 解释为什么无法使用已有的配置文件名称。
 2. 添加一个新的配置文件名称，以及预期行为的清晰描述，保存到
    sysfs-platform_profile ABI文档中。


code:
Chinese translated version of Documentation/admin-guide/media/omap3isp.rst

If you have any comment or update to the content, please contact the
original document maintainer directly.  However, if you have a problem
communicating in English you can also ask the Chinese maintainer for
help.  Contact the Chinese maintainer if this translation is outdated
or if there is a problem with the translation.

Maintainer: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
	  Sakari Ailus <sakari.ailus@iki.fi>
	  David Cohen <dacohen@gmail.com>
Chinese maintainer: Fu Wei <tekkamanninja@gmail.com>
---------------------------------------------------------------------
Documentation/admin-guide/media/omap3isp.rst 的中文翻译

如果想评论或更新本文的内容，请直接联系原文档的维护者。如果你使用英文
交流有困难的话，也可以向中文版维护者求助。如果本翻译更新不及时或者翻
译存在问题，请联系中文版维护者。
英文版维护者： Laurent Pinchart <laurent.pinchart@ideasonboard.com>
	  Sakari Ailus <sakari.ailus@iki.fi>
	  David Cohen <dacohen@gmail.com>
中文版维护者： 傅炜 Fu Wei <tekkamanninja@gmail.com>
中文版翻译者： 傅炜 Fu Wei <tekkamanninja@gmail.com>
中文版校译者： 傅炜 Fu Wei <tekkamanninja@gmail.com>

以下为正文
---------------------------------------------------------------------
OMAP 3 图像信号处理器 (ISP) 驱动

Copyright (C) 2010 Nokia Corporation
Copyright (C) 2009 Texas Instruments, Inc.

联系人: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
	  Sakari Ailus <sakari.ailus@iki.fi>
	  David Cohen <dacohen@gmail.com>

介绍
===

本文档介绍了由 drivers/media/video/omap3isp 加载的德州仪器
（TI）OMAP 3 图像信号处理器 (ISP) 驱动。原始驱动由德州仪器（TI）
编写，但此后由诺基亚重写了两次。

驱动已在以下 OMAP 3 系列的芯片中成功使用：

	3430
	3530
	3630

驱动实现了 V4L2、媒体控制器和 v4l2_subdev 接口。支持内核中使用
v4l2_subdev 接口的传感器、镜头和闪光灯驱动。

拆分为子设备
==========

OMAP 3 ISP 被拆分为 V4L2 子设备，ISP中的每个模块都由一个子设备
来表示。每个子设备向用户空间提供一个 V4L2 子设备接口。

	OMAP3 ISP CCP2
	OMAP3 ISP CSI2a
	OMAP3 ISP CCDC
	OMAP3 ISP preview
	OMAP3 ISP resizer
	OMAP3 ISP AEWB
	OMAP3 ISP AF
	OMAP3 ISP histogram

ISP 中每个可能的连接都通过一个链接嵌入到媒体控制器接口中。详见例程 [2]。

控制 OMAP 3 ISP
==============

通常，对 OMAP 3 ISP 的配置会在下一帧起始时生效。在传感器垂直消隐期间，
模块变为空闲时完成配置。在内存到内存的操作中，视频管道一次处理一帧。
应用配置应在帧间完成。

ISP 中的所有模块，除 CSI-2 和 （可能存在的）CCP2 接收器外，都必须
接收完整的帧数据。因此，传感器必须保证从不发送部分帧数据给ISP。

Autoidle（自动空闲）功能至少在 3430 的 ISP 模块中确实存在一些问题。
当 omap3isp 模块参数 autoidle 非零时，autoidle（自动空闲）功能
仅在 3630 中启用了。

事件机制
======

OMAP 3 ISP 驱动在 CCDC 和统计（AEWB、AF 和 直方图）子设备中支持
V4L2 事件机制接口。

CCDC 子设备通过 HS_VS 中断，处理 V4L2_EVENT_FRAME_SYNC 类型
事件，用于告知帧起始。早期版本的驱动则使用 V4L2_EVENT_OMAP3ISP_HS_VS。
当在 CCDC 模块中接收到起始帧的第一行时，会准确地触发事件。这个事件
可以在 CCDC 子设备中“订阅”。

（当使用并行接口时，必须注意正确地配置 VS 信号极性。而当使用串行接收时
这个会自动校正。）

每个统计子设备都可以产生事件。每当一个统计缓冲区可由用户空间应用程序
通过 VIDIOC_OMAP3ISP_STAT_REQ IOCTL 操作获取时，就会产生一个
事件。当前存在以下事件：

	V4L2_EVENT_OMAP3ISP_AEWB
	V4L2_EVENT_OMAP3ISP_AF
	V4L2_EVENT_OMAP3ISP_HIST

这些 ioctl 的事件数据类型为 struct omap3isp_stat_event_status
结构体。如果出现计算错误的统计，也同样会产生一个事件，但没有相关的统计
数据缓冲区。这种情况下 omap3isp_stat_event_status.buf_err 会被
设置为非零值。

私有 IOCTL
==========

OMAP 3 ISP 驱动支持标准的 V4L2 IOCTL 以及可能存在且实用的控制。但
ISP 提供的许多功能都不在标准 IOCTL 之列，例如 gamma（伽马）表和统计
数据采集配置等。

通常，会有一个私有 ioctl 用于配置每个包含硬件依赖功能的模块。

支持以下私有 IOCTL：

	VIDIOC_OMAP3ISP_CCDC_CFG
	VIDIOC_OMAP3ISP_PRV_CFG
	VIDIOC_OMAP3ISP_AEWB_CFG
	VIDIOC_OMAP3ISP_HIST_CFG
	VIDIOC_OMAP3ISP_AF_CFG
	VIDIOC_OMAP3ISP_STAT_REQ
	VIDIOC_OMAP3ISP_STAT_EN

在 include/linux/omap3isp.h 中描述了这些 ioctl 使用的参数结构体。
与特定 ISP 模块相关的 ISP 自身的详细功能在技术参考手册 (TRMs)中有
描述，详见文档结尾。

虽然在不使用任何私有 IOCTL 的情况下使用 ISP 驱动是可能的，但这样无法
获得最佳的图像质量。AEWB、AF 和 直方图（译者注：一般用于自动曝光和增益
控制，以及图像均衡等）模块无法在未使用适当的私有 IOCTL 配置的情况下使用。

CCDC 和 preview（预览）模块 IOCTL
===============================

VIDIOC_OMAP3ISP_CCDC_CFG 和 VIDIOC_OMAP3ISP_PRV_CFG IOCTL
被分别用于配置、启用和禁用 CCDC 和 preview（预览）模块的功能。在它们
所控制的模块中，两个 IOCTL 控制多种功能。VIDIOC_OMAP3ISP_CCDC_CFG IOCTL
接受一个指向 omap3isp_ccdc_update_config 结构体的指针作为它的参数。
同样的，VIDIOC_OMAP3ISP_PRV_CFG 接受一个指向 omap3isp_prev_update_config
结构体的指针。以上两个结构体定义位于 [1]。

这些结构体中的 update 域标识是否针对指定的功能更新配置，而 flag 域
则标识是启用还是禁用此功能。

update 和 flag 位接受以下掩码值。CCDC 和 preview（预览）模块的
每个单独功能都与一个 flag 关联（禁用或启用；在结构体中 flag 域的
一部分）和一个指向功能配置数据的指针。

对于 VIDIOC_OMAP3ISP_CCDC_CFG，下面列出了 update 和 flag 域
中的有效值。 这些值可能会在同一个 IOCTL 调用中配置多个功能。

        OMAP3ISP_CCDC_ALAW
        OMAP3ISP_CCDC_LPF
        OMAP3ISP_CCDC_BLCLAMP
        OMAP3ISP_CCDC_BCOMP
        OMAP3ISP_CCDC_FPC
        OMAP3ISP_CCDC_CULL
        OMAP3ISP_CCDC_CONFIG_LSC
        OMAP3ISP_CCDC_TBL_LSC

针对 VIDIOC_OMAP3ISP_PRV_CFG 的相应值如下：

        OMAP3ISP_PREV_LUMAENH
        OMAP3ISP_PREV_INVALAW
        OMAP3ISP_PREV_HRZ_MED
        OMAP3ISP_PREV_CFA
        OMAP3ISP_PREV_CHROMA_SUPP
        OMAP3ISP_PREV_WB
        OMAP3ISP_PREV_BLKADJ
        OMAP3ISP_PREV_RGB2RGB
        OMAP3ISP_PREV_COLOR_CONV
        OMAP3ISP_PREV_YC_LIMIT
        OMAP3ISP_PREV_DEFECT_COR
        OMAP3ISP_PREV_GAMMABYPASS
        OMAP3ISP_PREV_DRK_FRM_CAPTURE
        OMAP3ISP_PREV_DRK_FRM_SUBTRACT
        OMAP3ISP_PREV_LENS_SHADING
        OMAP3ISP_PREV_NF
        OMAP3ISP_PREV_GAMMA

在启用某个功能的时候，相关的配置数据指针不可为 NULL。在禁用某个功能时，
配置数据指针会被忽略。

统计模块 IOCTL
=============

统计子设备相较于其他子设备提供了更多动态配置选项。在图像处理流水线处于
工作状态时，它们可以被启用、禁用和重配。

统计模块总是从 CCDC 中获取输入的图像数据（由于直方图内存读取未实现）。
统计数据可由用户通过统计子设备节点使用私有 IOCTL 获取。

AEWB、AF 和 直方图子设备提供的私有 IOCTL 极大程度上反应了 ISP 硬件
提供的寄存器级接口。有些方面纯粹和驱动程序的实现相关，这些将在下面讨论。

VIDIOC_OMAP3ISP_STAT_EN
-----------------------

这个私有 IOCTL 启用/禁用 一个统计模块。如果这个申请在视频流启动前完成，
它将在视频流水线开始工作时生效。如果视频流水线已经处于工作状态了，它将在
CCDC 变为空闲时生效。

VIDIOC_OMAP3ISP_AEWB_CFG, VIDIOC_OMAP3ISP_HIST_CFG and VIDIOC_OMAP3ISP_AF_CFG
-----------------------------------------------------------------------------

这些 IOCTL 用于配置模块。它们要求用户应用程序对硬件有深入的认识。对
大多数域的解释可以在 OMAP 的 TRM 中找到。以下两个域对于以上所有的
私有 IOCTL 配置都很常见，由于他们没有在 TRM 中提及，故需要对其有
更好的认识。

omap3isp_[h3a_af/h3a_aewb/hist]_config.buf_size:

模块在内部处理自身缓冲。对模块数据输出所必需的缓存大小依赖于已申请的配置。
虽然驱动支持在视频流工作时重新配置，但对于所需缓存量大于模块启用时内部
所分配数量的情况，则不支持重新配置。在这种情况下将返回 -EBUSY。为了避免
此类状况，无论是禁用/重配/启用模块，还是第一次配置时申请必须的缓存大小，
都应在模块禁用的情况下进行。

内部缓冲分配的大小需综合考虑所申请配置的最小缓存量以及 buf_size 域中
所设的值。如果 buf_size 域在[minimum（最小值）, maximum（最大值）]
缓冲大小范围之外，则应该将其调整到其范围中。驱动则会选择最大值。正确的
buf_size 值将回写到用户应用程序中。

omap3isp_[h3a_af/h3a_aewb/hist]_config.config_counter:

由于配置并未在申请之后同步生效，驱动必须提供一个跟踪这类信息的方法，
以提供更准确的数据。在一个配置被申请之后，返回到用户空间应用程序的
config_counter 是一个与其配置相关的唯一值。当用户应用程序接收到
一个缓冲可用或一个新的缓冲申请事件时，这个 config_counter 用于
一个缓冲数据和一个配置的匹配。

VIDIOC_OMAP3ISP_STAT_REQ
------------------------

将内部缓冲队列中最早的数据发送到用户空间，然后丢弃此缓冲区。
omap3isp_stat_data.frame_number 域与视频缓冲的 field_count
域相匹配。

技术参考手册 (TRMs) 和其他文档
==========================

OMAP 3430 TRM:
<URL:http://focus.ti.com/pdfs/wtbu/OMAP34xx_ES3.1.x_PUBLIC_TRM_vZM.zip>
参考于 2011-03-05.

OMAP 35xx TRM:
<URL:http://www.ti.com/litv/pdf/spruf98o> 参考于 2011-03-05.

OMAP 3630 TRM:
<URL:http://focus.ti.com/pdfs/wtbu/OMAP36xx_ES1.x_PUBLIC_TRM_vQ.zip>
参考于 2011-03-05.

DM 3730 TRM:
<URL:http://www.ti.com/litv/pdf/sprugn4h> 参考于 2011-03-06.

参考资料
=======

[1] include/linux/omap3isp.h

[2] http://git.ideasonboard.org/?p=media-ctl.git;a=summary