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

:Original: Documentation/core-api/kobject.rst

:翻译:

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

.. _cn_core_api_kobject.rst:

=======================================================
关于kobjects、ksets和ktypes的一切你没想过需要了解的东西
=======================================================

:作者: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
:最后一次更新: December 19, 2007

根据Jon Corbet于2003年10月1日为lwn.net撰写的原创文章改编，网址是：
https://lwn.net/Articles/51437/

理解驱动模型和建立在其上的kobject抽象的部分的困难在于，没有明显的切入点。
处理kobjects需要理解一些不同的类型，所有这些类型都会相互引用。为了使事情
变得更简单，我们将多路并进，从模糊的术语开始，并逐渐增加细节。那么，先来
了解一些我们将要使用的术语的简明定义吧。

 - 一个kobject是一个kobject结构体类型的对象。Kobjects有一个名字和一个
   引用计数。一个kobject也有一个父指针（允许对象被排列成层次结构），一个
   特定的类型，并且，通常在sysfs虚拟文件系统中表示。

  Kobjects本身通常并不引人关注；相反它们常常被嵌入到其他包含真正引人注目
  的代码的结构体中。

  任何结构体都 **不应该** 有一个以上的kobject嵌入其中。如果有的话，对象的引用计
  数肯定会被打乱，而且不正确，你的代码就会出现错误。所以不要这样做。

 - ktype是嵌入一个kobject的对象的类型。每个嵌入kobject的结构体都需要一个
   相应的ktype。ktype控制着kobject在被创建和销毁时的行为。

 - 一个kset是一组kobjects。这些kobjects可以是相同的ktype或者属于不同的
   ktype。kset是kobjects集合的基本容器类型。Ksets包含它们自己的kobjects，
   但你可以安全地忽略这个实现细节，因为kset的核心代码会自动处理这个kobject。

 当你看到一个下面全是其他目录的sysfs目录时，通常这些目录中的每一个都对应
 于同一个kset中的一个kobject。

 我们将研究如何创建和操作所有这些类型。将采取一种自下而上的方法，所以我们
 将回到kobjects。

嵌入kobjects
=============

内核代码很少创建孤立的kobject，只有一个主要的例外，下面会解释。相反，
kobjects被用来控制对一个更大的、特定领域的对象的访问。为此，kobjects会被
嵌入到其他结构中。如果你习惯于用面向对象的术语来思考问题，那么kobjects可
以被看作是一个顶级的抽象类，其他的类都是从它派生出来的。一个kobject实现了
一系列的功能，这些功能本身并不特别有用，但在其他对象中却很好用。C语言不允
许直接表达继承，所以必须使用其他技术——比如结构体嵌入。

（对于那些熟悉内核链表实现的人来说，这类似于“list_head”结构本身很少有用，
但总是被嵌入到感兴趣的更大的对象中）。

例如， ``drivers/uio/uio.c`` 中的IO代码有一个结构体，定义了与uio设备相
关的内存区域::

    struct uio_map {
            struct kobject kobj;
            struct uio_mem *mem;
    };

如果你有一个uio_map结构体，找到其嵌入的kobject只是一个使用kobj成员的问题。
然而，与kobjects一起工作的代码往往会遇到相反的问题：给定一个结构体kobject
的指针，指向包含结构体的指针是什么？你必须避免使用一些技巧（比如假设
kobject在结构的开头），相反，你得使用container_of()宏，其可以在 ``<linux/kernel.h>``
中找到::

    container_of(ptr, type, member)

其中:

  * ``ptr`` 是一个指向嵌入kobject的指针，
  * ``type`` 是包含结构体的类型，
  * ``member`` 是 ``指针`` 所指向的结构体域的名称。

container_of()的返回值是一个指向相应容器类型的指针。因此，例如，一个嵌入到
uio_map结构 **中** 的kobject结构体的指针kp可以被转换为一个指向 **包含** uio_map
结构体的指针，方法是::

    struct uio_map *u_map = container_of(kp, struct uio_map, kobj);

为了方便起见，程序员经常定义一个简单的宏，用于将kobject指针 **反推** 到包含
类型。在早期的 ``drivers/uio/uio.c`` 中正是如此，你可以在这里看到::

    struct uio_map {
            struct kobject kobj;
            struct uio_mem *mem;
    };

    #define to_map(map) container_of(map, struct uio_map, kobj)

其中宏的参数“map”是一个指向有关的kobject结构体的指针。该宏随后被调用::

    struct uio_map *map = to_map(kobj);

kobjects的初始化
================

当然，创建kobject的代码必须初始化该对象。一些内部字段是通过（强制）调用kobject_init()
来设置的::

    void kobject_init(struct kobject *kobj, struct kobj_type *ktype);

ktype是正确创建kobject的必要条件，因为每个kobject都必须有一个相关的kobj_type。
在调用kobject_init()后，为了向sysfs注册kobject，必须调用函数kobject_add()::

    int kobject_add(struct kobject *kobj, struct kobject *parent,
                    const char *fmt, ...);

这将正确设置kobject的父级和kobject的名称。如果该kobject要与一个特定的kset相关
联，在调用kobject_add()之前必须分配kobj->kset。如果kset与kobject相关联，则
kobject的父级可以在调用kobject_add()时被设置为NULL，则kobject的父级将是kset
本身。

由于kobject的名字是在它被添加到内核时设置的，所以kobject的名字不应该被直接操作。
如果你必须改变kobject的名字，请调用kobject_rename()::

    int kobject_rename(struct kobject *kobj, const char *new_name);

kobject_rename()函数不会执行任何锁定操作，也不会对name进行可靠性检查，所以调用
者自己检查和串行化操作是明智的选择

有一个叫kobject_set_name()的函数，但那是历史遗产，正在被删除。如果你的代码需
要调用这个函数，那么它是不正确的，需要被修复。

要正确访问kobject的名称，请使用函数kobject_name()::

    const char *kobject_name(const struct kobject * kobj);

有一个辅助函数可以同时初始化和添加kobject到内核中，令人惊讶的是，该函数被称为
kobject_init_and_add()::

    int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
                             struct kobject *parent, const char *fmt, ...);

参数与上面描述的单个kobject_init()和kobject_add()函数相同。

Uevents
=======

当一个kobject被注册到kobject核心后，你需要向全世界宣布它已经被创建了。这可以通
过调用kobject_uevent()来实现::

    int kobject_uevent(struct kobject *kobj, enum kobject_action action);

当kobject第一次被添加到内核时，使用 *KOBJ_ADD* 动作。这应该在该kobject的任
何属性或子对象被正确初始化后进行，因为当这个调用发生时，用户空间会立即开始寻
找它们。

当kobject从内核中移除时（关于如何做的细节在下面）， **KOBJ_REMOVE** 的uevent
将由kobject核心自动创建，所以调用者不必担心手动操作。

引用计数
========

kobject的关键功能之一是作为它所嵌入的对象的一个引用计数器。只要对该对象的引用
存在，该对象（以及支持它的代码）就必须继续存在。用于操作kobject的引用计数的低
级函数是::

    struct kobject *kobject_get(struct kobject *kobj);
    void kobject_put(struct kobject *kobj);

对kobject_get()的成功调用将增加kobject的引用计数器值并返回kobject的指针。

当引用被释放时，对kobject_put()的调用将递减引用计数值，并可能释放该对象。请注
意，kobject_init()将引用计数设置为1，所以设置kobject的代码最终需要kobject_put()
来释放该引用。

因为kobjects是动态的，所以它们不能以静态方式或在堆栈中声明，而总是以动态方式分
配。未来版本的内核将包含对静态创建的kobjects的运行时检查，并将警告开发者这种不
当的使用。

如果你使用struct kobject只是为了给你的结构体提供一个引用计数器，请使用struct kref
来代替；kobject是多余的。关于如何使用kref结构体的更多信息，请参见Linux内核源代
码树中的文件Documentation/core-api/kref.rst

创建“简单的”kobjects
====================

有时，开发者想要的只是在sysfs层次结构中创建一个简单的目录，而不必去搞那些复杂
的ksets、显示和存储函数，以及其他细节。这是一个应该创建单个kobject的例外。要
创建这样一个条目（即简单的目录），请使用函数::

    struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);

这个函数将创建一个kobject，并将其放在sysfs中指定的父kobject下面的位置。要创
建与此kobject相关的简单属性，请使用::

    int sysfs_create_file(struct kobject *kobj, const struct attribute *attr);

或者::

    int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp);

这里使用的两种类型的属性，与已经用kobject_create_and_add()创建的kobject，
都可以是kobj_attribute类型，所以不需要创建特殊的自定义属性。

参见示例模块， ``samples/kobject/kobject-example.c`` ，以了解一个简单的
kobject和属性的实现。

ktypes和释放方法
================

以上讨论中还缺少一件重要的事情，那就是当一个kobject的引用次数达到零的时候
会发生什么。创建kobject的代码通常不知道何时会发生这种情况；首先，如果它知
道，那么使用kobject就没有什么意义。当sysfs被引入时，即使是可预测的对象生命
周期也会变得更加复杂，因为内核的其他部分可以获得在系统中注册的任何kobject
的引用。

最终的结果是，一个由kobject保护的结构体在其引用计数归零之前不能被释放。引
用计数不受创建kobject的代码的直接控制。因此，每当它的一个kobjects的最后一
个引用消失时，必须异步通知该代码。

一旦你通过kobject_add()注册了你的kobject，你绝对不能使用kfree()来直接释
放它。唯一安全的方法是使用kobject_put()。在kobject_init()之后总是使用
kobject_put()以避免错误的发生是一个很好的做法。

这个通知是通过kobject的release()方法完成的。通常这样的方法有如下形式::

    void my_object_release(struct kobject *kobj)
    {
            struct my_object *mine = container_of(kobj, struct my_object, kobj);

            /* Perform any additional cleanup on this object, then... */
            kfree(mine);
    }

有一点很重要：每个kobject都必须有一个release()方法，而且这个kobject必
须持续存在（处于一致的状态），直到这个方法被调用。如果这些约束条件没有
得到满足，那么代码就是有缺陷的。注意，如果你忘记提供release()方法，内
核会警告你。不要试图通过提供一个“空”的释放函数来摆脱这个警告。

如果你的清理函数只需要调用kfree()，那么你必须创建一个包装函数，该函数
使用container_of()来向上造型到正确的类型（如上面的例子所示），然后在整个
结构体上调用kfree()。

注意，kobject的名字在release函数中是可用的，但它不能在这个回调中被改
变。否则，在kobject核心中会出现内存泄漏，这让人很不爽。

有趣的是，release()方法并不存储在kobject本身；相反，它与ktype相关。
因此，让我们引入结构体kobj_type::

    struct kobj_type {
            void (*release)(struct kobject *kobj);
            const struct sysfs_ops *sysfs_ops;
            const struct attribute_group **default_groups;
            const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
            const void *(*namespace)(struct kobject *kobj);
            void (*get_ownership)(struct kobject *kobj, kuid_t *uid, kgid_t *gid);
    };

这个结构提用来描述一个特定类型的kobject（或者更正确地说，包含对象的
类型）。每个kobject都需要有一个相关的kobj_type结构；当你调用
kobject_init()或kobject_init_and_add()时必须指定一个指向该结构的
指针。

当然，kobj_type结构中的release字段是指向这种类型的kobject的release()
方法的一个指针。另外两个字段（sysfs_ops 和 default_groups）控制这种
类型的对象如何在 sysfs 中被表示；它们超出了本文的范围。

default_groups 指针是一个默认属性的列表，它将为任何用这个 ktype 注册
的 kobject 自动创建。

ksets
=====

一个kset仅仅是一个希望相互关联的kobjects的集合。没有限制它们必须是相
同的ktype，但是如果它们不是相同的，就要非常小心。

一个kset有以下功能:

 - 它像是一个包含一组对象的袋子。一个kset可以被内核用来追踪“所有块
   设备”或“所有PCI设备驱动”。

 - kset也是sysfs中的一个子目录，与kset相关的kobjects可以在这里显示
   出来。每个kset都包含一个kobject，它可以被设置为其他kobject的父对象；
   sysfs层次结构的顶级目录就是以这种方式构建的。

 - Ksets可以支持kobjects的 "热插拔"，并影响uevent事件如何被报告给
   用户空间。

 在面向对象的术语中，“kset”是顶级的容器类；ksets包含它们自己的kobject，
 但是这个kobject是由kset代码管理的，不应该被任何其他用户所操纵。

 kset在一个标准的内核链表中保存它的子对象。Kobjects通过其kset字段指向其
 包含的kset。在几乎所有的情况下，属于一个kset的kobjects在它们的父
 对象中都有那个kset（或者，严格地说，它的嵌入kobject）。

 由于kset中包含一个kobject，它应该总是被动态地创建，而不是静态地
 或在堆栈中声明。要创建一个新的kset，请使用::

  struct kset *kset_create_and_add(const char *name,
                                   const struct kset_uevent_ops *uevent_ops,
                                   struct kobject *parent_kobj);

当你完成对kset的处理后，调用::

  void kset_unregister(struct kset *k);

来销毁它。这将从sysfs中删除该kset并递减其引用计数值。当引用计数
为零时，该kset将被释放。因为对该kset的其他引用可能仍然存在，
释放可能发生在kset_unregister()返回之后。

一个使用kset的例子可以在内核树中的 ``samples/kobject/kset-example.c``
文件中看到。

如果一个kset希望控制与它相关的kobjects的uevent操作，它可以使用
结构体kset_uevent_ops来处理它::

  struct kset_uevent_ops {
          int (* const filter)(struct kobject *kobj);
          const char *(* const name)(struct kobject *kobj);
          int (* const uevent)(struct kobject *kobj, struct kobj_uevent_env *env);
  };

过滤器函数允许kset阻止一个特定kobject的uevent被发送到用户空间。
如果该函数返回0，该uevent将不会被发射出去。

name函数将被调用以覆盖uevent发送到用户空间的kset的默认名称。默
认情况下，该名称将与kset本身相同，但这个函数，如果存在，可以覆盖
该名称。

当uevent即将被发送至用户空间时，uevent函数将被调用，以允许更多
的环境变量被添加到uevent中。

有人可能会问，鉴于没有提出执行该功能的函数，究竟如何将一个kobject
添加到一个kset中。答案是这个任务是由kobject_add()处理的。当一个
kobject被传递给kobject_add()时，它的kset成员应该指向这个kobject
所属的kset。 kobject_add()将处理剩下的部分。

如果属于一个kset的kobject没有父kobject集，它将被添加到kset的目
录中。并非所有的kset成员都必须住在kset目录中。如果在添加kobject
之前分配了一个明确的父kobject，那么该kobject将被注册到kset中，
但是被添加到父kobject下面。

移除Kobject
===========

当一个kobject在kobject核心注册成功后，在代码使用完它时，必须将其
清理掉。要做到这一点，请调用kobject_put()。通过这样做，kobject核
心会自动清理这个kobject分配的所有内存。如果为这个对象发送了 ``KOBJ_ADD``
uevent，那么相应的 ``KOBJ_REMOVE`` uevent也将被发送，任何其他的
sysfs内务将被正确处理。

如果你需要分两次对kobject进行删除（比如说在你要销毁对象时无权睡眠），
那么调用kobject_del()将从sysfs中取消kobject的注册。这使得kobject
“不可见”，但它并没有被清理掉，而且该对象的引用计数仍然是一样的。在稍
后的时间调用kobject_put()来完成与该kobject相关的内存的清理。

kobject_del()可以用来放弃对父对象的引用，如果循环引用被构建的话。
在某些情况下，一个父对象引用一个子对象是有效的。循环引用必须通过明
确调用kobject_del()来打断，这样一个释放函数就会被调用，前一个循环
中的对象会相互释放。

示例代码出处
============

关于正确使用ksets和kobjects的更完整的例子，请参见示例程序
``samples/kobject/{kobject-example.c,kset-example.c}`` ，如果
您选择 ``CONFIG_SAMPLE_KOBJECT`` ，它们将被构建为可加载模块。


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

:Original: Documentation/core-api/kref.rst

翻译:

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

校译：

 <此处请校译员签名（自愿），我将在下一个版本添加>

.. _cn_core_api_kref.rst:

=================================
为内核对象添加引用计数器（krefs）
=================================

:作者: Corey Minyard <minyard@acm.org>
:作者: Thomas Hellstrom <thellstrom@vmware.com>

其中很多内容都是从Greg Kroah-Hartman2004年关于krefs的OLS论文和演讲中摘
录的，可以在以下网址找到:

  - http://www.kroah.com/linux/talks/ols_2004_kref_paper/Reprint-Kroah-Hartman-OLS2004.pdf
  - http://www.kroah.com/linux/talks/ols_2004_kref_talk/

简介
====

krefs允许你为你的对象添加引用计数器。如果你有在多个地方使用和传递的对象，
而你没有refcounts，你的代码几乎肯定是坏的。如果你想要引用计数，krefs是个
好办法。

要使用kref，请在你的数据结构中添加一个，如::

    struct my_data
    {
	.
	.
	struct kref refcount;
	.
	.
    };

kref可以出现在数据结构体中的任何地方。

初始化
======

你必须在分配kref之后初始化它。 要做到这一点，可以这样调用kref_init::

     struct my_data *data;

     data = kmalloc(sizeof(*data), GFP_KERNEL);
     if (!data)
            return -ENOMEM;
     kref_init(&data->refcount);

这将kref中的refcount设置为1。

Kref规则
========

一旦你有一个初始化的kref，你必须遵循以下规则:

1) 如果你对一个指针做了一个非临时性的拷贝，特别是如果它可以被传递给另一个执
   行线程，你必须在传递之前用kref_get()增加refcount::

       kref_get(&data->refcount);

	如果你已经有了一个指向kref-ed结构体的有效指针（refcount不能为零），你
	可以在没有锁的情况下这样做。

2) 当你完成对一个指针的处理时，你必须调用kref_put()::

       kref_put(&data->refcount, data_release);

   如果这是对该指针的最后一次引用，释放程序将被调用。如果代码从来没有尝试过
   在没有已经持有有效指针的情况下获得一个kref-ed结构体的有效指针，那么在没
   有锁的情况下这样做是安全的。

3) 如果代码试图获得对一个kref-ed结构体的引用，而不持有一个有效的指针，它必
   须按顺序访问，在kref_put()期间不能发生kref_get()，并且该结构体在kref_get()
   期间必须保持有效。

例如，如果你分配了一些数据，然后将其传递给另一个线程来处理::

    void data_release(struct kref *ref)
    {
	struct my_data *data = container_of(ref, struct my_data, refcount);
	kfree(data);
    }

    void more_data_handling(void *cb_data)
    {
	struct my_data *data = cb_data;
	.
	. do stuff with data here
	.
	kref_put(&data->refcount, data_release);
    }

    int my_data_handler(void)
    {
	int rv = 0;
	struct my_data *data;
	struct task_struct *task;
	data = kmalloc(sizeof(*data), GFP_KERNEL);
	if (!data)
		return -ENOMEM;
	kref_init(&data->refcount);

	kref_get(&data->refcount);
	task = kthread_run(more_data_handling, data, "more_data_handling");
	if (task == ERR_PTR(-ENOMEM)) {
		rv = -ENOMEM;
	        kref_put(&data->refcount, data_release);
		goto out;
	}

	.
	. do stuff with data here
	.
    out:
	kref_put(&data->refcount, data_release);
	return rv;
    }

