diff options
Diffstat (limited to 'Documentation/translations/zh_CN/core-api')
13 files changed, 924 insertions, 22 deletions
diff --git a/Documentation/translations/zh_CN/core-api/circular-buffers.rst b/Documentation/translations/zh_CN/core-api/circular-buffers.rst new file mode 100644 index 000000000000..694ad8e61070 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/circular-buffers.rst @@ -0,0 +1,210 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/circular-buffers.rst + +:翻译: + + 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn> + +:校译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + 吴想成 Wu Xiangcheng <bobwxc@email.cn> + 时奎亮 Alex Shi <alexs@kernel.org> + +========== +环形缓冲区 +========== + +:作者: David Howells <dhowells@redhat.com> +:作者: Paul E. McKenney <paulmck@linux.ibm.com> + + +Linux 提供了许多可用于实现循环缓冲的特性。有两组这样的特性: + + (1) 用于确定2次方大小的缓冲区信息的便利函数。 + + (2) 可以代替缓冲区中对象的生产者和消费者共享锁的内存屏障。 + +如下所述,要使用这些设施,只需要一个生产者和一个消费者。可以通过序列化来处理多个 +生产者,并通过序列化来处理多个消费者。 + +.. Contents: + + (*) 什么是环形缓冲区? + + (*) 测量2次幂缓冲区 + + (*) 内存屏障与环形缓冲区的结合使用 + - 生产者 + - 消费者 + + (*) 延伸阅读 + + + +什么是环形缓冲区? +================== + +首先,什么是环形缓冲区?环形缓冲区是具有固定的有限大小的缓冲区,它有两个索引: + + (1) 'head'索引 - 生产者将元素插入缓冲区的位置。 + + (2) 'tail'索引 - 消费者在缓冲区中找到下一个元素的位置。 + +通常,当tail指针等于head指针时,表明缓冲区是空的;而当head指针比tail指针少一个时, +表明缓冲区是满的。 + +添加元素时,递增head索引;删除元素时,递增tail索引。tail索引不应该跳过head索引, +两个索引在到达缓冲区末端时都应该被赋值为0,从而允许海量的数据流过缓冲区。 + +通常情况下,元素都有相同的单元大小,但这并不是使用以下技术的严格要求。如果要在缓 +冲区中包含多个元素或可变大小的元素,则索引可以增加超过1,前提是两个索引都没有超过 +另一个。然而,实现者必须小心,因为超过一个单位大小的区域可能会覆盖缓冲区的末端并 +且缓冲区会被分成两段。 + +测量2次幂缓冲区 +=============== + +计算任意大小的环形缓冲区的占用或剩余容量通常是一个费时的操作,需要使用模(除法) +指令。但是如果缓冲区的大小为2次幂,则可以使用更快的按位与指令代替。 + +Linux提供了一组用于处理2次幂环形缓冲区的宏。可以通过以下方式使用:: + + #include <linux/circ_buf.h> + +这些宏包括: + + (#) 测量缓冲区的剩余容量:: + + CIRC_SPACE(head_index, tail_index, buffer_size); + + 返回缓冲区[1]中可插入元素的剩余空间大小。 + + + (#) 测量缓冲区中的最大连续立即可用空间:: + + CIRC_SPACE_TO_END(head_index, tail_index, buffer_size); + + 返回缓冲区[1]中剩余的连续空间的大小,元素可以立即插入其中,而不必绕回到缓冲 + 区的开头。 + + + (#) 测量缓冲区的使用数:: + + CIRC_CNT(head_index, tail_index, buffer_size); + + 返回当前占用缓冲区[2]的元素数量。 + + + (#) 测量缓冲区的连续使用数:: + + CIRC_CNT_TO_END(head_index, tail_index, buffer_size); + + 返回可以从缓冲区中提取的连续元素[2]的数量,而不必绕回到缓冲区的开头。 + +这里的每一个宏名义上都会返回一个介于0和buffer_size-1之间的值,但是: + + (1) CIRC_SPACE*()是为了在生产者中使用。对生产者来说,它们将返回一个下限,因为生 + 产者控制着head索引,但消费者可能仍然在另一个CPU上耗尽缓冲区并移动tail索引。 + + 对消费者来说,它将显示一个上限,因为生产者可能正忙于耗尽空间。 + + (2) CIRC_CNT*()是为了在消费者中使用。对消费者来说,它们将返回一个下限,因为消费 + 者控制着tail索引,但生产者可能仍然在另一个CPU上填充缓冲区并移动head索引。 + + 对于生产者,它将显示一个上限,因为消费者可能正忙于清空缓冲区。 + + (3) 对于第三方来说,生产者和消费者对索引的写入顺序是无法保证的,因为它们是独立的, + 而且可能是在不同的CPU上进行的,所以在这种情况下的结果只是一种猜测,甚至可能 + 是错误的。 + +内存屏障与环形缓冲区的结合使用 +============================== + +通过将内存屏障与环形缓冲区结合使用,可以避免以下需求: + + (1) 使用单个锁来控制对缓冲区两端的访问,从而允许同时填充和清空缓冲区;以及 + + (2) 使用原子计数器操作。 + +这有两个方面:填充缓冲区的生产者和清空缓冲区的消费者。在任何时候,只应有一个生产 +者在填充缓冲区,同样的也只应有一个消费者在清空缓冲区,但双方可以同时操作。 + + +生产者 +------ + +生产者看起来像这样:: + + spin_lock(&producer_lock); + + unsigned long head = buffer->head; + /* spin_unlock()和下一个spin_lock()提供必要的排序。 */ + unsigned long tail = READ_ONCE(buffer->tail); + + if (CIRC_SPACE(head, tail, buffer->size) >= 1) { + /* 添加一个元素到缓冲区 */ + struct item *item = buffer[head]; + + produce_item(item); + + smp_store_release(buffer->head, + (head + 1) & (buffer->size - 1)); + + /* wake_up()将确保在唤醒任何人之前提交head */ + wake_up(consumer); + } + + spin_unlock(&producer_lock); + +这将表明CPU必须在head索引使其对消费者可用之前写入新项目的内容,同时CPU必须在唤醒 +消费者之前写入修改后的head索引。 + +请注意,wake_up()并不保证任何形式的屏障,除非确实唤醒了某些东西。因此我们不能依靠 +它来进行排序。但是数组中始终有一个元素留空,因此生产者必须产生两个元素,然后才可 +能破坏消费者当前正在读取的元素。同时,消费者连续调用之间成对的解锁-加锁提供了索引 +读取(指示消费者已清空给定元素)和生产者对该相同元素的写入之间的必要顺序。 + + +消费者 +------ + +消费者看起来像这样:: + + spin_lock(&consumer_lock); + + /* 读取该索引处的内容之前,先读取索引 */ + unsigned long head = smp_load_acquire(buffer->head); + unsigned long tail = buffer->tail; + + if (CIRC_CNT(head, tail, buffer->size) >= 1) { + + /* 从缓冲区中提取一个元素 */ + struct item *item = buffer[tail]; + + consume_item(item); + + /* 在递增tail之前完成对描述符的读取。 */ + smp_store_release(buffer->tail, + (tail + 1) & (buffer->size - 1)); + } + + spin_unlock(&consumer_lock); + +这表明CPU在读取新元素之前确保索引是最新的,然后在写入新的尾指针之前应确保CPU已完 +成读取该元素,这将擦除该元素。 + +请注意,使用READ_ONCE()和smp_load_acquire()来读取反向(head)索引。这可以防止编译 +器丢弃并重新加载其缓存值。如果您能确定反向(head)索引将仅使用一次,则这不是必须 +的。smp_load_acquire()还可以强制CPU对后续的内存引用进行排序。类似地,两种算法都使 +用smp_store_release()来写入线程的索引。这记录了我们正在写入可以并发读取的内容的事 +实,以防止编译器破坏存储,并强制对以前的访问进行排序。 + + +延伸阅读 +======== + +关于Linux的内存屏障设施的描述,请查看Documentation/memory-barriers.txt。 diff --git a/Documentation/translations/zh_CN/core-api/cpu_hotplug.rst b/Documentation/translations/zh_CN/core-api/cpu_hotplug.rst index 4772a900c37a..bc0d7ea6d834 100644 --- a/Documentation/translations/zh_CN/core-api/cpu_hotplug.rst +++ b/Documentation/translations/zh_CN/core-api/cpu_hotplug.rst @@ -49,12 +49,6 @@ CPU热拔插支持的一个更新颖的用途是它在SMP的暂停恢复支持 限制内核将支持的CPU总量。如果这里提供的数量低于实际可用的CPU数量,那么其他CPU 以后就不能上线了。 -``additional_cpus=n`` - 使用它来限制可热插拔的CPU。该选项设置 - ``cpu_possible_mask = cpu_present_mask + additional_cpus`` - - 这个选项只限于IA64架构。 - ``possible_cpus=n`` 这个选项设置 ``cpu_possible_mask`` 中的 ``possible_cpus`` 位。 diff --git a/Documentation/translations/zh_CN/core-api/errseq.rst b/Documentation/translations/zh_CN/core-api/errseq.rst new file mode 100644 index 000000000000..815fb303ea2f --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/errseq.rst @@ -0,0 +1,145 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/errseq.rst + +:翻译: + + 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn> + +:校译: + + 吴想成 Wu Xiangcheng <bobwxc@email.cn> + +================ +errseq_t数据类型 +================ + +``errseq_t`` 是一种在一个地方记录错误的方法,并允许任意数量的 ``订阅者`` 判断自上 +次采样点以来是否发生了变化。 + +最初的用例是跟踪文件同步系统调用( ``fsync``, ``fdatasync``, ``msync`` 和 +``sync_file_range`` )的错误,但它也可以用于其他情况。 + +它被实现为一个无符号的32位值。低位被指定保存错误代码(在1和MAX_ERRNO之间)。高位 +用作计数器。这里是用原子操作而不是锁来完成的,因此可以从任何上下文中调用这些函数。 + +请注意,如果频繁记录新错误,则存在冲突风险,因为我们用作计数器的位很少。 + +为了缓解这种情况,错误值和计数器之间的位被用作一个标志,以判断自记录新值以来是否 +对该值进行了采样。这使我们能够避免在上次记录错误后没有人取样的情况下碰撞计数器。 + +因此,我们得到了一个类似这样的值: + ++--------------------------------------+------+------------------------+ +| 31..13 | 12 | 11..0 | ++--------------------------------------+------+------------------------+ +| 计数器 | 标志 | 错误值 | ++--------------------------------------+------+------------------------+ + +总体思路是让 ``观察者`` 对errseq_t值进行采样,并将其保留为运行游标。该值稍后可用 +于判断自采样完成后是否发生了任何新错误,并原子地记录检查时的状态。这使得我们能在 +一个地方记录错误,然后有许多 ``观察者`` 可以判断自上次检查以来该值是否发生了变化。 + +新的errseq_t应始终清零。全零的errseq_t值是从未出现错误的特殊(但常见)情况。因此, +如果您希望知道自首次初始化以来是否曾经有过错误集,则全零值被用作 ``纪元`` 。 + +API的使用方法 +============= + +让我给你们讲一个关于员工drone的故事。现在,他总体上是个好员工,但公司有点...管理 +繁重。他今天必须向77名主管汇报,明天 ``大老板`` 要从外地赶来,他肯定也会考验这个 +可怜的家伙。 + +他们都把工作交给他去做---多到他都记不住谁交给他什么了,但这并不是什么大问题。主管 +们只想知道他什么时候完成他们迄今为止交给他的所有工作,以及自从他们上次询问以来他 +是否犯了任何错误。 + +他可能在他们实际上并没有交给他的工作上犯了错误,但他无法在那么详细的层面上记录事 +情,他所能记得的只是他最近犯的错误。 + +下面是我们 ``worker_drone`` 的表达式:: + + struct worker_drone { + errseq_t wd_err; /* 用来记录错误 */ + }; + +每天, ``worker_drone`` 都是以一张白纸开始的:: + + struct worker_drone wd; + + wd.wd_err = (errseq_t)0; + +主管们进来后对当天的工作进行初步了解。他们并不关心在他们观察开始之前发生的任何事 +情:: + + struct supervisor { + errseq_t s_wd_err; /* wd_err的私有“游标” */ + spinlock_t s_wd_err_lock; /* 保护s_wd_err */ + } + + struct supervisor su; + + su.s_wd_err = errseq_sample(&wd.wd_err); + spin_lock_init(&su.s_wd_err_lock); + +现在他们开始给他布置任务。每隔几分钟,他们就要求他完成迄今为止交给他的所有工作。 +然后问他是否有犯任何错误:: + + spin_lock(&su.su_wd_err_lock); + err = errseq_check_and_advance(&wd.wd_err, &su.s_wd_err); + spin_unlock(&su.su_wd_err_lock); + +到目前为止,它只是不断返回0。 + +现在,这家公司的老板非常吝啬,给了他不合格的设备来完成他的工作。偶尔设备会出现故 +障,导致他犯错。他重重地叹了一口气,并把它记录下来:: + + errseq_set(&wd.wd_err, -EIO); + +...然后继续工作。主管们最终会再次检查,他们在下次检查时都会发现这个错误。后续的调 +用将返回0,直到记录下另一个错误,此时将向每个调用报告一次。 + +请注意,主管们无法知道他们犯了多少错误,只能知道自上次检查以来是否犯了一个错误, +以及记录的最新值。 + +偶尔,大老板会来抽查,要求员工为他做一次性的工作。他并不像主管们那样全职观察员工, +但他确实需要知道在他的工作处理过程中是否发生了错误。 + +他只需对员工当前的errseq_t进行采样,然后用它来判断后来是否发生了错误:: + + errseq_t since = errseq_sample(&wd.wd_err); + /* 提交一些工作,等待完成 */ + err = errseq_check(&wd.wd_err, since); + +由于他只是要在那个点之后丢弃 ``since`` ,所以他不需要在这里推进它。同时他也不需要 +任何锁,因为它不能被其他人使用。 + +序列化更新errseq_t游标 +====================== + +请注意,errseq_t API在check_and_advance_operation期间不保护errseq_t游标。只有典型 +的错误代码是被原子化处理的。在多任务同时使用同一个errseq_t游标的情况下,对该游标 +的更新进行序列化是很重要的。 + +如果不这样做,那么游标就有可能向后移动。在这种情况下,同一个错误可能被报告多次。 + +因此,通常先执行errseq_check检查是否有任何变化,然后在获取锁后才执行 +errseq_check_and_advance。例如:: + + if (errseq_check(&wd.wd_err, READ_ONCE(su.s_wd_err)) { + /* su.s_wd_err被s_wd_err_lock保护 */ + spin_lock(&su.s_wd_err_lock); + err = errseq_check_and_advance(&wd.wd_err, &su.s_wd_err); + spin_unlock(&su.s_wd_err_lock); + } + +这就避免了自上次检查以来没有任何变化的常见情况下的自旋锁。 + +函数 +==== + +该API在以下内核代码中: + +lib/errseq.c diff --git a/Documentation/translations/zh_CN/core-api/generic-radix-tree.rst b/Documentation/translations/zh_CN/core-api/generic-radix-tree.rst new file mode 100644 index 000000000000..eacd1d2ebddc --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/generic-radix-tree.rst @@ -0,0 +1,23 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/generic-radix-tree.rst + +:翻译: + + 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn> + +=================== +通用基数树/稀疏数组 +=================== + +通用基数树/稀疏数组的相关内容请见include/linux/generic-radix-tree.h文件中的 +“DOC: Generic radix trees/sparse arrays”。 + +通用基数树函数 +-------------- + +该API在以下内核代码中: + +include/linux/generic-radix-tree.h diff --git a/Documentation/translations/zh_CN/core-api/idr.rst b/Documentation/translations/zh_CN/core-api/idr.rst new file mode 100644 index 000000000000..97a16e76b81b --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/idr.rst @@ -0,0 +1,80 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/idr.rst + +:翻译: + + 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn> + +:校译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + 吴想成 Wu Xiangcheng <bobwxc@email.cn> + 时奎亮 Alex Shi <alexs@kernel.org> + +====== +ID分配 +====== + +:作者: Matthew Wilcox + +概述 +==== + +要解决的一个常见问题是分配标识符(IDs);它通常是标识事物的数字。比如包括文件描述 +符、进程ID、网络协议中的数据包标识符、SCSI标记和设备实例编号。IDR和IDA为这个问题 +提供了一个合理的解决方案,以避免每个人都自创。IDR提供将ID映射到指针的能力,而IDA +仅提供ID分配,因此内存效率更高。 + +IDR接口已经被废弃,请使用 ``XArray`` 。 + +IDR的用法 +========= + +首先初始化一个IDR,对于静态分配的IDR使用DEFINE_IDR(),或者对于动态分配的IDR使用 +idr_init()。 + +您可以调用idr_alloc()来分配一个未使用的ID。通过调用idr_find()查询与该ID相关的指针, +并通过调用idr_remove()释放该ID。 + +如果需要更改与一个ID相关联的指针,可以调用idr_replace()。这样做的一个常见原因是通 +过将 ``NULL`` 指针传递给分配函数来保留ID;用保留的ID初始化对象,最后将初始化的对 +象插入IDR。 + +一些用户需要分配大于 ``INT_MAX`` 的ID。到目前为止,所有这些用户都满足 ``UINT_MAX`` +的限制,他们使用idr_alloc_u32()。如果您需要超出u32的ID,我们将与您合作以满足您的 +需求。 + +如果需要按顺序分配ID,可以使用idr_alloc_cyclic()。处理较大数量的ID时,IDR的效率会 +降低,所以使用这个函数会有一点代价。 + +要对IDR使用的所有指针进行操作,您可以使用基于回调的idr_for_each()或迭代器样式的 +idr_for_each_entry()。您可能需要使用idr_for_each_entry_continue()来继续迭代。如果 +迭代器不符合您的需求,您也可以使用idr_get_next()。 + +当使用完IDR后,您可以调用idr_destroy()来释放IDR占用的内存。这并不会释放IDR指向的 +对象;如果您想这样做,请使用其中一个迭代器来执行此操作。 + +您可以使用idr_is_empty()来查看当前是否分配了任何ID。 + +如果在从IDR分配一个新ID时需要带锁,您可能需要传递一组限制性的GFP标志,但这可能导 +致IDR无法分配内存。为了解决该问题,您可以在获取锁之前调用idr_preload(),然后在分 +配之后调用idr_preload_end()。 + +IDR同步的相关内容请见include/linux/idr.h文件中的“DOC: idr sync”。 + +IDA的用法 +========= + +IDA的用法的相关内容请见lib/idr.c文件中的“DOC: IDA description”。 + +函数和数据结构 +============== + +该API在以下内核代码中: + +include/linux/idr.h + +lib/idr.c diff --git a/Documentation/translations/zh_CN/core-api/index.rst b/Documentation/translations/zh_CN/core-api/index.rst index 7ca44629860c..922cabf7b5dd 100644 --- a/Documentation/translations/zh_CN/core-api/index.rst +++ b/Documentation/translations/zh_CN/core-api/index.rst @@ -44,17 +44,16 @@ assoc_array xarray rbtree - -Todolist: - - - idr circular-buffers generic-radix-tree packing - bus-virt-phys-mapping this_cpu_ops + +======= + +Todolist: + timekeeping errseq @@ -102,7 +101,7 @@ Todolist: ======== 如何在内核中分配和使用内存。请注意,在 -:doc:`/vm/index` 中有更多的内存管理文档。 +:doc:`/mm/index` 中有更多的内存管理文档。 .. toctree:: :maxdepth: 1 diff --git a/Documentation/translations/zh_CN/core-api/kernel-api.rst b/Documentation/translations/zh_CN/core-api/kernel-api.rst index c22662679065..a1ea7081077c 100644 --- a/Documentation/translations/zh_CN/core-api/kernel-api.rst +++ b/Documentation/translations/zh_CN/core-api/kernel-api.rst @@ -48,6 +48,8 @@ lib/string_helpers.c 该API在以下内核代码中: +include/linux/fortify-string.h + lib/string.c include/linux/string.h @@ -119,6 +121,12 @@ include/linux/textsearch.h Linux中的CRC和数学函数 ====================== +算术溢出检查 +------------ + +该API在以下内核代码中: + +include/linux/overflow.h CRC函数 ------- @@ -166,8 +174,6 @@ include/asm-generic/div64.h include/linux/math64.h -lib/math/div64.c - lib/math/gcd.c UUID/GUID @@ -220,7 +226,7 @@ kernel/relay.c 该API在以下内核代码中: -kernel/kmod.c +kernel/module/kmod.c 模块接口支持 ------------ diff --git a/Documentation/translations/zh_CN/core-api/local_ops.rst b/Documentation/translations/zh_CN/core-api/local_ops.rst index 41e4525038e8..eb5423f60f17 100644 --- a/Documentation/translations/zh_CN/core-api/local_ops.rst +++ b/Documentation/translations/zh_CN/core-api/local_ops.rst @@ -185,7 +185,7 @@ UP之间没有不同的行为,在你的架构的 ``local.h`` 中包括 ``asm-g static void __exit test_exit(void) { - del_timer_sync(&test_timer); + timer_shutdown_sync(&test_timer); } module_init(test_init); diff --git a/Documentation/translations/zh_CN/core-api/mm-api.rst b/Documentation/translations/zh_CN/core-api/mm-api.rst index a732b0eebf16..113359bdb7be 100644 --- a/Documentation/translations/zh_CN/core-api/mm-api.rst +++ b/Documentation/translations/zh_CN/core-api/mm-api.rst @@ -37,7 +37,7 @@ mm/gup.c 该API在以下内核代码中: -include/linux/gfp.h +include/linux/gfp_types.h Slab缓存 ======== diff --git a/Documentation/translations/zh_CN/core-api/packing.rst b/Documentation/translations/zh_CN/core-api/packing.rst new file mode 100644 index 000000000000..c0aab3a349d0 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/packing.rst @@ -0,0 +1,160 @@ +.. 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并简单地接受这些错误是合理的。它可以选择 +转储栈或打印错误描述。 diff --git a/Documentation/translations/zh_CN/core-api/printk-basics.rst b/Documentation/translations/zh_CN/core-api/printk-basics.rst index 59c6efb3fc41..cafa01bccff2 100644 --- a/Documentation/translations/zh_CN/core-api/printk-basics.rst +++ b/Documentation/translations/zh_CN/core-api/printk-basics.rst @@ -100,7 +100,7 @@ printk()的用法通常是这样的:: 为了调试,还有两个有条件编译的宏: pr_debug()和pr_devel(),除非定义了 ``DEBUG`` (或者在pr_debug()的情况下定义了 -``CONFIG_DYNAMIC_DEBUG`` ),否则它们会被编译。 +``CONFIG_DYNAMIC_DEBUG`` ),否则它们不会被编译。 函数接口 diff --git a/Documentation/translations/zh_CN/core-api/this_cpu_ops.rst b/Documentation/translations/zh_CN/core-api/this_cpu_ops.rst new file mode 100644 index 000000000000..bea5ee8eb8a0 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/this_cpu_ops.rst @@ -0,0 +1,285 @@ +.. 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区域缺少本地缓存行,其性能和唤醒时间将受到影响。 diff --git a/Documentation/translations/zh_CN/core-api/workqueue.rst b/Documentation/translations/zh_CN/core-api/workqueue.rst index f6567cf9d3fb..7fac6f75d078 100644 --- a/Documentation/translations/zh_CN/core-api/workqueue.rst +++ b/Documentation/translations/zh_CN/core-api/workqueue.rst @@ -202,7 +202,7 @@ workqueue将自动创建与属性相匹配的后备工作者池。调节并发 同的排序属性。 在目前的实现中,上述配置只保证了特定NUMA节点内的ST行为。相反, -``alloc_ordered_queue()`` 应该被用来实现全系统的ST行为。 +``alloc_ordered_workqueue()`` 应该被用来实现全系统的ST行为。 执行场景示例 @@ -313,8 +313,8 @@ And with cmwq with ``@max_active`` >= 3, :: 第一个可以用追踪的方式进行跟踪: :: - $ echo workqueue:workqueue_queue_work > /sys/kernel/debug/tracing/set_event - $ cat /sys/kernel/debug/tracing/trace_pipe > out.txt + $ echo workqueue:workqueue_queue_work > /sys/kernel/tracing/set_event + $ cat /sys/kernel/tracing/trace_pipe > out.txt (wait a few secs) 如果有什么东西在工作队列上忙着做循环,它就会主导输出,可以用工作项函数确定违规者。 |