这样，两个线程处理数据的顺序并不重要，kref_put()处理知道数据不再被引用并释
放它。kref_get()不需要锁，因为我们已经有了一个有效的指针，我们拥有一个
refcount。put不需要锁，因为没有任何东西试图在没有持有指针的情况下获取数据。

在上面的例子中，kref_put()在成功和错误路径中都会被调用2次。这是必要的，因
为引用计数被kref_init()和kref_get()递增了2次。

请注意，规则1中的 "before "是非常重要的。你不应该做类似于::

	task = kthread_run(more_data_handling, data, "more_data_handling");
	if (task == ERR_PTR(-ENOMEM)) {
		rv = -ENOMEM;
		goto out;
	} else
		/* BAD BAD BAD - 在交接后得到 */
		kref_get(&data->refcount);

不要以为你知道自己在做什么而使用上述构造。首先，你可能不知道自己在做什么。
其次，你可能知道自己在做什么（有些情况下涉及到锁，上述做法可能是合法的），
但其他不知道自己在做什么的人可能会改变代码或复制代码。这是很危险的作风。请
不要这样做。

在有些情况下，你可以优化get和put。例如，如果你已经完成了一个对象，并且给其
他对象排队，或者把它传递给其他对象，那么就没有理由先做一个get，然后再做一个
put::

	/* 糟糕的额外获取(get)和输出(put) */
	kref_get(&obj->ref);
	enqueue(obj);
	kref_put(&obj->ref, obj_cleanup);

只要做enqueue就可以了。 我们随时欢迎对这个问题的评论::

	enqueue(obj);
	/* 我们已经完成了对obj的处理，所以我们把我们的refcount传给了队列。
	 在这之后不要再碰obj了! */

最后一条规则（规则3）是最难处理的一条。例如，你有一个每个项目都被krefed的列表，
而你希望得到第一个项目。你不能只是从列表中抽出第一个项目，然后kref_get()它。
这违反了规则3，因为你还没有持有一个有效的指针。你必须添加一个mutex（或其他锁）。
比如说::

	static DEFINE_MUTEX(mutex);
	static LIST_HEAD(q);
	struct my_data
	{
		struct kref      refcount;
		struct list_head link;
	};

	static struct my_data *get_entry()
	{
		struct my_data *entry = NULL;
		mutex_lock(&mutex);
		if (!list_empty(&q)) {
			entry = container_of(q.next, struct my_data, link);
			kref_get(&entry->refcount);
		}
		mutex_unlock(&mutex);
		return entry;
	}

	static void release_entry(struct kref *ref)
	{
		struct my_data *entry = container_of(ref, struct my_data, refcount);

		list_del(&entry->link);
		kfree(entry);
	}

	static void put_entry(struct my_data *entry)
	{
		mutex_lock(&mutex);
		kref_put(&entry->refcount, release_entry);
		mutex_unlock(&mutex);
	}

如果你不想在整个释放操作过程中持有锁，kref_put()的返回值是有用的。假设你不想在
上面的例子中在持有锁的情况下调用kfree()（因为这样做有点无意义）。你可以使用kref_put()，
如下所示::

	static void release_entry(struct kref *ref)
	{
		/* 所有的工作都是在从kref_put()返回后完成的。*/
	}

	static void put_entry(struct my_data *entry)
	{
		mutex_lock(&mutex);
		if (kref_put(&entry->refcount, release_entry)) {
			list_del(&entry->link);
			mutex_unlock(&mutex);
			kfree(entry);
		} else
			mutex_unlock(&mutex);
	}

如果你必须调用其他程序作为释放操作的一部分，而这些程序可能需要很长的时间，或者可
能要求相同的锁，那么这真的更有用。请注意，在释放例程中做所有的事情还是比较好的，
因为它比较整洁。

上面的例子也可以用kref_get_unless_zero()来优化，方法如下::

	static struct my_data *get_entry()
	{
		struct my_data *entry = NULL;
		mutex_lock(&mutex);
		if (!list_empty(&q)) {
			entry = container_of(q.next, struct my_data, link);
			if (!kref_get_unless_zero(&entry->refcount))
				entry = NULL;
		}
		mutex_unlock(&mutex);
		return entry;
	}

	static void release_entry(struct kref *ref)
	{
		struct my_data *entry = container_of(ref, struct my_data, refcount);

		mutex_lock(&mutex);
		list_del(&entry->link);
		mutex_unlock(&mutex);
		kfree(entry);
	}

	static void put_entry(struct my_data *entry)
	{
		kref_put(&entry->refcount, release_entry);
	}

这对于在put_entry()中移除kref_put()周围的mutex锁是很有用的，但是重要的是
kref_get_unless_zero被封装在查找表中的同一关键部分，否则kref_get_unless_zero
可能引用已经释放的内存。注意，在不检查其返回值的情况下使用kref_get_unless_zero
是非法的。如果你确信（已经有了一个有效的指针）kref_get_unless_zero()会返回true，
那么就用kref_get()代替。

Krefs和RCU
==========

函数kref_get_unless_zero也使得在上述例子中使用rcu锁进行查找成为可能::

	struct my_data
	{
		struct rcu_head rhead;
		.
		struct kref refcount;
		.
		.
	};

	static struct my_data *get_entry_rcu()
	{
		struct my_data *entry = NULL;
		rcu_read_lock();
		if (!list_empty(&q)) {
			entry = container_of(q.next, struct my_data, link);
			if (!kref_get_unless_zero(&entry->refcount))
				entry = NULL;
		}
		rcu_read_unlock();
		return entry;
	}

	static void release_entry_rcu(struct kref *ref)
	{
		struct my_data *entry = container_of(ref, struct my_data, refcount);

		mutex_lock(&mutex);
		list_del_rcu(&entry->link);
		mutex_unlock(&mutex);
		kfree_rcu(entry, rhead);
	}

	static void put_entry(struct my_data *entry)
	{
		kref_put(&entry->refcount, release_entry_rcu);
	}

但要注意的是，在调用release_entry_rcu后，结构kref成员需要在有效内存中保留一个rcu
宽限期。这可以通过使用上面的kfree_rcu(entry, rhead)来实现，或者在使用kfree之前
调用synchronize_rcu()，但注意synchronize_rcu()可能会睡眠相当长的时间。

repo_name: angery/linux


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

:Original: Documentation/core-api/local_ops.rst

:翻译:

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

.. _cn_local_ops:

========================
本地原子操作的语义和行为
========================

:作者: Mathieu Desnoyers

本文解释了本地原子操作的目的，如何为任何给定的架构实现这些操作，并说明了
如何正确使用这些操作。它还强调了在内存写入顺序很重要的情况下，跨CPU读取
这些本地变量时必须采取的预防措施。

.. note::

    注意，基于 ``local_t`` 的操作不建议用于一般内核操作。请使用 ``this_cpu``
    操作来代替使用，除非真的有特殊目的。大多数内核中使用的 ``local_t`` 已
    经被 ``this_cpu`` 操作所取代。 ``this_cpu`` 操作在一条指令中结合了重
    定位和类似 ``local_t`` 的语义，产生了更紧凑和更快的执行代码。

本地原子操作的目的
==================

本地原子操作的目的是提供快速和高度可重入的每CPU计数器。它们通过移除LOCK前
缀和通常需要在CPU间同步的内存屏障，将标准原子操作的性能成本降到最低。

在许多情况下，拥有快速的每CPU原子计数器是很有吸引力的：它不需要禁用中断来保护中
断处理程序，它允许在NMI（Non Maskable Interrupt）处理程序中使用连贯的计数器。
它对追踪目的和各种性能监测计数器特别有用。

本地原子操作只保证在拥有数据的CPU上的变量修改的原子性。因此，必须注意确保只
有一个CPU写到 ``local_t`` 的数据。这是通过使用每CPU的数据来实现的，并确
保我们在一个抢占式安全上下文中修改它。然而，从任何一个CPU读取 ``local_t``
数据都是允许的：这样它就会显得与所有者CPU的其他内存写入顺序不一致。

针对特定架构的实现
==================

这可以通过稍微修改标准的原子操作来实现：只有它们的UP变体必须被保留。这通常
意味着删除LOCK前缀（在i386和x86_64上）和任何SMP同步屏障。如果架构在SMP和
UP之间没有不同的行为，在你的架构的 ``local.h`` 中包括 ``asm-generic/local.h``
就足够了。

通过在一个结构体中嵌入一个 ``atomic_long_t`` ， ``local_t`` 类型被定义为
一个不透明的 ``signed long`` 。这样做的目的是为了使从这个类型到
``long`` 的转换失败。该定义看起来像::

    typedef struct { atomic_long_t a; } local_t;

使用本地原子操作时应遵循的规则
==============================

* 被本地操作触及的变量必须是每cpu的变量。

* *只有* 这些变量的CPU所有者才可以写入这些变量。

* 这个CPU可以从任何上下文（进程、中断、软中断、nmi...）中使用本地操作来更新
  它的local_t变量。

* 当在进程上下文中使用本地操作时，必须禁用抢占（或中断），以确保进程在获得每
  CPU变量和进行实际的本地操作之间不会被迁移到不同的CPU。

* 当在中断上下文中使用本地操作时，在主线内核上不需要特别注意，因为它们将在局
  部CPU上运行，并且已经禁用了抢占。然而，我建议无论如何都要明确地禁用抢占，
  以确保它在-rt内核上仍能正确工作。

* 读取本地cpu变量将提供该变量的当前拷贝。

* 对这些变量的读取可以从任何CPU进行，因为对 “ ``long`` ”，对齐的变量的更新
  总是原子的。由于写入程序的CPU没有进行内存同步，所以在读取 *其他* cpu的变
  量时，可以读取该变量的过期副本。

如何使用本地原子操作
====================

::

    #include <linux/percpu.h>
    #include <asm/local.h>

    static DEFINE_PER_CPU(local_t, counters) = LOCAL_INIT(0);

计数器
======

计数是在一个signed long的所有位上进行的。

在可抢占的上下文中，围绕本地原子操作使用 ``get_cpu_var()`` 和
``put_cpu_var()`` ：它确保在对每个cpu变量进行写访问时，抢占被禁用。比如
说::

    local_inc(&get_cpu_var(counters));
    put_cpu_var(counters);

如果你已经在一个抢占安全上下文中，你可以使用 ``this_cpu_ptr()`` 代替::

    local_inc(this_cpu_ptr(&counters));

读取计数器
==========

那些本地计数器可以从外部的CPU中读取，以求得计数的总和。请注意，local_read
所看到的跨CPU的数据必须被认为是相对于拥有该数据的CPU上发生的其他内存写入来
说不符合顺序的::

    long sum = 0;
    for_each_online_cpu(cpu)
            sum += local_read(&per_cpu(counters, cpu));

如果你想使用远程local_read来同步CPU之间对资源的访问，必须在写入者和读取者
的CPU上分别使用显式的 ``smp_wmb()`` 和 ``smp_rmb()`` 内存屏障。如果你使
用 ``local_t`` 变量作为写在缓冲区中的字节的计数器，就会出现这种情况：在缓
冲区写和计数器增量之间应该有一个 ``smp_wmb()`` ，在计数器读和缓冲区读之间
也应有一个 ``smp_rmb()`` 。

下面是一个使用 ``local.h`` 实现每个cpu基本计数器的示例模块::

    /* test-local.c
     *
     * Sample module for local.h usage.
     */

    #include <asm/local.h>
    #include <linux/module.h>
    #include <linux/timer.h>

    static DEFINE_PER_CPU(local_t, counters) = LOCAL_INIT(0);

    static struct timer_list test_timer;

    /* IPI called on each CPU. */
    static void test_each(void *info)
    {
            /* Increment the counter from a non preemptible context */
            printk("Increment on cpu %d\n", smp_processor_id());
            local_inc(this_cpu_ptr(&counters));

            /* This is what incrementing the variable would look like within a
             * preemptible context (it disables preemption) :
             *
             * local_inc(&get_cpu_var(counters));
             * put_cpu_var(counters);
             */
    }

    static void do_test_timer(unsigned long data)
    {
            int cpu;

            /* Increment the counters */
            on_each_cpu(test_each, NULL, 1);
            /* Read all the counters */
            printk("Counters read from CPU %d\n", smp_processor_id());
            for_each_online_cpu(cpu) {
                    printk("Read : CPU %d, count %ld\n", cpu,
                            local_read(&per_cpu(counters, cpu)));
            }
            mod_timer(&test_timer, jiffies + 1000);
    }

    static int __init test_init(void)
    {
            /* initialize the timer that will increment the counter */
            timer_setup(&test_timer, do_test_timer, 0);
            mod_timer(&test_timer, jiffies + 1);

            return 0;
    }

    static void __exit test_exit(void)
    {
            timer_shutdown_sync(&test_timer);
    }

    module_init(test_init);
    module_exit(test_exit);

    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Mathieu Desnoyers");
    MODULE_DESCRIPTION("Local Atomic Ops");


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

:Original: Documentation/core-api/memory-allocation.rst

:翻译:

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

:校译:

 时奎亮 <alexs@kernel.org>

.. _cn_core-api_memory-allocation:

============
内存分配指南
============

Linux为内存分配提供了多种API。你可以使用 `kmalloc` 或 `kmem_cache_alloc`
系列分配小块内存，使用 `vmalloc` 及其派生产品分配大的几乎连续的区域，或者
你可以用 alloc_pages 直接向页面分配器请求页面。也可以使用更专业的分配器，
例如 `cma_alloc` 或 `zs_malloc` 。

大多数的内存分配API使用GFP标志来表达该内存应该如何分配。GFP的缩写代表
“(get free pages)获取空闲页”，是底层的内存分配功能。

（内存）分配API的多样性与众多的GFP标志相结合，使得“我应该如何分配内存？”这个问
题不那么容易回答，尽管很可能你应该使用

::

  kzalloc(<size>, GFP_KERNEL);

当然，有些情况下必须使用其他分配API和不同的GFP标志。

获取空闲页标志
==============
GFP标志控制分配器的行为。它们告诉我们哪些内存区域可以被使用，分配器应该多努力寻
找空闲的内存，这些内存是否可以被用户空间访问等等。内存管理API为GFP标志和它们的
组合提供了参考文件，这里我们简要介绍一下它们的推荐用法:

  * 大多数时候， ``GFP_KERNEL`` 是你需要的。内核数据结构的内存，DMA可用内存，inode
    缓存，所有这些和其他许多分配类型都可以使用 ``GFP_KERNEL`` 。注意，使用 ``GFP_KERNEL``
    意味着 ``GFP_RECLAIM`` ，这意味着在有内存压力的情况下可能会触发直接回收；调用上
    下文必须允许睡眠。

  * 如果分配是从一个原子上下文中进行的，例如中断处理程序，使用 ``GFP_NOWAIT`` 。这个
    标志可以防止直接回收和IO或文件系统操作。因此，在内存压力下， ``GFP_NOWAIT`` 分配
    可能会失败。有合理退路的分配应该使用 ``GFP_NOWARN`` 。

  * 如果你认为访问保留内存区是合理的，并且除非分配成功，否则内核会有压力，你可以使用 ``GFP_ATOMIC`` 。

  * 从用户空间触发的不可信任的分配应该是kmem核算的对象，必须设置 ``__GFP_ACCOUNT`` 位。
    有一个方便的用于 ``GFP_KERNEL`` 分配的 ``GFP_KERNEL_ACCOUNT`` 快捷键，其应该被核
    算。

  * 用户空间的分配应该使用 ``GFP_USER`` 、 ``GFP_HIGHUSER`` 或 ``GFP_HIGHUSER_MOVABLE``
    中的一个标志。标志名称越长，限制性越小。

    ``GFP_HIGHUSER_MOVABLE`` 不要求分配的内存将被内核直接访问，并意味着数据是可迁移的。

    ``GFP_HIGHUSER`` 意味着所分配的内存是不可迁移的，但也不要求它能被内核直接访问。举个
    例子就是一个硬件分配内存，这些数据直接映射到用户空间，但没有寻址限制。

    ``GFP_USER`` 意味着分配的内存是不可迁移的，它必须被内核直接访问。

你可能会注意到，在现有的代码中，有相当多的分配指定了 ``GFP_NOIO`` 或 ``GFP_NOFS`` 。
从历史上看，它们被用来防止递归死锁，这种死锁是由直接内存回收调用到FS或IO路径以及对已
经持有的资源进行阻塞引起的。从4.12开始，解决这个问题的首选方法是使用新的范围API，即
:ref:`Documentation/core-api/gfp_mask-from-fs-io.rst <gfp_mask_from_fs_io>`.

其他传统的GFP标志是 ``GFP_DMA`` 和 ``GFP_DMA32`` 。它们用于确保分配的内存可以被寻
址能力有限的硬件访问。因此，除非你正在为一个有这种限制的设备编写驱动程序，否则要避免
使用这些标志。而且，即使是有限制的硬件，也最好使用dma_alloc* APIs。

GFP标志和回收行为
-----------------
内存分配可能会触发直接或后台回收，了解页面分配器将如何努力满足该请求或其他请求是非常
有用的。

  * ``GFP_KERNEL & ~__GFP_RECLAIM`` - 乐观分配，完全不尝试释放内存。最轻量级的模
    式，甚至不启动后台回收。应该小心使用，因为它可能会耗尽内存，而下一个用户可能会启
    动更积极的回收。

  * ``GFP_KERNEL & ~__GFP_DIRECT_RECLAIM`` (or ``GFP_NOWAIT`` ) - 乐观分配，不
    试图从当前上下文中释放内存，但如果该区域低于低水位，可以唤醒kswapd来回收内存。可
    以从原子上下文中使用，或者当请求是一个性能优化，并且有另一个慢速路径的回退。

  * ``(GFP_KERNEL|__GFP_HIGH) & ~__GFP_DIRECT_RECLAIM`` (aka ``GFP_ATOMIC`` ) - 非
    睡眠分配，有一个昂贵的回退，所以它可以访问某些部分的内存储备。通常从中断/底层上下
    文中使用，有一个昂贵的慢速路径回退。

  * ``GFP_KERNEL`` - 允许后台和直接回收，并使用默认的页面分配器行为。这意味着廉价
    的分配请求基本上是不会失败的，但不能保证这种行为，所以失败必须由调用者适当检查（例
    如，目前允许OOM杀手失败）。

  * ``GFP_KERNEL | __GFP_NORETRY`` - 覆盖默认的分配器行为，所有的分配请求都会提前
    失败，而不是导致破坏性的回收（在这个实现中是一轮的回收）。OOM杀手不被调用。

  * ``GFP_KERNEL | __GFP_RETRY_MAYFAIL`` - 覆盖 **默认** 的分配器行为，所有分配请求都非
    常努力。如果回收不能取得任何进展，该请求将失败。OOM杀手不会被触发。

  * ``GFP_KERNEL | __GFP_NOFAIL`` - 覆盖默认的分配器行为，所有分配请求将无休止地循
    环，直到成功。这可能真的很危险，特别是对于较大的需求。

选择内存分配器
==============

分配内存的最直接的方法是使用kmalloc()系列的函数。而且，为了安全起见，最好使用将内存
设置为零的例程，如kzalloc()。如果你需要为一个数组分配内存，有kmalloc_array()和kcalloc()
辅助程序。辅助程序struct_size()、array_size()和array3_size()可以用来安全地计算对
象的大小而不会溢出。

可以用 `kmalloc` 分配的块的最大尺寸是有限的。实际的限制取决于硬件和内核配置，但是对于
小于页面大小的对象，使用 `kmalloc` 是一个好的做法。

用 `kmalloc` 分配的块的地址至少要对齐到ARCH_KMALLOC_MINALIGN字节。对于2的幂的大小，
对齐方式也被保证为至少是各自的大小。

用kmalloc()分配的块可以用krealloc()调整大小。与kmalloc_array()类似：以krealloc_array()
的形式提供了一个用于调整数组大小的辅助工具。

对于大量的分配，你可以使用vmalloc()和vzalloc()，或者直接向页面分配器请求页面。由vmalloc
和相关函数分配的内存在物理上是不连续的。

如果你不确定分配的大小对 `kmalloc` 来说是否太大，可以使用kvmalloc()及其派生函数。它将尝
试用kmalloc分配内存，如果分配失败，将用 `vmalloc` 重新尝试。对于哪些GFP标志可以与 `kvmalloc`
一起使用是有限制的；请看kvmalloc_node()参考文档。注意， `kvmalloc` 可能会返回物理上不连
续的内存。

如果你需要分配许多相同的对象，你可以使用slab缓存分配器。在使用缓存之前，应该用
kmem_cache_create()或kmem_cache_create_usercopy()来设置缓存。如果缓存的一部分可能被复
制到用户空间，应该使用第二个函数。在缓存被创建后，kmem_cache_alloc()和它的封装可以从该缓
存中分配内存。

当分配的内存不再需要时，它必须被释放。你可以使用kvfree()来处理用 `kmalloc` 、 `vmalloc`
和 `kvmalloc` 分配的内存。slab缓存应该用kmem_cache_free()来释放。不要忘记用
kmem_cache_destroy()来销毁缓存。


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

:Original: Documentation/core-api/memory-hotplug.rst

:翻译:

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

:校译:

 吴想成 Wu XiangCheng <bobwxc@email.cn>

.. _cn_core-api_memory-hotplug:

==========
内存热插拔
==========

内存热拔插事件通知器
====================

热插拔事件被发送到一个通知队列中。

在 ``include/linux/memory.h`` 中定义了六种类型的通知：

MEM_GOING_ONLINE
  在新内存可用之前生成，以便能够为子系统处理内存做准备。页面分配器仍然无法从新
  的内存中进行分配。

MEM_CANCEL_ONLINE
  如果MEM_GOING_ONLINE失败，则生成。

MEM_ONLINE
  当内存成功上线时产生。回调可以从新的内存中分配页面。

MEM_GOING_OFFLINE
  在开始对内存进行下线处理时生成。从内存中的分配不再可能，但是一些要下线的内存
  仍然在使用。回调可以用来释放一个子系统在指定内存块中已知的内存。

MEM_CANCEL_OFFLINE
  如果MEM_GOING_OFFLINE失败，则生成。来自我们试图离线的内存块中的内存又可以使
  用了。

MEM_OFFLINE
  在内存下线完成后生成。

可以通过调用如下函数来注册一个回调程序:

  hotplug_memory_notifier(callback_func, priority)

优先级数值较高的回调函数在数值较低的回调函数之前被调用。

一个回调函数必须有以下原型::

  int callback_func(
    struct notifier_block *self, unsigned long action, void *arg);

回调函数的第一个参数（self）是指向回调函数本身的通知器链块的一个指针。第二个参
数（action）是上述的事件类型之一。第三个参数（arg）传递一个指向
memory_notify结构体的指针::

	struct memory_notify {
		unsigned long start_pfn;
		unsigned long nr_pages;
		int status_change_nid_normal;
		int status_change_nid;
	}

- start_pfn是在线/离线内存的start_pfn。

- nr_pages是在线/离线内存的页数。

- status_change_nid_normal是当nodemask的N_NORMAL_MEMORY被设置/清除时设置节
  点id，如果是-1，则nodemask状态不改变。

- status_change_nid是当nodemask的N_MEMORY被（将）设置/清除时设置的节点id。这
  意味着一个新的（没上线的）节点通过联机获得新的内存，而一个节点失去了所有的内
  存。如果这个值为-1，那么nodemask的状态就不会改变。

  如果 status_changed_nid* >= 0，回调应该在必要时为节点创建/丢弃结构体。

回调程序应返回 ``include/linux/notifier.h`` 中定义的NOTIFY_DONE, NOTIFY_OK,
NOTIFY_BAD, NOTIFY_STOP中的一个值。

NOTIFY_DONE和NOTIFY_OK对进一步处理没有影响。

NOTIFY_BAD是作为对MEM_GOING_ONLINE、MEM_GOING_OFFLINE、MEM_ONLINE或MEM_OFFLINE
动作的回应，用于取消热插拔。它停止对通知队列的进一步处理。

NOTIFY_STOP停止对通知队列的进一步处理。

内部锁
======

当添加/删除使用内存块设备（即普通RAM）的内存时，device_hotplug_lock应该被保持
为:

- 针对在线/离线请求进行同步（例如，通过sysfs）。这样一来，内存块设备只有在内存
  被完全添加后才能被用户空间访问（.online/.state属性）。而在删除内存时，我们知
  道没有人在临界区。

- 与CPU热拔插或类似操作同步（例如ACPI和PPC相关操作）

特别是，在添加内存和用户空间试图以比预期更快的速度上线该内存时，有可能出现锁反转，
使用device_hotplug_lock可以避免此情况:

- device_online()将首先接受device_lock()，然后是mem_hotplug_lock。

- add_memory_resource()将首先使用mem_hotplug_lock，然后是device_lock()（在创
  建设备时，在bus_add_device()期间）。

由于在使用device_lock()之前，设备对用户空间是可见的，这可能导致锁的反转。

内存的上线/下线应该通过device_online()/device_offline()完成————确保它与通过
sysfs进行的操作正确同步。建议持有device_hotplug_lock（例如，保护online_type）。

当添加/删除/上线/下线内存或者添加/删除异构或设备内存时，我们应该始终持有写模式的
mem_hotplug_lock，以序列化内存热插拔（例如访问全局/区域变量）。

此外，mem_hotplug_lock（与device_hotplug_lock相反）在读取模式下允许一个相当
有效的get_online_mems/put_online_mems实现，所以访问内存的代码可以防止该内存
消失。


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

:Original: Documentation/core-api/mm-api.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>
 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn>

:校译:

 时奎亮<alexs@kernel.org>

.. _cn_core-api_mm-api:

============
内存管理APIs
============

API（Application Programming Interface，应用程序接口）

用户空间内存访问
================

该API在以下内核代码中:

arch/x86/include/asm/uaccess.h

arch/x86/lib/usercopy_32.c

mm/gup.c

.. _cn_mm-api-gfp-flags:

内存分配控制
============

该API在以下内核代码中:

include/linux/gfp.h

Slab缓存
========

此缓存非cpu片上缓存，请读者自行查阅资料。

该API在以下内核代码中:

include/linux/slab.h

mm/slab.c

mm/slab_common.c

mm/util.c

虚拟连续（内存页）映射
======================

该API在以下内核代码中:

mm/vmalloc.c

文件映射和页面缓存
==================

该API在以下内核代码中:

文件映射
--------

mm/filemap.c

预读
----

mm/readahead.c

回写
----

mm/page-writeback.c

截断
----

mm/truncate.c

include/linux/pagemap.h

内存池
======

该API在以下内核代码中:

mm/mempool.c

DMA池
=====

DMA(Direct Memory Access，直接存储器访问)

该API在以下内核代码中:

mm/dmapool.c

更多的内存管理函数
==================

该API在以下内核代码中:

mm/memory.c

mm/page_alloc.c

mm/mempolicy.c

include/linux/mm_types.h

include/linux/mm_inline.h

include/linux/page-flags.h

include/linux/mm.h

include/linux/page_ref.h

include/linux/mmzone.h

mm/util.c


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

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

:Original: Documentation/core-api/packing.rst

:翻译:

 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn>

:校译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>
 吴想成 Wu Xiangcheng <bobwxc@email.cn>
 时奎亮 Alex Shi <alexs@kernel.org>

========================
通用的位域打包和解包函数
========================

问题陈述
--------

使用硬件时，必须在几种与其交互的方法之间进行选择。

可以将指针映射到在硬件设备的内存区上精心设计的结构体，并将其字段作为结构成员（可
能声明为位域）访问。但是由于CPU和硬件设备之间潜在的字节顺序不匹配，以这种方式编写
代码会降低其可移植性。

此外，必须密切注意将硬件文档中的寄存器定义转换为结构的位域索引。此外，一些硬件
（通常是网络设备）倾向于以违反任何合理字边界（有时甚至是64位）的方式对其寄存器字
段进行分组。这就造成了不得不在结构中定义寄存器字段的“高”和“低”部分的不便。

结构域定义的更可靠的替代方法是通过移动适当数量的位来提取所需的字段。但这仍然不能
防止字节顺序不匹配，除非所有内存访问都是逐字节执行的。此外，代码很容易变得杂乱无
章，同时可能会在所需的许多位移操作中丢失一些高层次的想法。

许多驱动程序采用了位移的方法，然后试图用定制的宏来减少杂乱无章的东西，但更多的时
候，这些宏所采用的捷径依旧妨碍了代码真正的可移植性。

解决方案
--------

该API涉及2个基本操作：

  - 将一个CPU可使用的数字打包到内存缓冲区中（具有硬件约束/特殊性）。
  - 将内存缓冲区（具有硬件约束/特殊性）解压缩为一个CPU可使用的数字。

该API提供了对所述硬件约束和特殊性以及CPU字节序的抽象，因此这两者之间可能不匹配。

这些API函数的基本单元是u64。从CPU的角度来看，位63总是意味着字节7的位偏移量7，尽管
只是逻辑上的。问题是：我们将这个比特放在内存的什么位置？

以下示例介绍了打包u64字段的内存布局。打包缓冲区中的字节偏移量始终默认为0，1...7。
示例显示的是逻辑字节和位所在的位置。

1. 通常情况下（无特殊性），我们会这样做：

::

  63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32
  7                       6                       5                        4
  31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
  3                       2                       1                        0

也就是说，CPU可使用的u64的MSByte(7)位于内存偏移量0处，而u64的LSByte(0)位于内存偏移量7处。

这对应于大多数人认为的“大端”，其中位i对应于数字2^i。这在代码注释中也称为“逻辑”符号。

2. 如果设置了QUIRK_MSB_ON_THE_RIGHT，我们按如下方式操作：

::

  56 57 58 59 60 61 62 63 48 49 50 51 52 53 54 55 40 41 42 43 44 45 46 47 32 33 34 35 36 37 38 39
  7                       6                        5                       4
  24 25 26 27 28 29 30 31 16 17 18 19 20 21 22 23  8  9 10 11 12 13 14 15  0  1  2  3  4  5  6  7
  3                       2                        1                       0

也就是说，QUIRK_MSB_ON_THE_RIGHT不会影响字节定位，但会反转字节内的位偏移量。

3. 如果设置了QUIRK_LITTLE_ENDIAN，我们按如下方式操作：

::

  39 38 37 36 35 34 33 32 47 46 45 44 43 42 41 40 55 54 53 52 51 50 49 48 63 62 61 60 59 58 57 56
  4                       5                       6                       7
  7  6  5  4  3  2  1  0  15 14 13 12 11 10  9  8 23 22 21 20 19 18 17 16 31 30 29 28 27 26 25 24
  0                       1                       2                       3

因此，QUIRK_LITTLE_ENDIAN意味着在内存区域内，每个4字节的字的每个字节都被放置在与
该字的边界相比的镜像位置。

4. 如果设置了QUIRK_MSB_ON_THE_RIGHT和QUIRK_LITTLE_ENDIAN，我们这样做：

::

  32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
  4                       5                       6                       7
  0  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
  0                       1                       2                       3

5. 如果只设置了QUIRK_LSW32_IS_FIRST，我们这样做：

::

  31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
  3                       2                       1                        0
  63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32
  7                       6                       5                        4

在这种情况下，8字节内存区域解释如下：前4字节对应最不重要的4字节的字，后4字节对应
更重要的4字节的字。

6. 如果设置了QUIRK_LSW32_IS_FIRST和QUIRK_MSB_ON_THE_RIGHT，我们这样做：

::

  24 25 26 27 28 29 30 31 16 17 18 19 20 21 22 23  8  9 10 11 12 13 14 15  0  1  2  3  4  5  6  7
  3                       2                        1                       0
  56 57 58 59 60 61 62 63 48 49 50 51 52 53 54 55 40 41 42 43 44 45 46 47 32 33 34 35 36 37 38 39
  7                       6                        5                       4

7. 如果设置了QUIRK_LSW32_IS_FIRST和QUIRK_LITTLE_ENDIAN，则如下所示：

::

  7  6  5  4  3  2  1  0  15 14 13 12 11 10  9  8 23 22 21 20 19 18 17 16 31 30 29 28 27 26 25 24
  0                       1                       2                       3
  39 38 37 36 35 34 33 32 47 46 45 44 43 42 41 40 55 54 53 52 51 50 49 48 63 62 61 60 59 58 57 56
  4                       5                       6                       7

8. 如果设置了QUIRK_LSW32_IS_FIRST，QUIRK_LITTLE_ENDIAN和QUIRK_MSB_ON_THE_RIGHT，
   则如下所示：

::

  0  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
  0                       1                       2                       3
  32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
  4                       5                       6                       7

我们总是认为我们的偏移量好像没有特殊性，然后在访问内存区域之前翻译它们。

预期用途
--------

选择使用该API的驱动程序首先需要确定上述3种quirk组合（共8种）中的哪一种与硬件文档
中描述的相匹配。然后，他们应该封装packing()函数，创建一个新的xxx_packing()，使用
适当的QUIRK_* one-hot 位集合来调用它。

packing()函数返回一个int类型的错误码，以防止程序员使用不正确的API。这些错误预计不
会在运行时发生，因此xxx_packing()返回void并简单地接受这些错误是合理的。它可以选择
转储栈或打印错误描述。


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

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

:Original: Documentation/core-api/padata.rst

:翻译:

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

.. _cn_core_api_padata.rst:

==================
padata并行执行机制
==================

:日期: 2020年5月

Padata是一种机制，内核可以通过此机制将工作分散到多个CPU上并行完成，同时
可以选择保持它们的顺序。

它最初是为IPsec开发的，它需要在不对这些数据包重新排序的其前提下，为大量的数
据包进行加密和解密。这是目前padata的序列化作业支持的唯一用途。

Padata还支持多线程作业，将作业平均分割，同时在线程之间进行负载均衡和协调。

执行序列化作业
==============

初始化
------

使用padata执行序列化作业的第一步是建立一个padata_instance结构体，以全面
控制作业的运行方式::

    #include <linux/padata.h>

    struct padata_instance *padata_alloc(const char *name);

'name'即标识了这个实例。

然后，通过分配一个padata_shell来完成padata的初始化::

   struct padata_shell *padata_alloc_shell(struct padata_instance *pinst);

一个padata_shell用于向padata提交一个作业，并允许一系列这样的作业被独立地
序列化。一个padata_instance可以有一个或多个padata_shell与之相关联，每个
都允许一系列独立的作业。

修改cpumasks
------------

用于运行作业的CPU可以通过两种方式改变，通过padata_set_cpumask()编程或通
过sysfs。前者的定义是::

    int padata_set_cpumask(struct padata_instance *pinst, int cpumask_type,
			   cpumask_var_t cpumask);

这里cpumask_type是PADATA_CPU_PARALLEL（并行）或PADATA_CPU_SERIAL（串行）之一，其中并
行cpumask描述了哪些处理器将被用来并行执行提交给这个实例的作业，串行cpumask
定义了哪些处理器被允许用作串行化回调处理器。 cpumask指定了要使用的新cpumask。

一个实例的cpumasks可能有sysfs文件。例如，pcrypt的文件在
/sys/kernel/pcrypt/<instance-name>。在一个实例的目录中，有两个文件，parallel_cpumask
和serial_cpumask，任何一个cpumask都可以通过在文件中回显（echo）一个bitmask
来改变，比如说::

    echo f > /sys/kernel/pcrypt/pencrypt/parallel_cpumask

读取其中一个文件会显示用户提供的cpumask，它可能与“可用”的cpumask不同。

Padata内部维护着两对cpumask，用户提供的cpumask和“可用的”cpumask(每一对由一个
并行和一个串行cpumask组成)。用户提供的cpumasks在实例分配时默认为所有可能的CPU，
并且可以如上所述进行更改。可用的cpumasks总是用户提供的cpumasks的一个子集，只包
含用户提供的掩码中的在线CPU；这些是padata实际使用的cpumasks。因此，向padata提
供一个包含离线CPU的cpumask是合法的。一旦用户提供的cpumask中的一个离线CPU上线，
padata就会使用它。

改变CPU掩码的操作代价很高，所以不应频繁更改。

运行一个作业
-------------

实际上向padata实例提交工作需要创建一个padata_priv结构体，它代表一个作业::

    struct padata_priv {
        /* Other stuff here... */
	void                    (*parallel)(struct padata_priv *padata);
	void                    (*serial)(struct padata_priv *padata);
    };

这个结构体几乎肯定会被嵌入到一些针对要做的工作的大结构体中。它的大部分字段对
padata来说是私有的，但是这个结构在初始化时应该被清零，并且应该提供parallel()和
serial()函数。在完成工作的过程中，这些函数将被调用，我们马上就会遇到。

工作的提交是通过::

    int padata_do_parallel(struct padata_shell *ps,
		           struct padata_priv *padata, int *cb_cpu);

ps和padata结构体必须如上所述进行设置；cb_cpu指向作业完成后用于最终回调的首选CPU；
它必须在当前实例的CPU掩码中（如果不是，cb_cpu指针将被更新为指向实际选择的CPU）。
padata_do_parallel()的返回值在成功时为0，表示工作正在进行中。-EBUSY意味着有人
在其他地方正在搞乱实例的CPU掩码，而当cb_cpu不在串行cpumask中、并行或串行cpumasks
中无在线CPU，或实例停止时，则会出现-EINVAL反馈。

每个提交给padata_do_parallel()的作业将依次传递给一个CPU上的上述parallel()函数
的一个调用，所以真正的并行是通过提交多个作业来实现的。parallel()在运行时禁用软
件中断，因此不能睡眠。parallel()函数把获得的padata_priv结构体指针作为其唯一的参
数；关于实际要做的工作的信息可能是通过使用container_of()找到封装结构体来获得的。

请注意，parallel()没有返回值；padata子系统假定parallel()将从此时开始负责这项工
作。作业不需要在这次调用中完成，但是，如果parallel()留下了未完成的工作，它应该准
备在前一个作业完成之前，被以新的作业再次调用

序列化作业
----------

当一个作业完成时，parallel()（或任何实际完成该工作的函数）应该通过调用通知padata此
事::

    void padata_do_serial(struct padata_priv *padata);

在未来的某个时刻，padata_do_serial()将触发对padata_priv结构体中serial()函数的调
用。这个调用将发生在最初要求调用padata_do_parallel()的CPU上；它也是在本地软件中断
被禁用的情况下运行的。
请注意，这个调用可能会被推迟一段时间，因为padata代码会努力确保作业按照提交的顺序完
成。

销毁
----

清理一个padata实例时，可以预见的是调用两个free函数，这两个函数对应于分配的逆过程::

    void padata_free_shell(struct padata_shell *ps);
    void padata_free(struct padata_instance *pinst);

用户有责任确保在调用上述任何一项之前，所有未完成的工作都已完成。

运行多线程作业
==============

一个多线程作业有一个主线程和零个或多个辅助线程，主线程参与作业，然后等待所有辅助线
程完成。padata将作业分割成称为chunk的单元，其中chunk是一个线程在一次调用线程函数
中完成的作业片段。

用户必须做三件事来运行一个多线程作业。首先，通过定义一个padata_mt_job结构体来描述
作业，这在接口部分有解释。这包括一个指向线程函数的指针，padata每次将作业块分配给线
程时都会调用这个函数。然后，定义线程函数，它接受三个参数： ``start`` 、 ``end`` 和 ``arg`` ，
其中前两个参数限定了线程操作的范围，最后一个是指向作业共享状态的指针，如果有的话。
准备好共享状态，它通常被分配在主线程的堆栈中。最后，调用padata_do_multithreaded()，
它将在作业完成后返回。

接口
====

该API在以下内核代码中:

include/linux/padata.h

kernel/padata.c


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

:Original: Documentation/core-api/printk-formats.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>
 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn>

.. _cn_printk-formats.rst:

==============================
如何获得正确的printk格式占位符
==============================

:作者: Randy Dunlap <rdunlap@infradead.org>
:作者: Andrew Murray <amurray@mpc-data.co.uk>

整数类型
========

::

	        若变量类型是Type，则使用printk格式占位符。
	        -------------------------------------------
		char			%d 或 %x
		unsigned char		%u 或 %x
		short int		%d 或 %x
		unsigned short int	%u 或 %x
		int			%d 或 %x
		unsigned int		%u 或 %x
		long			%ld 或 %lx
		unsigned long		%lu 或 %lx
		long long		%lld 或 %llx
		unsigned long long	%llu 或 %llx
		size_t			%zu 或 %zx
		ssize_t			%zd 或 %zx
		s8			%d 或 %x
		u8			%u 或 %x
		s16			%d 或 %x
		u16			%u 或 %x
		s32			%d 或 %x
		u32			%u 或 %x
		s64			%lld 或 %llx
		u64			%llu 或 %llx

如果 <type> 的大小依赖于配置选项 (例如 sector_t, blkcnt_t) 或其大小依赖于架构
(例如 tcflag_t)，则使用其可能的最大类型的格式占位符并显式强制转换为它。

例如

::

	printk("test: sector number/total blocks: %llu/%llu\n",
		(unsigned long long)sector, (unsigned long long)blockcount);

提醒：sizeof()返回类型为size_t。

内核的printf不支持%n。显而易见，浮点格式(%e, %f, %g, %a)也不被识别。使用任何不
支持的占位符或长度限定符都会导致一个WARN并且终止vsnprintf()执行。

指针类型
========

一个原始指针值可以用%p打印，它将在打印前对地址进行哈希处理。内核也支持扩展占位符来打印
不同类型的指针。

一些扩展占位符会打印给定地址上的数据，而不是打印地址本身。在这种情况下，以下错误消息可能
会被打印出来，而不是无法访问的消息::

	(null)	 data on plain NULL address
	(efault) data on invalid address
	(einval) invalid data on a valid address

普通指针
----------

::

	%p	abcdef12 or 00000000abcdef12

没有指定扩展名的指针（即没有修饰符的%p）被哈希（hash），以防止内核内存布局消息的泄露。这
样还有一个额外的好处，就是提供一个唯一的标识符。在64位机器上，前32位被清零。当没有足够的
熵进行散列处理时，内核将打印(ptrval)代替

如果可能的话，使用专门的修饰符，如%pS或%pB（如下所述），以避免打印一个必须事后解释的非哈
希地址。如果不可能，而且打印地址的目的是为调试提供更多的消息，使用%p，并在调试过程中
用 ``no_hash_pointers`` 参数启动内核，这将打印所有未修改的%p地址。如果你 *真的* 想知
道未修改的地址，请看下面的%px。

如果（也只有在）你将地址作为虚拟文件的内容打印出来，例如在procfs或sysfs中（使用
seq_printf()，而不是printk()）由用户空间进程读取，使用下面描述的%pK修饰符，不
要用%p或%px。

错误指针
--------

::

	%pe	-ENOSPC

用于打印错误指针(即IS_ERR()为真的指针)的符号错误名。不知道符号名的错误值会以十进制打印，
而作为%pe参数传递的非ERR_PTR会被视为普通的%p。

符号/函数指针
-------------

::

	%pS	versatile_init+0x0/0x110
	%ps	versatile_init
	%pSR	versatile_init+0x9/0x110
		(with __builtin_extract_return_addr() translation)
	%pB	prev_fn_of_versatile_init+0x88/0x88

``S`` 和 ``s`` 占位符用于打印符号格式的指针。它们的结果是符号名称带有(S)或不带有(s)偏移
量。如果禁用KALLSYMS，则打印符号地址。

``B`` 占位符的结果是带有偏移量的符号名，在打印堆栈回溯时应该使用。占位符将考虑编译器优化
的影响，当使用尾部调用并使用noreturn GCC属性标记时，可能会发生这种优化。

如果指针在一个模块内，模块名称和可选的构建ID将被打印在符号名称之后，并在说明符的末尾添加
一个额外的 ``b`` 。

::

	%pS	versatile_init+0x0/0x110 [module_name]
	%pSb	versatile_init+0x0/0x110 [module_name ed5019fdf5e53be37cb1ba7899292d7e143b259e]
	%pSRb	versatile_init+0x9/0x110 [module_name ed5019fdf5e53be37cb1ba7899292d7e143b259e]
		(with __builtin_extract_return_addr() translation)
	%pBb	prev_fn_of_versatile_init+0x88/0x88 [module_name ed5019fdf5e53be37cb1ba7899292d7e143b259e]

来自BPF / tracing追踪的探查指针
----------------------------------

::

	%pks	kernel string
	%pus	user string

``k`` 和 ``u`` 指定符用于打印来自内核内存(k)或用户内存(u)的先前探测的内存。后面的 ``s`` 指
定符的结果是打印一个字符串。对于直接在常规的vsnprintf()中使用时，(k)和(u)注释被忽略，但是，当
在BPF的bpf_trace_printk()之外使用时，它会读取它所指向的内存，不会出现错误。

内核指针
--------

::

	%pK	01234567 or 0123456789abcdef

用于打印应该对非特权用户隐藏的内核指针。%pK的行为取决于kptr_restrict sysctl——详见
Documentation/admin-guide/sysctl/kernel.rst。

未经修改的地址
--------------

::

	%px	01234567 or 0123456789abcdef

对于打印指针，当你 *真的* 想打印地址时。在用%px打印指针之前，请考虑你是否泄露了内核内
存布局的敏感消息。%px在功能上等同于%lx（或%lu）。%px是首选，因为它在grep查找时更唯一。
如果将来我们需要修改内核处理打印指针的方式，我们将能更好地找到调用点。

在使用%px之前，请考虑使用%p并在调试过程中启用' ' no_hash_pointer ' '内核参数是否足
够(参见上面的%p描述)。%px的一个有效场景可能是在panic发生之前立即打印消息，这样无论如何
都可以防止任何敏感消息被利用，使用%px就不需要用no_hash_pointer来重现panic。

指针差异
--------

::

	%td	2560
	%tx	a00

为了打印指针的差异，使用ptrdiff_t的%t修饰符。

例如::

	printk("test: difference between pointers: %td\n", ptr2 - ptr1);

结构体资源（Resources）
-----------------------

::

	%pr	[mem 0x60000000-0x6fffffff flags 0x2200] or
		[mem 0x0000000060000000-0x000000006fffffff flags 0x2200]
	%pR	[mem 0x60000000-0x6fffffff pref] or
		[mem 0x0000000060000000-0x000000006fffffff pref]

用于打印结构体资源。 ``R`` 和 ``r`` 占位符的结果是打印出的资源带有（R）或不带有（r）解码标志
成员。

通过引用传递。

物理地址类型 phys_addr_t
------------------------

::

	%pa[p]	0x01234567 or 0x0123456789abcdef

用于打印phys_addr_t类型（以及它的衍生物，如resource_size_t），该类型可以根据构建选项而
变化，无论CPU数据真实物理地址宽度如何。

通过引用传递。

DMA地址类型dma_addr_t
---------------------

::

	%pad	0x01234567 or 0x0123456789abcdef

用于打印dma_addr_t类型，该类型可以根据构建选项而变化，而不考虑CPU数据路径的宽度。

通过引用传递。

原始缓冲区为转义字符串
----------------------

::

	%*pE[achnops]

用于将原始缓冲区打印成转义字符串。对于以下缓冲区::

		1b 62 20 5c 43 07 22 90 0d 5d

几个例子展示了如何进行转换（不包括两端的引号）。::

		%*pE		"\eb \C\a"\220\r]"
		%*pEhp		"\x1bb \C\x07"\x90\x0d]"
		%*pEa		"\e\142\040\\\103\a\042\220\r\135"

转换规则是根据可选的标志组合来应用的(详见:c:func:`string_escape_mem` 内核文档):

	- a - ESCAPE_ANY
	- c - ESCAPE_SPECIAL
	- h - ESCAPE_HEX
	- n - ESCAPE_NULL
	- o - ESCAPE_OCTAL
	- p - ESCAPE_NP
	- s - ESCAPE_SPACE

默认情况下，使用 ESCAPE_ANY_NP。

ESCAPE_ANY_NP是许多情况下的明智选择，特别是对于打印SSID。

如果字段宽度被省略，那么将只转义1个字节。

原始缓冲区为十六进制字符串
--------------------------

::

	%*ph	00 01 02  ...  3f
	%*phC	00:01:02: ... :3f
	%*phD	00-01-02- ... -3f
	%*phN	000102 ... 3f

对于打印小的缓冲区（最长64个字节），可以用一定的分隔符作为一个
十六进制字符串。对于较大的缓冲区，可以考虑使用
:c:func:`print_hex_dump` 。

MAC/FDDI地址
------------

::

	%pM	00:01:02:03:04:05
	%pMR	05:04:03:02:01:00
	%pMF	00-01-02-03-04-05
	%pm	000102030405
	%pmR	050403020100

用于打印以十六进制表示的6字节MAC/FDDI地址。 ``M`` 和 ``m`` 占位符导致打印的
地址有(M)或没有(m)字节分隔符。默认的字节分隔符是冒号（：）。

对于FDDI地址，可以在 ``M`` 占位符之后使用 ``F`` 说明，以使用破折号(——)分隔符
代替默认的分隔符。

对于蓝牙地址， ``R`` 占位符应使用在 ``M`` 占位符之后，以使用反转的字节顺序，适
合于以小尾端顺序的蓝牙地址的肉眼可见的解析。

通过引用传递。

IPv4地址
--------

::

	%pI4	1.2.3.4
	%pi4	001.002.003.004
	%p[Ii]4[hnbl]

用于打印IPv4点分隔的十进制地址。 ``I4`` 和 ``i4`` 占位符的结果是打印的地址
有(i4)或没有(I4)前导零。

附加的 ``h`` 、 ``n`` 、 ``b`` 和 ``l`` 占位符分别用于指定主机、网络、大
尾端或小尾端地址。如果没有提供占位符，则使用默认的网络/大尾端顺序。

通过引用传递。

IPv6 地址
---------

::

	%pI6	0001:0002:0003:0004:0005:0006:0007:0008
	%pi6	00010002000300040005000600070008
	%pI6c	1:2:3:4:5:6:7:8

用于打印IPv6网络顺序的16位十六进制地址。 ``I6`` 和 ``i6`` 占位符的结果是
打印的地址有(I6)或没有(i6)分号。始终使用前导零。

额外的 ``c`` 占位符可与 ``I`` 占位符一起使用，以打印压缩的IPv6地址，如
https://tools.ietf.org/html/rfc5952 所述

通过引用传递。

IPv4/IPv6地址(generic, with port, flowinfo, scope)
--------------------------------------------------

::

	%pIS	1.2.3.4		or 0001:0002:0003:0004:0005:0006:0007:0008
	%piS	001.002.003.004	or 00010002000300040005000600070008
	%pISc	1.2.3.4		or 1:2:3:4:5:6:7:8
	%pISpc	1.2.3.4:12345	or [1:2:3:4:5:6:7:8]:12345
	%p[Ii]S[pfschnbl]

用于打印一个IP地址，不需要区分它的类型是AF_INET还是AF_INET6。一个指向有效结构
体sockaddr的指针，通过 ``IS`` 或 ``IS`` 指定，可以传递给这个格式占位符。

附加的 ``p`` 、  ``f`` 和 ``s`` 占位符用于指定port(IPv4, IPv6)、
flowinfo (IPv6)和sope(IPv6)。port有一个 ``:`` 前缀，flowinfo是 ``/`` 和
范围是 ``%`` ，每个后面都跟着实际的值。

对于IPv6地址，如果指定了额外的指定符 ``c`` ，则使用
https://tools.ietf.org/html/rfc5952 描述的压缩IPv6地址。
如https://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-07
所建议的，IPv6地址由'['，']'包围，以防止出现额外的占位符 ``p`` ， ``f`` 或 ``s`` 。

对于IPv4地址，也可以使用额外的 ``h`` ， ``n`` ， ``b`` 和 ``l`` 说
明符，但对于IPv6地址则忽略。

通过引用传递。

更多例子::

	%pISfc		1.2.3.4		or [1:2:3:4:5:6:7:8]/123456789
	%pISsc		1.2.3.4		or [1:2:3:4:5:6:7:8]%1234567890
	%pISpfc		1.2.3.4:12345	or [1:2:3:4:5:6:7:8]:12345/123456789

UUID/GUID地址
-------------

::

	%pUb	00010203-0405-0607-0809-0a0b0c0d0e0f
	%pUB	00010203-0405-0607-0809-0A0B0C0D0E0F
	%pUl	03020100-0504-0706-0809-0a0b0c0e0e0f
	%pUL	03020100-0504-0706-0809-0A0B0C0E0E0F

用于打印16字节的UUID/GUIDs地址。附加的 ``l`` , ``L`` , ``b`` 和 ``B`` 占位符用
于指定小写(l)或大写(L)十六进制表示法中的小尾端顺序，以及小写(b)或大写(B)十六进制表
示法中的大尾端顺序。

如果没有使用额外的占位符，则将打印带有小写十六进制表示法的默认大端顺序。

通过引用传递。

目录项（dentry）的名称
----------------------

::

	%pd{,2,3,4}
	%pD{,2,3,4}

用于打印dentry名称；如果我们用 :c:func:`d_move` 和它比较，名称可能是新旧混合的，但
不会oops。 %pd dentry比较安全，其相当于我们以前用的%s dentry->d_name.name，%pd<n>打
印 ``n`` 最后的组件。 %pD对结构文件做同样的事情。

通过引用传递。

块设备（block_device）名称
--------------------------

::

	%pg	sda, sda1 or loop0p1

用于打印block_device指针的名称。

va_format结构体
---------------

::

	%pV

用于打印结构体va_format。这些结构包含一个格式字符串
和va_list如下

::

	struct va_format {
		const char *fmt;
		va_list *va;
	};

实现 "递归vsnprintf"。

如果没有一些机制来验证格式字符串和va_list参数的正确性，请不要使用这个功能。

通过引用传递。

设备树节点
----------

::

	%pOF[fnpPcCF]

用于打印设备树节点结构。默认行为相当于%pOFf。

	- f - 设备节点全称
	- n - 设备节点名
	- p - 设备节点句柄
	- P - 设备节点路径规范(名称+@单位)
	- F - 设备节点标志
	- c - 主要兼容字符串
	- C - 全兼容字符串

当使用多个参数时，分隔符是':'。

例如

::

	%pOF	/foo/bar@0			- Node full name
	%pOFf	/foo/bar@0			- Same as above
	%pOFfp	/foo/bar@0:10			- Node full name + phandle
	%pOFfcF	/foo/bar@0:foo,device:--P-	- Node full name +
	                                          major compatible string +
						  node flags
							D - dynamic
							d - detached
							P - Populated
							B - Populated bus

通过引用传递。

Fwnode handles
--------------

::

	%pfw[fP]

用于打印fwnode_handles的消息。默认情况下是打印完整的节点名称，包括路径。
这些修饰符在功能上等同于上面的%pOF。

	- f - 节点的全名，包括路径。
	- P - 节点名称，包括地址（如果有的话）。

例如 (ACPI)

::

	%pfwf	\_SB.PCI0.CIO2.port@1.endpoint@0	- Full node name
	%pfwP	endpoint@0				- Node name

例如 (OF)

::

	%pfwf	/ocp@68000000/i2c@48072000/camera@10/port/endpoint - Full name
	%pfwP	endpoint				- Node name

时间和日期
----------

::

	%pt[RT]			YYYY-mm-ddTHH:MM:SS
	%pt[RT]s		YYYY-mm-dd HH:MM:SS
	%pt[RT]d		YYYY-mm-dd
	%pt[RT]t		HH:MM:SS
	%pt[RT][dt][r][s]

用于打印日期和时间::

	R  struct rtc_time structure
	T  time64_t type

以我们（人类）可读的格式。

默认情况下，年将以1900为单位递增，月将以1为单位递增。 使用%pt[RT]r (raw)
来抑制这种行为。

%pt[RT]s（空格）将覆盖ISO 8601的分隔符，在日期和时间之间使用''（空格）而
不是'T'（大写T）。当日期或时间被省略时，它不会有任何影响。

通过引用传递。

clk结构体
---------

::

	%pC	pll1
	%pCn	pll1

用于打印clk结构。%pC 和 %pCn 打印时钟的名称（通用时钟框架）或唯一的32位
ID（传统时钟框架）。

通过引用传递。

位图及其衍生物，如cpumask和nodemask
-----------------------------------

::

	%*pb	0779
	%*pbl	0,3-6,8-10

对于打印位图（bitmap）及其派生的cpumask和nodemask，%*pb输出以字段宽度为位数的位图，
%*pbl输出以字段宽度为位数的范围列表。

字段宽度用值传递，位图用引用传递。可以使用辅助宏cpumask_pr_args()和
nodemask_pr_args()来方便打印cpumask和nodemask。

标志位字段，如页标志、gfp_flags
-------------------------------

::

	%pGp	0x17ffffc0002036(referenced|uptodate|lru|active|private|node=0|zone=2|lastcpupid=0x1fffff)
	%pGg	GFP_USER|GFP_DMA32|GFP_NOWARN
	%pGv	read|exec|mayread|maywrite|mayexec|denywrite

将flags位字段打印为构造值的符号常量集合。标志的类型由第三个字符给出。目前支持的
是[p]age flags， [v]ma_flags(都期望 ``unsigned long *`` )和
[g]fp_flags(期望 ``gfp_t *`` )。标志名称和打印顺序取决于特定的类型。

注意，这种格式不应该直接用于跟踪点的:c:func:`TP_printk()` 部分。相反，应使
用 <trace/events/mmflags.h>中的show_*_flags()函数。

通过引用传递。

网络设备特性
------------

::

	%pNF	0x000000000000c000

用于打印netdev_features_t。

通过引用传递。

V4L2和DRM FourCC代码(像素格式)
------------------------------

::

	%p4cc

打印V4L2或DRM使用的FourCC代码，包括格式端序及其十六进制的数值。

通过引用传递。

例如::

	%p4cc	BG12 little-endian (0x32314742)
	%p4cc	Y10  little-endian (0x20303159)
	%p4cc	NV12 big-endian (0xb231564e)

谢谢
====

如果您添加了其他%p扩展，请在可行的情况下，用一个或多个测试用例扩展<lib/test_printf.c>。

谢谢你的合作和关注。


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

:Original: Documentation/core-api/protection-keys.rst

:翻译:

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

:校译:

 吴想成 Wu XiangCheng <bobwxc@email.cn>

.. _cn_core-api_protection-keys:

============
内存保护密钥
============

用户空间的内存保护密钥（Memory Protection Keys for Userspace，PKU，亦
即PKEYs）是英特尔Skylake（及以后）“可扩展处理器”服务器CPU上的一项功能。
它将在未来的非服务器英特尔处理器和未来的AMD处理器中可用。

对于任何希望测试或使用该功能的人来说，它在亚马逊的EC2 C5实例中是可用的，
并且已知可以在那里使用Ubuntu 17.04镜像运行。

内存保护密钥提供了一种机制来执行基于页面的保护，但在应用程序改变保护域
时不需要修改页表。它的工作原理是在每个页表项中为“保护密钥”分配4个以
前被忽略的位，从而提供16个可能的密钥。

还有一个新的用户可访问寄存器（PKRU），为每个密钥提供两个单独的位（访
问禁止和写入禁止）。作为一个CPU寄存器，PKRU在本质上是线程本地的，可能
会给每个线程提供一套不同于其他线程的保护措施。

有两条新指令（RDPKRU/WRPKRU）用于读取和写入新的寄存器。该功能仅在64位
模式下可用，尽管物理地址扩展页表中理论上有空间。这些权限只在数据访问上
强制执行，对指令获取没有影响。

系统调用
========

有3个系统调用可以直接与pkeys进行交互::

	int pkey_alloc(unsigned long flags, unsigned long init_access_rights)
	int pkey_free(int pkey);
	int pkey_mprotect(unsigned long start, size_t len,
			  unsigned long prot, int pkey);

在使用一个pkey之前，必须先用pkey_alloc()分配它。一个应用程序直接调用
WRPKRU指令，以改变一个密钥覆盖的内存的访问权限。在这个例子中，WRPKRU
被一个叫做pkey_set()的C函数所封装::

	int real_prot = PROT_READ|PROT_WRITE;
	pkey = pkey_alloc(0, PKEY_DISABLE_WRITE);
	ptr = mmap(NULL, PAGE_SIZE, PROT_NONE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
	ret = pkey_mprotect(ptr, PAGE_SIZE, real_prot, pkey);
	... application runs here

现在，如果应用程序需要更新'ptr'处的数据，它可以获得访问权，进行更新，
然后取消其写访问权::

	pkey_set(pkey, 0); // clear PKEY_DISABLE_WRITE
	*ptr = foo; // assign something
	pkey_set(pkey, PKEY_DISABLE_WRITE); // set PKEY_DISABLE_WRITE again

现在，当它释放内存时，它也将释放pkey，因为它不再被使用了::

	munmap(ptr, PAGE_SIZE);
	pkey_free(pkey);

.. note:: pkey_set()是RDPKRU和WRPKRU指令的一个封装器。在tools/testing/selftests/x86/protection_keys.c中可以找到一个实现实例。
          tools/testing/selftests/x86/protection_keys.c.

行为
====

内核试图使保护密钥与普通的mprotect()的行为一致。例如，如果你这样做::

	mprotect(ptr, size, PROT_NONE);
	something(ptr);

这样做的时候，你可以期待保护密钥的相同效果::

	pkey = pkey_alloc(0, PKEY_DISABLE_WRITE | PKEY_DISABLE_READ);
	pkey_mprotect(ptr, size, PROT_READ|PROT_WRITE, pkey);
	something(ptr);

无论something()是否是对'ptr'的直接访问，这都应该为真。
如::

	*ptr = foo;

或者当内核代表应用程序进行访问时，比如read()::

	read(fd, ptr, 1);

在这两种情况下，内核都会发送一个SIGSEGV，但当违反保护密钥时，si_code
将被设置为SEGV_PKERR，而当违反普通的mprotect()权限时，则是SEGV_ACCERR。


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

:Original: Documentation/core-api/rbtree.rst

:翻译:

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

=========================
Linux中的红黑树（rbtree）
=========================

:日期: 2007年1月18日
:作者: Rob Landley <rob@landley.net>

何为红黑树，它们有什么用？
--------------------------

红黑树是一种自平衡二叉搜索树，被用来存储可排序的键/值数据对。这与基数树（被用来高效
存储稀疏数组，因此使用长整型下标来插入/访问/删除结点）和哈希表（没有保持排序因而无法
容易地按序遍历，同时必须调节其大小和哈希函数，然而红黑树可以优雅地伸缩以便存储任意
数量的键）不同。

红黑树和AVL树类似，但在插入和删除时提供了更快的实时有界的最坏情况性能（分别最多两次
旋转和三次旋转，来平衡树），查询时间轻微变慢（但时间复杂度仍然是O(log n)）。

引用Linux每周新闻（Linux Weekly News）：

    内核中有多处红黑树的使用案例。最后期限调度器和完全公平排队（CFQ）I/O调度器利用
    红黑树跟踪请求；数据包CD/DVD驱动程序也是如此。高精度时钟代码使用一颗红黑树组织
    未完成的定时器请求。ext3文件系统用红黑树跟踪目录项。虚拟内存区域（VMAs）、epoll
    文件描述符、密码学密钥和在“分层令牌桶”调度器中的网络数据包都由红黑树跟踪。

本文档涵盖了对Linux红黑树实现的使用方法。更多关于红黑树的性质和实现的信息，参见：

  Linux每周新闻关于红黑树的文章
    https://lwn.net/Articles/184495/

  维基百科红黑树词条
    https://en.wikipedia.org/wiki/Red-black_tree

红黑树的Linux实现
-----------------

Linux的红黑树实现在文件“lib/rbtree.c”中。要使用它，需要“#include <linux/rbtree.h>”。

Linux的红黑树实现对速度进行了优化，因此比传统的实现少一个间接层（有更好的缓存局部性）。
每个rb_node结构体的实例嵌入在它管理的数据结构中，因此不需要靠指针来分离rb_node和它
管理的数据结构。用户应该编写他们自己的树搜索和插入函数，来调用已提供的红黑树函数，
而不是使用一个比较回调函数指针。加锁代码也留给红黑树的用户编写。

创建一颗红黑树
--------------

红黑树中的数据结点是包含rb_node结构体成员的结构体::

  struct mytype {
  	struct rb_node node;
  	char *keystring;
  };

当处理一个指向内嵌rb_node结构体的指针时，包住rb_node的结构体可用标准的container_of()
宏访问。此外，个体成员可直接用rb_entry(node, type, member)访问。

每颗红黑树的根是一个rb_root数据结构，它由以下方式初始化为空:

  struct rb_root mytree = RB_ROOT;

在一颗红黑树中搜索值
--------------------

为你的树写一个搜索函数是相当简单的：从树根开始，比较每个值，然后根据需要继续前往左边或
右边的分支。

示例::

  struct mytype *my_search(struct rb_root *root, char *string)
  {
  	struct rb_node *node = root->rb_node;

  	while (node) {
  		struct mytype *data = container_of(node, struct mytype, node);
		int result;

		result = strcmp(string, data->keystring);

		if (result < 0)
  			node = node->rb_left;
		else if (result > 0)
  			node = node->rb_right;
		else
  			return data;
	}
	return NULL;
  }

在一颗红黑树中插入数据
----------------------

在树中插入数据的步骤包括：首先搜索插入新结点的位置，然后插入结点并对树再平衡
（"recoloring"）。

插入的搜索和上文的搜索不同，它要找到嫁接新结点的位置。新结点也需要一个指向它的父节点
的链接，以达到再平衡的目的。

示例::

  int my_insert(struct rb_root *root, struct mytype *data)
  {
  	struct rb_node **new = &(root->rb_node), *parent = NULL;

  	/* Figure out where to put new node */
  	while (*new) {
  		struct mytype *this = container_of(*new, struct mytype, node);
  		int result = strcmp(data->keystring, this->keystring);

		parent = *new;
  		if (result < 0)
  			new = &((*new)->rb_left);
  		else if (result > 0)
  			new = &((*new)->rb_right);
  		else
  			return FALSE;
  	}

  	/* Add new node and rebalance tree. */
  	rb_link_node(&data->node, parent, new);
  	rb_insert_color(&data->node, root);

	return TRUE;
  }

在一颗红黑树中删除或替换已经存在的数据
--------------------------------------

若要从树中删除一个已经存在的结点，调用::

  void rb_erase(struct rb_node *victim, struct rb_root *tree);

示例::

  struct mytype *data = mysearch(&mytree, "walrus");

  if (data) {
  	rb_erase(&data->node, &mytree);
  	myfree(data);
  }

若要用一个新结点替换树中一个已经存在的键值相同的结点，调用::

  void rb_replace_node(struct rb_node *old, struct rb_node *new,
  			struct rb_root *tree);

通过这种方式替换结点不会对树做重排序：如果新结点的键值和旧结点不同，红黑树可能被
破坏。

（按排序的顺序）遍历存储在红黑树中的元素
----------------------------------------

我们提供了四个函数，用于以排序的方式遍历一颗红黑树的内容。这些函数可以在任意红黑树
上工作，并且不需要被修改或包装（除非加锁的目的）::

  struct rb_node *rb_first(struct rb_root *tree);
  struct rb_node *rb_last(struct rb_root *tree);
  struct rb_node *rb_next(struct rb_node *node);
  struct rb_node *rb_prev(struct rb_node *node);

要开始迭代，需要使用一个指向树根的指针调用rb_first()或rb_last()，它将返回一个指向
树中第一个或最后一个元素所包含的节点结构的指针。要继续的话，可以在当前结点上调用
rb_next()或rb_prev()来获取下一个或上一个结点。当没有剩余的结点时，将返回NULL。

迭代器函数返回一个指向被嵌入的rb_node结构体的指针，由此，包住rb_node的结构体可用
标准的container_of()宏访问。此外，个体成员可直接用rb_entry(node, type, member)
访问。

示例::

  struct rb_node *node;
  for (node = rb_first(&mytree); node; node = rb_next(node))
	printk("key=%s\n", rb_entry(node, struct mytype, node)->keystring);

带缓存的红黑树
--------------

计算最左边（最小的）结点是二叉搜索树的一个相当常见的任务，例如用于遍历，或用户根据
他们自己的逻辑依赖一个特定的顺序。为此，用户可以使用'struct rb_root_cached'来优化
时间复杂度为O(logN)的rb_first()的调用，以简单地获取指针，避免了潜在的昂贵的树迭代。
维护操作的额外运行时间开销可忽略，不过内存占用较大。

和rb_root结构体类似，带缓存的红黑树由以下方式初始化为空::

  struct rb_root_cached mytree = RB_ROOT_CACHED;

带缓存的红黑树只是一个常规的rb_root，加上一个额外的指针来缓存最左边的节点。这使得
rb_root_cached可以存在于rb_root存在的任何地方，并且只需增加几个接口来支持带缓存的
树::

  struct rb_node *rb_first_cached(struct rb_root_cached *tree);
  void rb_insert_color_cached(struct rb_node *, struct rb_root_cached *, bool);
  void rb_erase_cached(struct rb_node *node, struct rb_root_cached *);

操作和删除也有对应的带缓存的树的调用::

  void rb_insert_augmented_cached(struct rb_node *node, struct rb_root_cached *,
				  bool, struct rb_augment_callbacks *);
  void rb_erase_augmented_cached(struct rb_node *, struct rb_root_cached *,
				 struct rb_augment_callbacks *);

对增强型红黑树的支持
--------------------

增强型红黑树是一种在每个结点里存储了“一些”附加数据的红黑树，其中结点N的附加数据
必须是以N为根的子树中所有结点的内容的函数。它是建立在红黑树基础设施之上的可选特性。
想要使用这个特性的红黑树用户，插入和删除结点时必须调用增强型接口并提供增强型回调函数。

实现增强型红黑树操作的C文件必须包含<linux/rbtree_augmented.h>而不是<linux/rbtree.h>。
注意，linux/rbtree_augmented.h暴露了一些红黑树实现的细节而你不应依赖它们，请坚持
使用文档记录的API，并且不要在头文件中包含<linux/rbtree_augmented.h>，以最小化你的
用户意外地依赖这些实现细节的可能。

插入时，用户必须更新通往被插入节点的路径上的增强信息，然后像往常一样调用rb_link_node()，
然后是rb_augment_inserted()而不是平时的rb_insert_color()调用。如果
rb_augment_inserted()再平衡了红黑树，它将回调至一个用户提供的函数来更新受影响的
子树上的增强信息。

删除一个结点时，用户必须调用rb_erase_augmented()而不是rb_erase()。
rb_erase_augmented()回调至一个用户提供的函数来更新受影响的子树上的增强信息。

在两种情况下，回调都是通过rb_augment_callbacks结构体提供的。必须定义3个回调：

- 一个传播回调，它更新一个给定结点和它的祖先们的增强数据，直到一个给定的停止点
  （如果是NULL，将更新一路更新到树根）。

- 一个复制回调，它将一颗给定子树的增强数据复制到一个新指定的子树树根。

- 一个树旋转回调，它将一颗给定的子树的增强值复制到新指定的子树树根上，并重新计算
  先前的子树树根的增强值。

rb_erase_augmented()编译后的代码可能会内联传播、复制回调，这将导致函数体积更大，
因此每个增强型红黑树的用户应该只有一个rb_erase_augmented()的调用点，以限制编译后
的代码大小。

使用示例
^^^^^^^^

区间树是增强型红黑树的一个例子。参考Cormen，Leiserson，Rivest和Stein写的
《算法导论》。区间树的更多细节：

经典的红黑树只有一个键，它不能直接用来存储像[lo:hi]这样的区间范围，也不能快速查找
与新的lo:hi重叠的部分，或者查找是否有与新的lo:hi完全匹配的部分。

然而，红黑树可以被增强，以一种结构化的方式来存储这种区间范围，从而使高效的查找和
精确匹配成为可能。

这个存储在每个节点中的“额外信息”是其所有后代结点中的最大hi（max_hi）值。这个信息
可以保持在每个结点上，只需查看一下该结点和它的直系子结点们。这将被用于时间复杂度
为O(log n)的最低匹配查找（所有可能的匹配中最低的起始地址），就像这样::

  struct interval_tree_node *
  interval_tree_first_match(struct rb_root *root,
			    unsigned long start, unsigned long last)
  {
	struct interval_tree_node *node;

	if (!root->rb_node)
		return NULL;
	node = rb_entry(root->rb_node, struct interval_tree_node, rb);

	while (true) {
		if (node->rb.rb_left) {
			struct interval_tree_node *left =
				rb_entry(node->rb.rb_left,
					 struct interval_tree_node, rb);
			if (left->__subtree_last >= start) {
				/*
				 * Some nodes in left subtree satisfy Cond2.
				 * Iterate to find the leftmost such node N.
				 * If it also satisfies Cond1, that's the match
				 * we are looking for. Otherwise, there is no
				 * matching interval as nodes to the right of N
				 * can't satisfy Cond1 either.
				 */
				node = left;
				continue;
			}
		}
		if (node->start <= last) {		/* Cond1 */
			if (node->last >= start)	/* Cond2 */
				return node;	/* node is leftmost match */
			if (node->rb.rb_right) {
				node = rb_entry(node->rb.rb_right,
					struct interval_tree_node, rb);
				if (node->__subtree_last >= start)
					continue;
			}
		}
		return NULL;	/* No match */
	}
  }

插入/删除是通过以下增强型回调来定义的::

  static inline unsigned long
  compute_subtree_last(struct interval_tree_node *node)
  {
	unsigned long max = node->last, subtree_last;
	if (node->rb.rb_left) {
		subtree_last = rb_entry(node->rb.rb_left,
			struct interval_tree_node, rb)->__subtree_last;
		if (max < subtree_last)
			max = subtree_last;
	}
	if (node->rb.rb_right) {
		subtree_last = rb_entry(node->rb.rb_right,
			struct interval_tree_node, rb)->__subtree_last;
		if (max < subtree_last)
			max = subtree_last;
	}
	return max;
  }

  static void augment_propagate(struct rb_node *rb, struct rb_node *stop)
  {
	while (rb != stop) {
		struct interval_tree_node *node =
			rb_entry(rb, struct interval_tree_node, rb);
		unsigned long subtree_last = compute_subtree_last(node);
		if (node->__subtree_last == subtree_last)
			break;
		node->__subtree_last = subtree_last;
		rb = rb_parent(&node->rb);
	}
  }

  static void augment_copy(struct rb_node *rb_old, struct rb_node *rb_new)
  {
	struct interval_tree_node *old =
		rb_entry(rb_old, struct interval_tree_node, rb);
	struct interval_tree_node *new =
		rb_entry(rb_new, struct interval_tree_node, rb);

	new->__subtree_last = old->__subtree_last;
  }

  static void augment_rotate(struct rb_node *rb_old, struct rb_node *rb_new)
  {
	struct interval_tree_node *old =
		rb_entry(rb_old, struct interval_tree_node, rb);
	struct interval_tree_node *new =
		rb_entry(rb_new, struct interval_tree_node, rb);

	new->__subtree_last = old->__subtree_last;
	old->__subtree_last = compute_subtree_last(old);
  }

  static const struct rb_augment_callbacks augment_callbacks = {
	augment_propagate, augment_copy, augment_rotate
  };

  void interval_tree_insert(struct interval_tree_node *node,
			    struct rb_root *root)
  {
	struct rb_node **link = &root->rb_node, *rb_parent = NULL;
	unsigned long start = node->start, last = node->last;
	struct interval_tree_node *parent;

	while (*link) {
		rb_parent = *link;
		parent = rb_entry(rb_parent, struct interval_tree_node, rb);
		if (parent->__subtree_last < last)
			parent->__subtree_last = last;
		if (start < parent->start)
			link = &parent->rb.rb_left;
		else
			link = &parent->rb.rb_right;
	}

	node->__subtree_last = last;
	rb_link_node(&node->rb, rb_parent, link);
	rb_insert_augmented(&node->rb, root, &augment_callbacks);
  }

  void interval_tree_remove(struct interval_tree_node *node,
			    struct rb_root *root)
  {
	rb_erase_augmented(&node->rb, root, &augment_callbacks);
  }


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

:Original: Documentation/core-api/refcount-vs-atomic.rst

:翻译:

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

.. _cn_refcount-vs-atomic:

=======================================
与atomic_t相比，refcount_t的API是这样的
=======================================

.. contents:: :local:

简介
====

refcount_t API的目标是为实现对象的引用计数器提供一个最小的API。虽然来自
lib/refcount.c的独立于架构的通用实现在下面使用了原子操作，但一些 ``refcount_*()``
和 ``atomic_*()`` 函数在内存顺序保证方面有很多不同。本文档概述了这些差异，并
提供了相应的例子，以帮助开发者根据这些内存顺序保证的变化来验证他们的代码。

本文档中使用的术语尽量遵循tools/memory-model/Documentation/explanation.txt
中定义的正式LKMM。

memory-barriers.txt和atomic_t.txt提供了更多关于内存顺序的背景，包括通用的
和针对原子操作的。

内存顺序的相关类型
==================

.. note:: 下面的部分只涵盖了本文使用的与原子操作和引用计数器有关的一些内存顺
   序类型。如果想了解更广泛的情况，请查阅memory-barriers.txt文件。

在没有任何内存顺序保证的情况下（即完全无序），atomics和refcounters只提供原
子性和程序顺序（program order, po）关系（在同一个CPU上）。它保证每个
``atomic_* ()`` 和 ``refcount_*()`` 操作都是原子性的，指令在单个CPU上按程序
顺序执行。这是用READ_ONCE()/WRITE_ONCE()和比较并交换原语实现的。

强（完全）内存顺序保证在同一CPU上的所有较早加载和存储的指令（所有程序顺序较早
[po-earlier]指令）在执行任何程序顺序较后指令（po-later）之前完成。它还保证
同一CPU上储存的程序优先较早的指令和来自其他CPU传播的指令必须在该CPU执行任何
程序顺序较后指令之前传播到其他CPU（A-累积属性）。这是用smp_mb()实现的。

RELEASE内存顺序保证了在同一CPU上所有较早加载和存储的指令（所有程序顺序较早
指令）在此操作前完成。它还保证同一CPU上储存的程序优先较早的指令和来自其他CPU
传播的指令必须在释放（release）操作之前传播到所有其他CPU（A-累积属性）。这是用
smp_store_release()实现的。

ACQUIRE内存顺序保证了同一CPU上的所有后加载和存储的指令（所有程序顺序较后
指令）在获取（acquire）操作之后完成。它还保证在获取操作执行后，同一CPU上
储存的所有程序顺序较后指令必须传播到所有其他CPU。这是用
smp_acquire__after_ctrl_dep()实现的。

对Refcounters的控制依赖（取决于成功）保证了如果一个对象的引用被成功获得（引用计数
器的增量或增加行为发生了，函数返回true），那么进一步的存储是针对这个操作的命令。对存
储的控制依赖没有使用任何明确的屏障来实现，而是依赖于CPU不对存储进行猜测。这只是
一个单一的CPU关系，对其他CPU不提供任何保证。

函数的比较
==========

情况1） - 非 “读/修改/写”（RMW）操作
------------------------------------

函数变化:

 * atomic_set() --> refcount_set()
 * atomic_read() --> refcount_read()

内存顺序保证变化:

 * none (两者都是完全无序的)

情况2） - 基于增量的操作，不返回任何值
--------------------------------------

函数变化:

 * atomic_inc() --> refcount_inc()
 * atomic_add() --> refcount_add()

内存顺序保证变化:

 * none (两者都是完全无序的)

情况3） - 基于递减的RMW操作，没有返回值
---------------------------------------

函数变化:

 * atomic_dec() --> refcount_dec()

内存顺序保证变化:

 * 完全无序的 --> RELEASE顺序

情况4） - 基于增量的RMW操作，返回一个值
---------------------------------------

函数变化:

 * atomic_inc_not_zero() --> refcount_inc_not_zero()
 * 无原子性对应函数 --> refcount_add_not_zero()

内存顺序保证变化:

 * 完全有序的 --> 控制依赖于存储的成功

.. note:: 此处 **假设** 了，必要的顺序是作为获得对象指针的结果而提供的。

情况 5） - 基于Dec/Sub递减的通用RMW操作，返回一个值
---------------------------------------------------

函数变化:

 * atomic_dec_and_test() --> refcount_dec_and_test()
 * atomic_sub_and_test() --> refcount_sub_and_test()

内存顺序保证变化:

 * 完全有序的 --> RELEASE顺序 + 成功后ACQUIRE顺序

情况6）其他基于递减的RMW操作，返回一个值
----------------------------------------

函数变化:

 * 无原子性对应函数 --> refcount_dec_if_one()
 * ``atomic_add_unless(&var, -1, 1)`` --> ``refcount_dec_not_one(&var)``

内存顺序保证变化:

 * 完全有序的 --> RELEASE顺序 + 控制依赖

.. note:: atomic_add_unless()只在执行成功时提供完整的顺序。

情况7）--基于锁的RMW
--------------------

函数变化:

 * atomic_dec_and_lock() --> refcount_dec_and_lock()
 * atomic_dec_and_mutex_lock() --> refcount_dec_and_mutex_lock()

内存顺序保证变化:

 * 完全有序 --> RELEASE顺序 + 控制依赖 + 持有


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

:Original: Documentation/core-api/symbol-namespaces.rst

:翻译:

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

.. _cn_symbol-namespaces.rst:

=================================
符号命名空间（Symbol Namespaces）
=================================

本文档描述了如何使用符号命名空间来构造通过EXPORT_SYMBOL()系列宏导出的内核内符号的导出面。

.. 目录

       === 1 简介
       === 2 如何定义符号命名空间
          --- 2.1 使用EXPORT_SYMBOL宏
          --- 2.2 使用DEFAULT_SYMBOL_NAMESPACE定义
       === 3 如何使用命名空间中导出的符号
       === 4 加载使用命名空间符号的模块
       === 5 自动创建MODULE_IMPORT_NS声明

1. 简介
=======

符号命名空间已经被引入，作为构造内核内API的导出面的一种手段。它允许子系统维护者将
他们导出的符号划分进独立的命名空间。这对于文档的编写非常有用（想想SUBSYSTEM_DEBUG
命名空间），也可以限制一组符号在内核其他部分的使用。今后，使用导出到命名空间的符号
的模块必须导入命名空间。否则，内核将根据其配置，拒绝加载该模块或警告说缺少
导入。

2. 如何定义符号命名空间
=======================

符号可以用不同的方法导出到命名空间。所有这些都在改变 EXPORT_SYMBOL 和与之类似的那些宏
被检测到的方式，以创建 ksymtab 条目。

2.1 使用EXPORT_SYMBOL宏
=======================

除了允许将内核符号导出到内核符号表的宏EXPORT_SYMBOL()和EXPORT_SYMBOL_GPL()之外，
这些宏的变体还可以将符号导出到某个命名空间：EXPORT_SYMBOL_NS() 和 EXPORT_SYMBOL_NS_GPL()。
它们需要一个额外的参数：命名空间（the namespace）。请注意，由于宏扩展，该参数需
要是一个预处理器符号。例如，要把符号 ``usb_stor_suspend`` 导出到命名空间 ``USB_STORAGE``，
请使用::

       EXPORT_SYMBOL_NS(usb_stor_suspend, USB_STORAGE);

相应的 ksymtab 条目结构体 ``kernel_symbol`` 将有相应的成员 ``命名空间`` 集。
导出时未指明命名空间的符号将指向 ``NULL`` 。如果没有定义命名空间，则默认没有。
``modpost`` 和kernel/module/main.c分别在构建时或模块加载时使用名称空间。

2.2 使用DEFAULT_SYMBOL_NAMESPACE定义
====================================

为一个子系统的所有符号定义命名空间可能会非常冗长，并可能变得难以维护。因此，我
们提供了一个默认定义（DEFAULT_SYMBOL_NAMESPACE），如果设置了这个定义， 它将成
为所有没有指定命名空间的 EXPORT_SYMBOL() 和 EXPORT_SYMBOL_GPL() 宏扩展的默认
定义。

有多种方法来指定这个定义，使用哪种方法取决于子系统和维护者的喜好。第一种方法是在
子系统的 ``Makefile`` 中定义默认命名空间。例如，如果要将usb-common中定义的所有符号导
出到USB_COMMON命名空间，可以在drivers/usb/common/Makefile中添加这样一行::

       ccflags-y += -DDEFAULT_SYMBOL_NAMESPACE=USB_COMMON

这将影响所有 EXPORT_SYMBOL() 和 EXPORT_SYMBOL_GPL() 语句。当这个定义存在时，
用EXPORT_SYMBOL_NS()导出的符号仍然会被导出到作为命名空间参数传递的命名空间中，
因为这个参数优先于默认的符号命名空间。

定义默认命名空间的第二个选项是直接在编译单元中作为预处理声明。上面的例子就会变
成::

       #undef  DEFAULT_SYMBOL_NAMESPACE
       #define DEFAULT_SYMBOL_NAMESPACE USB_COMMON

应置于相关编译单元中任何 EXPORT_SYMBOL 宏之前

3. 如何使用命名空间中导出的符号
===============================

为了使用被导出到命名空间的符号，内核模块需要明确地导入这些命名空间。
否则内核可能会拒绝加载该模块。模块代码需要使用宏MODULE_IMPORT_NS来
表示它所使用的命名空间的符号。例如，一个使用usb_stor_suspend符号的
模块，需要使用如下语句导入命名空间USB_STORAGE::

       MODULE_IMPORT_NS(USB_STORAGE);

这将在模块中为每个导入的命名空间创建一个 ``modinfo`` 标签。这也顺带
使得可以用modinfo检查模块已导入的命名空间::

       $ modinfo drivers/usb/storage/ums-karma.ko
       [...]
       import_ns:      USB_STORAGE
       [...]

建议将 MODULE_IMPORT_NS() 语句添加到靠近其他模块元数据定义的地方，
如 MODULE_AUTHOR() 或 MODULE_LICENSE() 。关于自动创建缺失的导入
语句的方法，请参考第5节。

4. 加载使用命名空间符号的模块
=============================

在模块加载时（比如 ``insmod`` ），内核将检查每个从模块中引用的符号是否可
用，以及它可能被导出到的名字空间是否被模块导入。内核的默认行为是拒绝
加载那些没有指明足以导入的模块。此错误会被记录下来，并且加载将以
EINVAL方式失败。要允许加载不满足这个前提条件的模块，可以使用此配置选项：
设置 MODULE_ALLOW_MISSING_NAMESPACE_IMPORTS=y 将使加载不受影响，但会
发出警告。

5. 自动创建MODULE_IMPORT_NS声明
===============================

缺少命名空间的导入可以在构建时很容易被检测到。事实上，如果一个模块
使用了一个命名空间的符号而没有导入它，modpost会发出警告。
MODULE_IMPORT_NS()语句通常会被添加到一个明确的位置（和其他模块元
数据一起）。为了使模块作者（和子系统维护者）的生活更加轻松，我们提
供了一个脚本和make目标来修复丢失的导入。修复丢失的导入可以用::

       $ make nsdeps

对模块作者来说，以下情况可能很典型::

       - 编写依赖未导入命名空间的符号的代码
       - ``make``
       - 注意 ``modpost`` 的警告，提醒你有一个丢失的导入。
       - 运行 ``make nsdeps``将导入添加到正确的代码位置。

对于引入命名空间的子系统维护者来说，其步骤非常相似。同样，make nsdeps最终将
为树内模块添加缺失的命名空间导入::

       - 向命名空间转移或添加符号（例如，使用EXPORT_SYMBOL_NS()）。
       - `make e`（最好是用allmodconfig来覆盖所有的内核模块）。
       - 注意 ``modpost`` 的警告，提醒你有一个丢失的导入。
       - 运行 ``maknsdeps``将导入添加到正确的代码位置。

你也可以为外部模块的构建运行nsdeps。典型的用法是::

       $ make -C <path_to_kernel_src> M=$PWD nsdeps


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

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

:Original: Documentation/core-api/this_cpu_ops.rst

:翻译:

 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn>

:校译:

 吴想成 Wu Xiangcheng <bobwxc@email.cn>

============
this_cpu操作
============

:作者: Christoph Lameter, 2014年8月4日
:作者: Pranith Kumar, 2014年8月2日

this_cpu操作是一种优化访问与当前执行处理器相关的每CPU变量的方法。这是通过使用段寄
存器（或专用寄存器，cpu在其中永久存储特定处理器的每CPU区域的起始）来完成的。

this_cpu操作将每CPU变量的偏移量添加到处理器特定的每CPU基址上，并将该操作编码到对
每CPU变量进行操作的指令中。

这意味着在偏移量的计算和对数据的操作之间不存在原子性问题。因此，没有必要禁用抢占
或中断来确保处理器在计算地址和数据操作之间不被改变。

读取-修改-写入操作特别值得关注。通常处理器具有特殊的低延迟指令，可以在没有典型同
步开销的情况下运行，但仍提供某种宽松的原子性保证。例如，x86可以执行RMW（读取，
修改，写入）指令，如同inc/dec/cmpxchg，而无需锁前缀和相关的延迟损失。

对没有锁前缀的变量的访问是不同步的，也不需要同步，因为我们处理的是当前执行的处理
器所特有的每CPU数据。只有当前的处理器可以访问该变量，因此系统中的其他处理器不存在
并发性问题。

请注意，远程处理器对每CPU区域的访问是特殊情况，可能会影响通过 ``this_cpu_*`` 的本
地RMW操作的性能和正确性（远程写操作）。

this_cpu操作的主要用途是优化计数器操作。

定义了以下具有隐含抢占保护的this_cpu()操作。可以使用这些操作而不用担心抢占和中断::

	this_cpu_read(pcp)
	this_cpu_write(pcp, val)
	this_cpu_add(pcp, val)
	this_cpu_and(pcp, val)
	this_cpu_or(pcp, val)
	this_cpu_add_return(pcp, val)
	this_cpu_xchg(pcp, nval)
	this_cpu_cmpxchg(pcp, oval, nval)
	this_cpu_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2)
	this_cpu_sub(pcp, val)
	this_cpu_inc(pcp)
	this_cpu_dec(pcp)
	this_cpu_sub_return(pcp, val)
	this_cpu_inc_return(pcp)
	this_cpu_dec_return(pcp)

this_cpu操作的内部工作
----------------------

在x86上，fs:或gs:段寄存器包含每CPU区域的基址。这样就可以简单地使用段覆盖，将每CPU
相对地址重定位到处理器适当的每CPU区域。所以对每CPU基址的重定位是通过段寄存器前缀
在指令中编码完成的。

例如::

	DEFINE_PER_CPU(int, x);
	int z;

	z = this_cpu_read(x);

产生的单指令为::

	mov ax, gs:[x]

而不是像每CPU操作那样，先是一系列的地址计算，然后从该地址获取。在this_cpu_ops之前，
这样的序列还需要先禁用/启用抢占功能，以防止内核在计算过程中将线程移动到不同的处理
器上。

请思考下面this_cpu操作::

	this_cpu_inc(x)

这将产生如下单指令（无锁前缀！）::

	inc gs:[x]

而不是在没有段寄存器的情况下所需要的以下操作::

	int *y;
	int cpu;

	cpu = get_cpu();
	y = per_cpu_ptr(&x, cpu);
	(*y)++;
	put_cpu();

请注意，这些操作只能用于为特定处理器保留的每CPU数据。如果不在上下文代码中禁用抢占，
``this_cpu_inc()`` 将仅保证每CPU的某一个计数器被正确地递增，但不能保证操作系统不
会在this_cpu指令执行的前后直接移动该进程。一般来说，这意味着每个处理器的单个计数
器的值是没有意义的。所有每CPU计数器的总和才是唯一有意义的值。

每CPU变量的使用是出于性能的考虑。如果多个处理器同时处理相同的代码路径，可以避免缓
存行跳转。每个处理器都有自己的每CPU变量，因此不会发生并发缓存行更新。为这种优化必
须付出的代价是，当需要计数器的值时要将每CPU计数器相加。

特殊的操作
----------

::

	y = this_cpu_ptr(&x)

使用每CPU变量的偏移量(&x!)，并返回属于当前执行处理器的每CPU变量的地址。
``this_cpu_ptr`` 避免了通用 ``get_cpu``/``put_cpu`` 序列所需的多个步骤。没有可用
的处理器编号。相反，本地每CPU区域的偏移量只是简单地添加到每CPU偏移量上。

请注意，这个操作通常是在抢占被禁用后再在代码段中使用。然后该指针用来访问临界区中
的本地每CPU数据。当重新启用抢占时，此指针通常不再有用，因为它可能不再指向当前处理
器的每CPU数据。

每CPU变量和偏移量
-----------------

每CPU变量相对于每CPU区域的起始点是有偏移的。它们没有地址，尽管代码里看起来像有一
样。不能直接对偏移量解引用，必须用处理器每CPU区域基指针加上偏移量，以构成有效地址。

因此，在每CPU操作的上下文之外使用x或&x是无效的，这种行为通常会被当作一个空指针的
解引用来处理。

::

	DEFINE_PER_CPU(int, x);

在每CPU操作的上下文中，上面表达式说明x是一个每CPU变量。大多数this_cpu操作都需要一
个cpu变量。

::

	int __percpu *p = &x;

&x和p是每CPU变量的偏移量。 ``this_cpu_ptr()`` 使用每CPU变量的偏移量，这让它看起来
有点奇怪。

每CPU结构体字段的操作
---------------------

假设我们有一个每CPU结构::

	struct s {
		int n,m;
	};

	DEFINE_PER_CPU(struct s, p);

这些字段的操作非常简单::

	this_cpu_inc(p.m)

	z = this_cpu_cmpxchg(p.m, 0, 1);

如果我们有一个相对于结构体s的偏移量::

	struct s __percpu *ps = &p;

	this_cpu_dec(ps->m);

	z = this_cpu_inc_return(ps->n);

如果我们后面不使用 ``this_cpu ops`` 来操作字段，则指针的计算可能需要使用
``this_cpu_ptr()``::

	struct s *pp;

	pp = this_cpu_ptr(&p);

	pp->m--;

	z = pp->n++;

this_cpu ops的变体
------------------

this_cpu的操作是中断安全的。一些架构不支持这些每CPU的本地操作。在这种情况下，该操
作必须被禁用中断的代码所取代，然后做那些保证是原子的操作，再重新启用中断。当然这
样做是很昂贵的。如果有其他原因导致调度器不能改变我们正在执行的处理器，那么就没有
理由禁用中断了。为此，我们提供了以下__this_cpu操作。

这些操作不能保证并发中断或抢占。如果在中断上下文中不使用每CPU变量并且调度程序无法
抢占，那么它们是安全的。如果在操作进行时仍有中断发生，并且中断也修改了变量，则无
法保证RMW操作是安全的::

	__this_cpu_read(pcp)
	__this_cpu_write(pcp, val)
	__this_cpu_add(pcp, val)
	__this_cpu_and(pcp, val)
	__this_cpu_or(pcp, val)
	__this_cpu_add_return(pcp, val)
	__this_cpu_xchg(pcp, nval)
	__this_cpu_cmpxchg(pcp, oval, nval)
	__this_cpu_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2)
	__this_cpu_sub(pcp, val)
	__this_cpu_inc(pcp)
	__this_cpu_dec(pcp)
	__this_cpu_sub_return(pcp, val)
	__this_cpu_inc_return(pcp)
	__this_cpu_dec_return(pcp)

将增加x，并且不会回退到在无法通过地址重定位和同一指令中的读取-修改-写入操作实现原
子性的平台上禁用中断的代码。

&this_cpu_ptr(pp)->n 对比 this_cpu_ptr(&pp->n)
----------------------------------------------

第一个操作使用偏移量并形成一个地址，然后再加上n字段的偏移量。这可能会导致编译器产
生两条加法指令。

第二个操作先加上两个偏移量，然后进行重定位。恕我直言，第二种形式看起来更干净，而
且更容易与 ``()`` 结合。第二种形式也与 ``this_cpu_read()`` 和大家的使用方式一致。

远程访问每CPU数据
-----------------

每CPU数据结构被设计为由一个CPU独占使用。如果您按预期使用变量，则 ``this_cpu_ops()``
保证是 ``原子的`` ，因为没有其他CPU可以访问这些数据结构。

在某些特殊情况下，您可能需要远程访问每CPU数据结构。通常情况下，进行远程读访问是安
全的，这经常是为了统计计数器值。远程写访问可能会出现问题，因为this_cpu操作没有锁
语义。远程写可能会干扰this_cpu RMW操作。

除非绝对必要，否则强烈建议不要对每CPU数据结构进行远程写访问。请考虑使用IPI来唤醒
远程CPU，并对其每CPU区域进行更新。

要远程访问每CPU数据结构，通常使用 ``per_cpu_ptr()`` 函数::

	DEFINE_PER_CPU(struct data, datap);

	struct data *p = per_cpu_ptr(&datap, cpu);

这清楚地表明，我们正准备远程访问每CPU区域。

您还可以执行以下操作以将datap偏移量转换为地址::

	struct data *p = this_cpu_ptr(&datap);

但是，将通过this_cpu_ptr计算的指针传递给其他cpu是不寻常的，应该避免。

远程访问通常只用于读取另一个cpu的每CPU数据状态。由于this_cpu操作宽松的同步要求，
写访问可能会导致奇特的问题。

下面的情况说明了写入操作的一些问题，由于两个每CPU变量共享一个缓存行，但宽松的同步
仅应用于更新缓存行的一个进程。

考虑以下示例::

	struct test {
		atomic_t a;
		int b;
	};

	DEFINE_PER_CPU(struct test, onecacheline);

如果一个处理器远程更新字段 ``a`` ，而本地处理器将使用this_cpu ops来更新字段 ``b`` ，
会发生什么情况，这一点值得注意。应避免在同一缓存行内同时访问数据。此外，可能还需
要进行代价高昂的同步。在这种情况下，通常建议使用IPI，而不是远程写入另一个处理器的
每CPU区域。

即使在远程写很少的情况下，请记住远程写将从最有可能访问它的处理器中逐出缓存行。如
果处理器唤醒时发现每CPU区域缺少本地缓存行，其性能和唤醒时间将受到影响。


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

:Original: Documentation/core-api/unaligned-memory-access.rst

:翻译:

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

:校译:

 时奎亮 <alexs@kernel.org>

.. _cn_core-api_unaligned-memory-access:

==============
非对齐内存访问
==============

:作者: Daniel Drake <dsd@gentoo.org>,
:作者: Johannes Berg <johannes@sipsolutions.net>

:感谢他们的帮助: Alan Cox, Avuton Olrich, Heikki Orsila, Jan Engelhardt,
  Kyle McMartin, Kyle Moffett, Randy Dunlap, Robert Hancock, Uli Kunitz,
  Vadim Lobanov

Linux运行在各种各样的架构上，这些架构在内存访问方面有不同的表现。本文介绍了一些
关于不对齐访问的细节，为什么你需要编写不引起不对齐访问的代码，以及如何编写这样的
代码

非对齐访问的定义
================

当你试图从一个不被N偶数整除的地址（即addr % N != 0）开始读取N字节的数据时，就
会发生无对齐内存访问。例如，从地址0x10004读取4个字节的数据是可以的，但从地址
0x10005读取4个字节的数据将是一个不对齐的内存访问。

上述内容可能看起来有点模糊，因为内存访问可以以不同的方式发生。这里的背景是在机器
码层面上：某些指令在内存中读取或写入一些字节（例如x86汇编中的movb、movw、movl）。
正如将变得清晰的那样，相对容易发现那些将编译为多字节内存访问指令的C语句，即在处理
u16、u32和u64等类型时。

自然对齐
========

上面提到的规则构成了我们所说的自然对齐。当访问N个字节的内存时，基础内存地址必须被
N平均分割，即addr % N == 0。

在编写代码时，假设目标架构有自然对齐的要求。

在现实中，只有少数架构在所有大小的内存访问上都要求自然对齐。然而，我们必须考虑所
有支持的架构；编写满足自然对齐要求的代码是实现完全可移植性的最简单方法。

为什么非对齐访问时坏事
======================

执行非对齐内存访问的效果因架构不同而不同。在这里写一整篇关于这些差异的文档是很容
易的；下面是对常见情况的总结:

 - 一些架构能够透明地执行非对齐内存访问，但通常会有很大的性能代价。
 - 当不对齐的访问发生时，一些架构会引发处理器异常。异常处理程序能够纠正不对齐的
   访问，但要付出很大的性能代价。
 - 一些架构在发生不对齐访问时，会引发处理器异常，但异常中并没有包含足够的信息来
   纠正不对齐访问。
 - 有些架构不能进行无对齐内存访问，但会默默地执行与请求不同的内存访问，从而导致
   难以发现的微妙的代码错误!

从上文可以看出，如果你的代码导致不对齐的内存访问发生，那么你的代码在某些平台上将无
法正常工作，在其他平台上将导致性能问题。

不会导致非对齐访问的代码
========================

起初，上面的概念似乎有点难以与实际编码实践联系起来。毕竟，你对某些变量的内存地址没
有很大的控制权，等等。

幸运的是事情并不复杂，因为在大多数情况下，编译器会确保代码工作正常。例如，以下面的
结构体为例::

	struct foo {
		u16 field1;
		u32 field2;
		u8 field3;
	};

让我们假设上述结构体的一个实例驻留在从地址0x10000开始的内存中。根据基本的理解，访问
field2会导致非对齐访问，这并不是不合理的。你会期望field2位于该结构体的2个字节的偏移
量，即地址0x10002，但该地址不能被4平均整除（注意，我们在这里读一个4字节的值）。

幸运的是，编译器理解对齐约束，所以在上述情况下，它会在field1和field2之间插入2个字节
的填充。因此，对于标准的结构体类型，你总是可以依靠编译器来填充结构体，以便对字段的访
问可以适当地对齐（假设你没有将字段定义不同长度的类型）。

同样，你也可以依靠编译器根据变量类型的大小，将变量和函数参数对齐到一个自然对齐的方案。

在这一点上，应该很清楚，访问单个字节（u8或char）永远不会导致无对齐访问，因为所有的内
存地址都可以被1均匀地整除。

在一个相关的话题上，考虑到上述因素，你可以观察到，你可以对结构体中的字段进行重新排序，
以便将字段放在不重排就会插入填充物的地方，从而减少结构体实例的整体常驻内存大小。上述
例子的最佳布局是::

	struct foo {
		u32 field2;
		u16 field1;
		u8 field3;
	};

对于一个自然对齐方案，编译器只需要在结构的末尾添加一个字节的填充。添加这种填充是为了满
足这些结构的数组的对齐约束。

另一点值得一提的是在结构体类型上使用__attribute__((packed))。这个GCC特有的属性告诉编
译器永远不要在结构体中插入任何填充，当你想用C结构体来表示一些“off the wire”的固定排列
的数据时，这个属性很有用。

你可能会倾向于认为，在访问不满足架构对齐要求的字段时，使用这个属性很容易导致不对齐的访
问。然而，编译器也意识到了对齐的限制，并且会产生额外的指令来执行内存访问，以避免造成不
对齐的访问。当然，与non-packed的情况相比，额外的指令显然会造成性能上的损失，所以packed
属性应该只在避免结构填充很重要的时候使用。

导致非对齐访问的代码
====================

考虑到上述情况，让我们来看看一个现实生活中可能导致非对齐内存访问的函数的例子。下面这个
函数取自include/linux/etherdevice.h，是一个优化的例程，用于比较两个以太网MAC地址是否
相等::

  bool ether_addr_equal(const u8 *addr1, const u8 *addr2)
  {
  #ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
	u32 fold = ((*(const u32 *)addr1) ^ (*(const u32 *)addr2)) |
		   ((*(const u16 *)(addr1 + 4)) ^ (*(const u16 *)(addr2 + 4)));

	return fold == 0;
  #else
	const u16 *a = (const u16 *)addr1;
	const u16 *b = (const u16 *)addr2;
	return ((a[0] ^ b[0]) | (a[1] ^ b[1]) | (a[2] ^ b[2])) == 0;
  #endif
  }

在上述函数中，当硬件具有高效的非对齐访问能力时，这段代码没有问题。但是当硬件不能在任意
边界上访问内存时，对a[0]的引用导致从地址addr1开始的2个字节（16位）被读取。

想一想，如果addr1是一个奇怪的地址，如0x10003，会发生什么？(提示：这将是一个非对齐访
问。)

尽管上述函数存在潜在的非对齐访问问题，但它还是被包含在内核中，但被理解为只在16位对齐
的地址上正常工作。调用者应该确保这种对齐方式或者根本不使用这个函数。这个不对齐的函数
仍然是有用的，因为它是在你能确保对齐的情况下的一个很好的优化，这在以太网网络环境中几
乎是一直如此。

下面是另一个可能导致非对齐访问的代码的例子::

	void myfunc(u8 *data, u32 value)
	{
		[...]
		*((u32 *) data) = cpu_to_le32(value);
		[...]
	}

每当数据参数指向的地址不被4均匀整除时，这段代码就会导致非对齐访问。

综上所述，你可能遇到非对齐访问问题的两种主要情况包括:

 1. 将变量定义不同长度的类型
 2. 指针运算后访问至少2个字节的数据

避免非对齐访问
==============

避免非对齐访问的最简单方法是使用<asm/unaligned.h>头文件提供的get_unaligned()和
put_unaligned()宏。

回到前面的一个可能导致非对齐访问的代码例子::

	void myfunc(u8 *data, u32 value)
	{
		[...]
		*((u32 *) data) = cpu_to_le32(value);
		[...]
	}

为了避免非对齐的内存访问，你可以将其改写如下::

	void myfunc(u8 *data, u32 value)
	{
		[...]
		value = cpu_to_le32(value);
		put_unaligned(value, (u32 *) data);
		[...]
	}

get_unaligned()宏的工作原理与此类似。假设'data'是一个指向内存的指针，并且你希望避免
非对齐访问，其用法如下::

	u32 value = get_unaligned((u32 *) data);

这些宏适用于任何长度的内存访问（不仅仅是上面例子中的32位）。请注意，与标准的对齐内存
访问相比，使用这些宏来访问非对齐内存可能会在性能上付出代价。

如果使用这些宏不方便，另一个选择是使用memcpy()，其中源或目标（或两者）的类型为u8*或
非对齐char*。由于这种操作的字节性质，避免了非对齐访问。

对齐 vs. 网络
=============

在需要对齐负载的架构上，网络要求IP头在四字节边界上对齐，以优化IP栈。对于普通的以太网
硬件，常数NET_IP_ALIGN被使用。在大多数架构上，这个常数的值是2，因为正常的以太网头是
14个字节，所以为了获得适当的对齐，需要DMA到一个可以表示为4*n+2的地址。一个值得注意的
例外是powerpc，它将NET_IP_ALIGN定义为0，因为DMA到未对齐的地址可能非常昂贵，与未对齐
的负载的成本相比相形见绌。

对于一些不能DMA到未对齐地址的以太网硬件，如4*n+2或非以太网硬件，这可能是一个问题，这
时需要将传入的帧复制到一个对齐的缓冲区。因为这在可以进行非对齐访问的架构上是不必要的，
所以可以使代码依赖于CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS，像这样::

	#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
		skb = original skb
	#else
		skb = copy skb
	#endif


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

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

:Original: Documentation/core-api/watch_queue.rst

:翻译:

周彬彬 Binbin Zhou <zhoubinbin@loongson.cn>

:校译:

司延腾 Yanteng Si <siyanteng@loongson.cn>
吴想成 Wu Xiangcheng <bobwxc@email.cn>

============
通用通知机制
============

通用通知机制是建立在标准管道驱动之上的，它可以有效地将来自内核的通知消息拼接到用
户空间打开的管道中。这可以与以下方面结合使用::

  * Key/keyring 通知

通知缓冲区可以通过以下方式启用：

	“General setup”/“General notification queue”
	(CONFIG_WATCH_QUEUE)

文档包含以下章节：

.. contents:: :local:

概述
====

该设施以一种特殊模式打开的管道形式出现，管道的内部环形缓冲区用于保存内核生成的消
息。然后通过read()读出这些消息。在此类管道上禁用拼接以及类似的操作，因为它们希望
在某些情况下将其添加的内容还原到环中-这可能最终会与通知消息重叠。

管道的所有者必须告诉内核它想通过该管道观察哪些源。只有连接到该管道上的源才会将消
息插入其中。请注意，一个源可能绑定到多个管道，并同时将消息插入到所有管道中。

还可以将过滤器放置在管道上，以便在不感兴趣时可以忽略某些源类型和子事件。

如果环中没有可用的插槽，或者没有预分配的消息缓冲区可用，则将丢弃消息。在这两种情
况下，read()都会在读取缓冲区中当前的最后一条消息后，将WATCH_META_LOSS_NOTIFICATION
插入到输出缓冲区中。

请注意，当生成一个通知时，内核不会等待消费者收集它，而是继续执行。这意味着可以在
持有自旋锁的同时生成通知，并且还可以保护内核不被用户空间故障无限期地阻碍。

消息结构
========

通知消息由一个简短的头部开始::

	struct watch_notification {
		__u32	type:24;
		__u32	subtype:8;
		__u32	info;
	};

“type”表示通知记录的来源，“subtype”表示该来源的记录类型（见下文观测源章节）。该类
型也可以是“WATCH_TYPE_META”。这是一个由观测队列本身在内部生成的特殊记录类型。有两
个子类型：

  * WATCH_META_REMOVAL_NOTIFICATION
  * WATCH_META_LOSS_NOTIFICATION

第一个表示安装了观察的对象已被删除或销毁，第二个表示某些消息已丢失。

“info”表示一系列东西，包括：

  * 消息的长度，以字节为单位，包括头（带有WATCH_INFO_LENGTH的掩码，并按
    WATCH_INFO_LENGTH__SHIFT移位）。这表示记录的大小，可能在8到127字节之间。

  * 观测ID（带有WATCH_INFO_ID掩码，并按WATCH_INFO_ID__SHIFT移位）。这表示观测的主
    叫ID，可能在0到255之间。多个观测组可以共享一个队列，这提供了一种区分它们的方法。

  * 特定类型的字段（WATCH_INFO_TYPE_INFO）。这是由通知生产者设置的，以指示类型和
    子类型的某些特定含义。

除长度外，信息中的所有内容都可以用于过滤。

头部后面可以有补充信息。此格式是由类型和子类型决定的。

观测列表（通知源）API
=====================

“观测列表“是订阅通知源的观测者的列表。列表可以附加到对象（比如键或超级块），也可
以是全局的（比如对于设备事件）。从用户空间的角度来看，一个非全局的观测列表通常是
通过引用它所属的对象来引用的（比如使用KEYCTL_NOTIFY并给它一个密钥序列号来观测特定
的密钥）。

为了管理观测列表，提供了以下函数：

  * ::

	void init_watch_list(struct watch_list *wlist,
			     void (*release_watch)(struct watch *wlist));

    初始化一个观测列表。 如果 ``release_watch`` 不是NULL，那么这表示当watch_list对
    象被销毁时，应该调用函数来丢弃观测列表对被观测对象的任何引用。

  * ``void remove_watch_list(struct watch_list *wlist);``

    这将删除订阅watch_list的所有观测，并释放它们，然后销毁watch_list对象本身。

观测队列（通知输出）API
=======================

“观测队列”是由应用程序分配的用以记录通知的缓冲区，其工作原理完全隐藏在管道设备驱
动中，但必须获得对它的引用才能设置观测。可以通过以下方式进行管理：

  * ``struct watch_queue *get_watch_queue(int fd);``

    由于观测队列在内核中通过实现缓冲区的管道的文件描述符表示，用户空间必须通过系
    统调用传递该文件描述符，这可以用于从系统调用中查找指向观测队列的不透明指针。

  * ``void put_watch_queue(struct watch_queue *wqueue);``

    该函数用以丢弃从 ``get_watch_queue()`` 获得的引用。

观测订阅API
===========

“观测”是观测列表上的订阅，表示观测队列，从而表示应写入通知记录的缓冲区。观测队列
对象还可以携带该对象的过滤规则，由用户空间设置。watch结构体的某些部分可以由驱动程
序设置::

	struct watch {
		union {
			u32		info_id;	/* 在info字段中进行OR运算的ID */
			...
		};
		void			*private;	/* 被观测对象的私有数据 */
		u64			id;		/* 内部标识符 */
		...
	};

``info_id`` 值是从用户空间获得并按WATCH_INFO_ID__SHIFT移位的8位数字。当通知写入关
联的观测队列缓冲区时，这将与struct watch_notification::info的WATCH_INFO_ID字段进
行或运算。

``private`` 字段是与watch_list相关联的驱动程序数据，并由 ``watch_list::release_watch()``
函数清除。

``id`` 字段是源的ID。使用不同ID发布的通知将被忽略。

提供以下函数来管理观测:

  * ``void init_watch(struct watch *watch, struct watch_queue *wqueue);``

    初始化一个观测对象，把它的指针设置到观察队列中，使用适当的限制来避免死锁。

  * ``int add_watch_to_object(struct watch *watch, struct watch_list *wlist);``

    将观测订阅到观测列表（通知源）。watch结构体中的driver-settable字段必须在调用
    它之前设置。

  * ::

	int remove_watch_from_object(struct watch_list *wlist,
				     struct watch_queue *wqueue,
				     u64 id, false);

    从观测列表中删除一个观测，该观测必须与指定的观测队列（``wqueue``）和对象标识
    符（``id``）匹配。通知（``WATCH_META_REMOVAL_NOTIFICATION``）被发送到观测队列
    表示该观测已被删除。

  * ``int remove_watch_from_object(struct watch_list *wlist, NULL, 0, true);``

    从观测列表中删除所有观测。预计这将被称为销毁前的准备工作，届时新的观测将无法
    访问观测列表。通知（``WATCH_META_REMOVAL_NOTIFICATION``）被发送到每个订阅观测
    的观测队列，以表明该观测已被删除。

通知发布API
===========

要将通知发布到观测列表以便订阅的观测可以看到，应使用以下函数::

	void post_watch_notification(struct watch_list *wlist,
				     struct watch_notification *n,
				     const struct cred *cred,
				     u64 id);

应预先设置通知格式，并应传入一个指向头部（``n``）的指针。通知可能大于此值，并且缓
冲槽为单位的大小在 ``n->info & WATCH_INFO_LENGTH`` 中注明。

``cred`` 结构体表示源（对象）的证书，并传递给LSM，例如SELinux，以允许或禁止根据该队
列（对象）的证书在每个单独队列中记录注释。

``id`` 是源对象ID（如密钥上的序列号）。只有设置相同ID的观测才能看到这个通知。

观测源
======

任何特定的缓冲区都可以从多个源获取信息。 这些源包括:

  * WATCH_TYPE_KEY_NOTIFY

    这种类型的通知表示密钥和密钥环的变化，包括密钥环内容或密钥属性的变化。

    更多信息请参见Documentation/security/keys/core.rst。

事件过滤
========

当创建观测队列后，我们可以应用一组过滤器以限制接收的事件::

	struct watch_notification_filter filter = {
		...
	};
	ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter)

过滤器的描述的类型变量是::

	struct watch_notification_filter {
		__u32	nr_filters;
		__u32	__reserved;
		struct watch_notification_type_filter filters[];
	};

其中“nr_filters”表示filters[]数组中过滤器的数量，而“__reserved”应为0。
“filter”数组有以下类型的元素::

	struct watch_notification_type_filter {
		__u32	type;
		__u32	info_filter;
		__u32	info_mask;
		__u32	subtype_filter[8];
	};

其中：

  * ``type`` 是过滤的事件类型，应类似于“WATCH_TYPE_KEY_NOTIFY”。

  * ``info_filter`` 与 ``info_mask`` 充当通知记录的信息字段的过滤器，只有在以下情
    况，通知才会写入缓冲区::

	(watch.info & info_mask) == info_filter

    例如，这可以用于忽略不在一个挂载树上的观测点的事件。

  * ``subtype_filter`` 是一个位掩码，表示感兴趣的子类型。subtype_filter[0]的
    bit[0]对应子类型0，bit[1]对应子类型1，以此类推。

若ioctl()的参数为NULL，则过滤器将被移除，并且来自观测源的所有事件都将通过。

用户空间代码示例
================

缓冲区的创建如下所示::

	pipe2(fds, O_TMPFILE);
	ioctl(fds[1], IOC_WATCH_QUEUE_SET_SIZE, 256);

它可以被设置成接收密钥环变化的通知::

	keyctl(KEYCTL_WATCH_KEY, KEY_SPEC_SESSION_KEYRING, fds[1], 0x01);

然后，这些通知可以被如下方式所使用::

	static void consumer(int rfd, struct watch_queue_buffer *buf)
	{
		unsigned char buffer[128];
		ssize_t buf_len;

		while (buf_len = read(rfd, buffer, sizeof(buffer)),
		       buf_len > 0
		       ) {
			void *p = buffer;
			void *end = buffer + buf_len;
			while (p < end) {
				union {
					struct watch_notification n;
					unsigned char buf1[128];
				} n;
				size_t largest, len;

				largest = end - p;
				if (largest > 128)
					largest = 128;
				memcpy(&n, p, largest);

				len = (n->info & WATCH_INFO_LENGTH) >>
					WATCH_INFO_LENGTH__SHIFT;
				if (len == 0 || len > largest)
					return;

				switch (n.n.type) {
				case WATCH_TYPE_META:
					got_meta(&n.n);
				case WATCH_TYPE_KEY_NOTIFY:
					saw_key_change(&n.n);
					break;
				}

				p += len;
			}
		}
	}


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

:Original: Documentation/core-api/workqueue.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>
 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn>

.. _cn_workqueue.rst:

=========================
并发管理的工作队列 (cmwq)
=========================

:日期: September, 2010
:作者: Tejun Heo <tj@kernel.org>
:作者: Florian Mickler <florian@mickler.org>

简介
====

在很多情况下，需要一个异步进程的执行环境，工作队列（wq）API是这种情况下
最常用的机制。

当需要这样一个异步执行上下文时，一个描述将要执行的函数的工作项（work，
即一个待执行的任务）被放在队列中。一个独立的线程作为异步执行环境。该队
列被称为workqueue，线程被称为工作者（worker，即执行这一队列的线程）。

当工作队列上有工作项时，工作者会一个接一个地执行与工作项相关的函数。当
工作队列中没有任何工作项时，工作者就会变得空闲。当一个新的工作项被排入
队列时，工作者又开始执行。

为什么要cmwq?
=============

在最初的wq实现中，多线程（MT）wq在每个CPU上有一个工作者线程，而单线程
（ST）wq在全系统有一个工作者线程。一个MT wq需要保持与CPU数量相同的工
作者数量。这些年来，内核增加了很多MT wq的用户，随着CPU核心数量的不断
增加，一些系统刚启动就达到了默认的32k PID的饱和空间。

尽管MT wq浪费了大量的资源，但所提供的并发性水平却不能令人满意。这个限
制在ST和MT wq中都有，只是在MT中没有那么严重。每个wq都保持着自己独立的
工作者池。一个MT wq只能为每个CPU提供一个执行环境，而一个ST wq则为整个
系统提供一个。工作项必须竞争这些非常有限的执行上下文，从而导致各种问题，
包括在单一执行上下文周围容易发生死锁。

(MT wq)所提供的并发性水平和资源使用之间的矛盾也迫使其用户做出不必要的权衡，比
如libata选择使用ST wq来轮询PIO，并接受一个不必要的限制，即没有两个轮
询PIO可以同时进行。由于MT wq并没有提供更好的并发性，需要更高层次的并
发性的用户，如async或fscache，不得不实现他们自己的线程池。

并发管理工作队列（cmwq）是对wq的重新实现，重点是以下目标。

* 保持与原始工作队列API的兼容性。

* 使用由所有wq共享的每CPU统一的工作者池，在不浪费大量资源的情况下按
* 需提供灵活的并发水平。

* 自动调节工作者池和并发水平，使API用户不需要担心这些细节。

设计
====

为了简化函数的异步执行，引入了一个新的抽象概念，即工作项。

一个工作项是一个简单的结构，它持有一个指向将被异步执行的函数的指针。
每当一个驱动程序或子系统希望一个函数被异步执行时，它必须建立一个指
向该函数的工作项，并在工作队列中排队等待该工作项。（就是挂到workqueue
队列里面去）

特定目的线程，称为工作线程（工作者），一个接一个地执行队列中的功能。
如果没有工作项排队，工作者线程就会闲置。这些工作者线程被管理在所谓
的工作者池中。

cmwq设计区分了面向用户的工作队列，子系统和驱动程序在上面排队工作，
以及管理工作者池和处理排队工作项的后端机制。

每个可能的CPU都有两个工作者池，一个用于正常的工作项，另一个用于高
优先级的工作项，还有一些额外的工作者池，用于服务未绑定工作队列的工
作项目——这些后备池的数量是动态的。

当他们认为合适的时候，子系统和驱动程序可以通过特殊的
``workqueue API`` 函数创建和排队工作项。他们可以通过在工作队列上
设置标志来影响工作项执行方式的某些方面，他们把工作项放在那里。这些
标志包括诸如CPU定位、并发限制、优先级等等。要获得详细的概述，请参
考下面的 ``alloc_workqueue()`` 的 API 描述。

当一个工作项被排入一个工作队列时，目标工作池将根据队列参数和工作队
列属性确定，并被附加到工作池的共享工作列表上。例如，除非特别重写，
否则一个绑定的工作队列的工作项将被排在与发起线程运行的CPU相关的普
通或高级工作工作者池的工作项列表中。

对于任何工作者池的实施，管理并发水平（有多少执行上下文处于活动状
态）是一个重要问题。最低水平是为了节省资源，而饱和水平是指系统被
充分使用。

每个与实际CPU绑定的worker-pool通过钩住调度器来实现并发管理。每当
一个活动的工作者被唤醒或睡眠时，工作者池就会得到通知，并跟踪当前可
运行的工作者的数量。一般来说，工作项不会占用CPU并消耗很多周期。这
意味着保持足够的并发性以防止工作处理停滞应该是最优的。只要CPU上有
一个或多个可运行的工作者，工作者池就不会开始执行新的工作，但是，当
最后一个运行的工作者进入睡眠状态时，它会立即安排一个新的工作者，这
样CPU就不会在有待处理的工作项目时闲置。这允许在不损失执行带宽的情
况下使用最少的工作者。

除了kthreads的内存空间外，保留空闲的工作者并没有其他成本，所以cmwq
在杀死它们之前会保留一段时间的空闲。

对于非绑定的工作队列，后备池的数量是动态的。可以使用
``apply_workqueue_attrs()`` 为非绑定工作队列分配自定义属性，
workqueue将自动创建与属性相匹配的后备工作者池。调节并发水平的责任在
用户身上。也有一个标志可以将绑定的wq标记为忽略并发管理。
详情请参考API部分。

前进进度的保证依赖于当需要更多的执行上下文时可以创建工作者，这也是
通过使用救援工作者来保证的。所有可能在处理内存回收的代码路径上使用
的工作项都需要在wq上排队，wq上保留了一个救援工作者，以便在内存有压
力的情况下下执行。否则，工作者池就有可能出现死锁，等待执行上下文释
放出来。

应用程序编程接口 (API)
======================

``alloc_workqueue()`` 分配了一个wq。原来的 ``create_*workqueue()``
函数已被废弃，并计划删除。 ``alloc_workqueue()`` 需要三个
参数 - ``@name`` , ``@flags`` 和 ``@max_active`` 。
``@name`` 是wq的名称，如果有的话，也用作救援线程的名称。

一个wq不再管理执行资源，而是作为前进进度保证、刷新(flush)和
工作项属性的域。 ``@flags`` 和 ``@max_active`` 控制着工作
项如何被分配执行资源、安排和执行。

``flags``
---------

``WQ_UNBOUND``
  排队到非绑定wq的工作项由特殊的工作者池提供服务，这些工作者不
  绑定在任何特定的CPU上。这使得wq表现得像一个简单的执行环境提
  供者，没有并发管理。非绑定工作者池试图尽快开始执行工作项。非
  绑定的wq牺牲了局部性，但在以下情况下是有用的。

  * 预计并发水平要求会有很大的波动，使用绑定的wq最终可能会在不
    同的CPU上产生大量大部分未使用的工作者，因为发起线程在不同
    的CPU上跳转。

  * 长期运行的CPU密集型工作负载，可以由系统调度器更好地管理。

``WQ_FREEZABLE``
  一个可冻结的wq参与了系统暂停操作的冻结阶段。wq上的工作项被
  排空，在解冻之前没有新的工作项开始执行。

``WQ_MEM_RECLAIM``
  所有可能在内存回收路径中使用的wq都必须设置这个标志。无论内
  存压力如何，wq都能保证至少有一个执行上下文。

``WQ_HIGHPRI``
  高优先级wq的工作项目被排到目标cpu的高优先级工作者池中。高
  优先级的工作者池由具有较高级别的工作者线程提供服务。

  请注意，普通工作者池和高优先级工作者池之间并不相互影响。他
  们各自维护其独立的工作者池，并在其工作者之间实现并发管理。

``WQ_CPU_INTENSIVE``
  CPU密集型wq的工作项对并发水平没有贡献。换句话说，可运行的
  CPU密集型工作项不会阻止同一工作者池中的其他工作项开始执行。
  这对于那些预计会占用CPU周期的绑定工作项很有用，这样它们的
  执行就会受到系统调度器的监管。

  尽管CPU密集型工作项不会对并发水平做出贡献，但它们的执行开
  始仍然受到并发管理的管制，可运行的非CPU密集型工作项会延迟
  CPU密集型工作项的执行。

  这个标志对于未绑定的wq来说是没有意义的。

``max_active``
--------------

``@max_active`` 决定了每个CPU可以分配给wq的工作项的最大执行上
下文数量。例如，如果 ``@max_active为16`` ，每个CPU最多可以同
时执行16个wq的工作项。

目前，对于一个绑定的wq， ``@max_active`` 的最大限制是512，当指
定为0时使用的默认值是256。对于非绑定的wq，其限制是512和
4 * ``num_possible_cpus()`` 中的较高值。这些值被选得足够高，所
以它们不是限制性因素，同时会在失控情况下提供保护。

一个wq的活动工作项的数量通常由wq的用户来调节，更具体地说，是由用
户在同一时间可以排列多少个工作项来调节。除非有特定的需求来控制活动
工作项的数量，否则建议指定 为"0"。

一些用户依赖于ST wq的严格执行顺序。 ``@max_active`` 为1和 ``WQ_UNBOUND``
的组合用来实现这种行为。这种wq上的工作项目总是被排到未绑定的工作池
中，并且在任何时候都只有一个工作项目处于活动状态，从而实现与ST wq相
同的排序属性。

在目前的实现中，上述配置只保证了特定NUMA节点内的ST行为。相反，
``alloc_ordered_queue()`` 应该被用来实现全系统的ST行为。

执行场景示例
============

下面的示例执行场景试图说明cmwq在不同配置下的行为。

 工作项w0、w1、w2被排到同一个CPU上的一个绑定的wq q0上。w0
 消耗CPU 5ms，然后睡眠10ms，然后在完成之前再次消耗CPU 5ms。

忽略所有其他的任务、工作和处理开销，并假设简单的FIFO调度，
下面是一个高度简化的原始wq的可能事件序列的版本。::

 TIME IN MSECS	EVENT
 0		w0 starts and burns CPU
 5		w0 sleeps
 15		w0 wakes up and burns CPU
 20		w0 finishes
 20		w1 starts and burns CPU
 25		w1 sleeps
 35		w1 wakes up and finishes
 35		w2 starts and burns CPU
 40		w2 sleeps
 50		w2 wakes up and finishes

And with cmwq with ``@max_active`` >= 3, ::

 TIME IN MSECS	EVENT
 0		w0 starts and burns CPU
 5		w0 sleeps
 5		w1 starts and burns CPU
 10		w1 sleeps
 10		w2 starts and burns CPU
 15		w2 sleeps
 15		w0 wakes up and burns CPU
 20		w0 finishes
 20		w1 wakes up and finishes
 25		w2 wakes up and finishes

如果 ``@max_active`` == 2, ::

 TIME IN MSECS	EVENT
 0		w0 starts and burns CPU
 5		w0 sleeps
 5		w1 starts and burns CPU
 10		w1 sleeps
 15		w0 wakes up and burns CPU
 20		w0 finishes
 20		w1 wakes up and finishes
 20		w2 starts and burns CPU
 25		w2 sleeps
 35		w2 wakes up and finishes

现在，我们假设w1和w2被排到了不同的wq q1上，这个wq q1
有 ``WQ_CPU_INTENSIVE`` 设置::

 TIME IN MSECS	EVENT
 0		w0 starts and burns CPU
 5		w0 sleeps
 5		w1 and w2 start and burn CPU
 10		w1 sleeps
 15		w2 sleeps
 15		w0 wakes up and burns CPU
 20		w0 finishes
 20		w1 wakes up and finishes
 25		w2 wakes up and finishes

指南
====

* 如果一个wq可能处理在内存回收期间使用的工作项目，请不
  要忘记使用 ``WQ_MEM_RECLAIM`` 。每个设置了
  ``WQ_MEM_RECLAIM`` 的wq都有一个为其保留的执行环境。
  如果在内存回收过程中使用的多个工作项之间存在依赖关系，
  它们应该被排在不同的wq中，每个wq都有 ``WQ_MEM_RECLAIM`` 。

* 除非需要严格排序，否则没有必要使用ST wq。

* 除非有特殊需要，建议使用0作为@max_active。在大多数使用情
  况下，并发水平通常保持在默认限制之下。

* 一个wq作为前进进度保证（WQ_MEM_RECLAIM，冲洗（flush）和工
  作项属性的域。不涉及内存回收的工作项，不需要作为工作项组的一
  部分被刷新，也不需要任何特殊属性，可以使用系统中的一个wq。使
  用专用wq和系统wq在执行特性上没有区别。

* 除非工作项预计会消耗大量的CPU周期，否则使用绑定的wq通常是有
  益的，因为wq操作和工作项执行中的定位水平提高了。

调试
====

因为工作函数是由通用的工作者线程执行的，所以需要一些手段来揭示一些行为不端的工作队列用户。

工作者线程在进程列表中显示为: ::

  root      5671  0.0  0.0      0     0 ?        S    12:07   0:00 [kworker/0:1]
  root      5672  0.0  0.0      0     0 ?        S    12:07   0:00 [kworker/1:2]
  root      5673  0.0  0.0      0     0 ?        S    12:12   0:00 [kworker/0:0]
  root      5674  0.0  0.0      0     0 ?        S    12:13   0:00 [kworker/1:0]

如果kworkers失控了（使用了太多的cpu），有两类可能的问题:

	1. 正在迅速调度的事情
	2. 一个消耗大量cpu周期的工作项。

第一个可以用追踪的方式进行跟踪: ::

	$ echo workqueue:workqueue_queue_work > /sys/kernel/debug/tracing/set_event
	$ cat /sys/kernel/debug/tracing/trace_pipe > out.txt
	(wait a few secs)

如果有什么东西在工作队列上忙着做循环，它就会主导输出，可以用工作项函数确定违规者。

对于第二类问题，应该可以只检查违规工作者线程的堆栈跟踪。 ::

	$ cat /proc/THE_OFFENDING_KWORKER/stack

工作项函数在堆栈追踪中应该是微不足道的。

不可重入条件
============

工作队列保证，如果在工作项排队后满足以下条件，则工作项不能重入：

        1. 工作函数没有被改变。
        2. 没有人将该工作项排到另一个工作队列中。
        3. 该工作项尚未被重新启动。

换言之，如果上述条件成立，则保证在任何给定时间最多由一个系统范围内的工作程序执行
该工作项。

请注意，在self函数中将工作项重新排队（到同一队列）不会破坏这些条件，因此可以安全
地执行此操作。否则在破坏工作函数内部的条件时需要小心。

内核内联文档参考
================

该API在以下内核代码中:

include/linux/workqueue.h

kernel/workqueue.c