diff options
Diffstat (limited to 'Documentation/translations/zh_CN/core-api')
20 files changed, 3817 insertions, 25 deletions
diff --git a/Documentation/translations/zh_CN/core-api/cachetlb.rst b/Documentation/translations/zh_CN/core-api/cachetlb.rst new file mode 100644 index 000000000000..6fee45fe5e80 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/cachetlb.rst @@ -0,0 +1,327 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/cachetlb.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +.. _cn_core-api_cachetlb: + +====================== +Linux下的缓存和TLB刷新 +====================== + +:作者: David S. Miller <davem@redhat.com> + +*译注:TLB,Translation Lookaside Buffer,页表缓存/变换旁查缓冲器* + +本文描述了由Linux虚拟内存子系统调用的缓存/TLB刷新接口。它列举了每个接 +口,描述了它的预期目的,以及接口被调用后的预期副作用。 + +下面描述的副作用是针对单处理器的实现,以及在单个处理器上发生的情况。若 +为SMP,则只需将定义简单地扩展一下,使发生在某个特定接口的副作用扩展到系 +统的所有处理器上。不要被这句话吓到,以为SMP的缓存/tlb刷新一定是很低 +效的,事实上,这是一个可以进行很多优化的领域。例如,如果可以证明一个用 +户地址空间从未在某个cpu上执行过(见mm_cpumask()),那么就不需要在该 +cpu上对这个地址空间进行刷新。 + +首先是TLB刷新接口,因为它们是最简单的。在Linux下,TLB被抽象为cpu +用来缓存从软件页表获得的虚拟->物理地址转换的东西。这意味着,如果软件页 +表发生变化,这个“TLB”缓存中就有可能出现过时(脏)的翻译。因此,当软件页表 +发生变化时,内核会在页表发生 *变化后* 调用以下一种刷新方法: + +1) ``void flush_tlb_all(void)`` + + 最严格的刷新。在这个接口运行后,任何以前的页表修改都会对cpu可见。 + + 这通常是在内核页表被改变时调用的,因为这种转换在本质上是“全局”的。 + +2) ``void flush_tlb_mm(struct mm_struct *mm)`` + + 这个接口从TLB中刷新整个用户地址空间。在运行后,这个接口必须确保 + 以前对地址空间‘mm’的任何页表修改对cpu来说是可见的。也就是说,在 + 运行后,TLB中不会有‘mm’的页表项。 + + 这个接口被用来处理整个地址空间的页表操作,比如在fork和exec过程 + 中发生的事情。 + +3) ``void flush_tlb_range(struct vm_area_struct *vma, + unsigned long start, unsigned long end)`` + + 这里我们要从TLB中刷新一个特定范围的(用户)虚拟地址转换。在运行后, + 这个接口必须确保以前对‘start’到‘end-1’范围内的地址空间‘vma->vm_mm’ + 的任何页表修改对cpu来说是可见的。也就是说,在运行后,TLB中不会有 + ‘mm’的页表项用于‘start’到‘end-1’范围内的虚拟地址。 + + “vma”是用于该区域的备份存储。主要是用于munmap()类型的操作。 + + 提供这个接口是希望端口能够找到一个合适的有效方法来从TLB中删除多 + 个页面大小的转换,而不是让内核为每个可能被修改的页表项调用 + flush_tlb_page(见下文)。 + +4) ``void flush_tlb_page(struct vm_area_struct *vma, unsigned long addr)`` + + 这一次我们需要从TLB中删除PAGE_SIZE大小的转换。‘vma’是Linux用来跟 + 踪进程的mmap区域的支持结构体,地址空间可以通过vma->vm_mm获得。另 + 外,可以通过测试(vma->vm_flags & VM_EXEC)来查看这个区域是否是 + 可执行的(因此在split-tlb类型的设置中可能在“指令TLB”中)。 + + 在运行后,这个接口必须确保之前对用户虚拟地址“addr”的地址空间 + “vma->vm_mm”的页表修改对cpu来说是可见的。也就是说,在运行后,TLB + 中不会有虚拟地址‘addr’的‘vma->vm_mm’的页表项。 + + 这主要是在故障处理时使用。 + +5) ``void update_mmu_cache(struct vm_area_struct *vma, + unsigned long address, pte_t *ptep)`` + + 在每个缺页异常结束时,这个程序被调用,以告诉体系结构特定的代码,在 + 软件页表中,在地址空间“vma->vm_mm”的虚拟地址“地址”处,现在存在 + 一个翻译。 + + 可以用它所选择的任何方式使用这个信息来进行移植。例如,它可以使用这 + 个事件来为软件管理的TLB配置预装TLB转换。目前sparc64移植就是这么干 + 的。 + +接下来,我们有缓存刷新接口。一般来说,当Linux将现有的虚拟->物理映射 +改变为新的值时,其顺序将是以下形式之一:: + + 1) flush_cache_mm(mm); + change_all_page_tables_of(mm); + flush_tlb_mm(mm); + + 2) flush_cache_range(vma, start, end); + change_range_of_page_tables(mm, start, end); + flush_tlb_range(vma, start, end); + + 3) flush_cache_page(vma, addr, pfn); + set_pte(pte_pointer, new_pte_val); + flush_tlb_page(vma, addr); + +缓存级别的刷新将永远是第一位的,因为这允许我们正确处理那些缓存严格, +且在虚拟地址被从缓存中刷新时要求一个虚拟地址的虚拟->物理转换存在的系统。 +HyperSparc cpu就是这样一个具有这种属性的cpu。 + +下面的缓存刷新程序只需要在特定的cpu需要的范围内处理缓存刷新。大多数 +情况下,这些程序必须为cpu实现,这些cpu有虚拟索引的缓存,当虚拟->物 +理转换被改变或移除时,必须被刷新。因此,例如,IA32处理器的物理索引 +的物理标记的缓存没有必要实现这些接口,因为这些缓存是完全同步的,并 +且不依赖于翻译信息。 + +下面逐个列出这些程序: + +1) ``void flush_cache_mm(struct mm_struct *mm)`` + + 这个接口将整个用户地址空间从高速缓存中刷掉。也就是说,在运行后, + 将没有与‘mm’相关的缓存行。 + + 这个接口被用来处理整个地址空间的页表操作,比如在退出和执行过程 + 中发生的事情。 + +2) ``void flush_cache_dup_mm(struct mm_struct *mm)`` + + 这个接口将整个用户地址空间从高速缓存中刷新掉。也就是说,在运行 + 后,将没有与‘mm’相关的缓存行。 + + 这个接口被用来处理整个地址空间的页表操作,比如在fork过程中发生 + 的事情。 + + 这个选项与flush_cache_mm分开,以允许对VIPT缓存进行一些优化。 + +3) ``void flush_cache_range(struct vm_area_struct *vma, + unsigned long start, unsigned long end)`` + + 在这里,我们要从缓存中刷新一个特定范围的(用户)虚拟地址。运行 + 后,在“start”到“end-1”范围内的虚拟地址的“vma->vm_mm”的缓存中 + 将没有页表项。 + + “vma”是被用于该区域的备份存储。主要是用于munmap()类型的操作。 + + 提供这个接口是希望端口能够找到一个合适的有效方法来从缓存中删 + 除多个页面大小的区域, 而不是让内核为每个可能被修改的页表项调 + 用 flush_cache_page (见下文)。 + +4) ``void flush_cache_page(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn)`` + + 这一次我们需要从缓存中删除一个PAGE_SIZE大小的区域。“vma”是 + Linux用来跟踪进程的mmap区域的支持结构体,地址空间可以通过 + vma->vm_mm获得。另外,我们可以通过测试(vma->vm_flags & + VM_EXEC)来查看这个区域是否是可执行的(因此在“Harvard”类 + 型的缓存布局中可能是在“指令缓存”中)。 + + “pfn”表示“addr”所对应的物理页框(通过PAGE_SHIFT左移这个 + 值来获得物理地址)。正是这个映射应该从缓存中删除。 + + 在运行之后,对于虚拟地址‘addr’的‘vma->vm_mm’,在缓存中不会 + 有任何页表项,它被翻译成‘pfn’。 + + 这主要是在故障处理过程中使用。 + +5) ``void flush_cache_kmaps(void)`` + + 只有在平台使用高位内存的情况下才需要实现这个程序。它将在所有的 + kmaps失效之前被调用。 + + 运行后,内核虚拟地址范围PKMAP_ADDR(0)到PKMAP_ADDR(LAST_PKMAP) + 的缓存中将没有页表项。 + + 这个程序应该在asm/highmem.h中实现。 + +6) ``void flush_cache_vmap(unsigned long start, unsigned long end)`` + ``void flush_cache_vunmap(unsigned long start, unsigned long end)`` + + 在这里,在这两个接口中,我们从缓存中刷新一个特定范围的(内核) + 虚拟地址。运行后,在“start”到“end-1”范围内的虚拟地址的内核地 + 址空间的缓存中不会有页表项。 + + 这两个程序中的第一个是在vmap_range()安装了页表项之后调用的。 + 第二个是在vunmap_range()删除页表项之前调用的。 + +还有一类cpu缓存问题,目前需要一套完全不同的接口来正确处理。最大 +的问题是处理器的数据缓存中的虚拟别名。 + +.. note:: + + 这段内容有些晦涩,为了减轻中文阅读压力,特作此译注。 + + 别名(alias)属于缓存一致性问题,当不同的虚拟地址映射相同的 + 物理地址,而这些虚拟地址的index不同,此时就发生了别名现象(多 + 个虚拟地址被称为别名)。通俗点来说就是指同一个物理地址的数据被 + 加载到不同的cacheline中就会出现别名现象。 + + 常见的解决方法有两种:第一种是硬件维护一致性,设计特定的cpu电 + 路来解决问题(例如设计为PIPT的cache);第二种是软件维护一致性, + 就是下面介绍的sparc的解决方案——页面染色,涉及的技术细节太多, + 译者不便展开,请读者自行查阅相关资料。 + +您的移植是否容易在其D-cache中出现虚拟别名?嗯,如果您的D-cache +是虚拟索引的,且cache大于PAGE_SIZE(页大小),并且不能防止同一 +物理地址的多个cache行同时存在,您就会遇到这个问题。 + +如果你的D-cache有这个问题,首先正确定义asm/shmparam.h SHMLBA, +它基本上应该是你的虚拟寻址D-cache的大小(或者如果大小是可变的, +则是最大的可能大小)。这个设置将迫使SYSv IPC层只允许用户进程在 +这个值的倍数的地址上对共享内存进行映射。 + +.. note:: + + 这并不能解决共享mmaps的问题,请查看sparc64移植解决 + 这个问题的一个方法(特别是 SPARC_FLAG_MMAPSHARED)。 + +接下来,你必须解决所有其他情况下的D-cache别名问题。请记住这个事 +实,对于一个给定的页面映射到某个用户地址空间,总是至少还有一个映 +射,那就是内核在其线性映射中从PAGE_OFFSET开始。因此,一旦第一个 +用户将一个给定的物理页映射到它的地址空间,就意味着D-cache的别名 +问题有可能存在,因为内核已经将这个页映射到它的虚拟地址。 + + ``void copy_user_page(void *to, void *from, unsigned long addr, struct page *page)`` + ``void clear_user_page(void *to, unsigned long addr, struct page *page)`` + + 这两个程序在用户匿名或COW页中存储数据。它允许一个端口有效地 + 避免用户空间和内核之间的D-cache别名问题。 + + 例如,一个端口可以在复制过程中把“from”和“to”暂时映射到内核 + 的虚拟地址上。这两个页面的虚拟地址的选择方式是,内核的加载/存 + 储指令发生在虚拟地址上,而这些虚拟地址与用户的页面映射是相同 + 的“颜色”。例如,Sparc64就使用这种技术。 + + “addr”参数告诉了用户最终要映射这个页面的虚拟地址,“page”参 + 数给出了一个指向目标页结构体的指针。 + + 如果D-cache别名不是问题,这两个程序可以简单地直接调用 + memcpy/memset而不做其他事情。 + + ``void flush_dcache_page(struct page *page)`` + + 任何时候,当内核写到一个页面缓存页,或者内核要从一个页面缓存 + 页中读出,并且这个页面的用户空间共享/可写映射可能存在时, + 这个程序就会被调用。 + + .. note:: + + 这个程序只需要为有可能被映射到用户进程的地址空间的 + 页面缓存调用。因此,例如,处理页面缓存中vfs符号链 + 接的VFS层代码根本不需要调用这个接口。 + + “内核写入页面缓存的页面”这句话的意思是,具体来说,内核执行存 + 储指令,在该页面的页面->虚拟映射处弄脏该页面的数据。在这里,通 + 过刷新的手段处理D-cache的别名是很重要的,以确保这些内核存储对 + 该页的用户空间映射是可见的。 + + 推论的情况也同样重要,如果有用户对这个文件有共享+可写的映射, + 我们必须确保内核对这些页面的读取会看到用户所做的最新的存储。 + + 如果D-cache别名不是一个问题,这个程序可以简单地定义为该架构上 + 的nop。 + + 在page->flags (PG_arch_1)中有一个位是“架构私有”。内核保证, + 对于分页缓存的页面,当这样的页面第一次进入分页缓存时,它将清除 + 这个位。 + + 这使得这些接口可以更有效地被实现。如果目前没有用户进程映射这个 + 页面,它允许我们“推迟”(也许是无限期)实际的刷新过程。请看 + sparc64的flush_dcache_page和update_mmu_cache实现,以了解如 + 何做到这一点。 + + 这个想法是,首先在flush_dcache_page()时,如果page->mapping->i_mmap + 是一个空树,只需标记架构私有页标志位。之后,在update_mmu_cache() + 中,会对这个标志位进行检查,如果设置了,就进行刷新,并清除标志位。 + + .. important:: + + 通常很重要的是,如果你推迟刷新,实际的刷新发生在同一个 + CPU上,因为它将cpu存储到页面上,使其变脏。同样,请看 + sparc64关于如何处理这个问题的例子。 + + ``void copy_to_user_page(struct vm_area_struct *vma, struct page *page, + unsigned long user_vaddr, void *dst, void *src, int len)`` + ``void copy_from_user_page(struct vm_area_struct *vma, struct page *page, + unsigned long user_vaddr, void *dst, void *src, int len)`` + + 当内核需要复制任意的数据进出任意的用户页时(比如ptrace()),它将使 + 用这两个程序。 + + 任何必要的缓存刷新或其他需要发生的一致性操作都应该在这里发生。如果 + 处理器的指令缓存没有对cpu存储进行窥探,那么你很可能需要为 + copy_to_user_page()刷新指令缓存。 + + ``void flush_anon_page(struct vm_area_struct *vma, struct page *page, + unsigned long vmaddr)`` + + 当内核需要访问一个匿名页的内容时,它会调用这个函数(目前只有 + get_user_pages())。注意:flush_dcache_page()故意对匿名页不起作 + 用。默认的实现是nop(对于所有相干的架构应该保持这样)。对于不一致性 + 的架构,它应该刷新vmaddr处的页面缓存。 + + ``void flush_icache_range(unsigned long start, unsigned long end)`` + + 当内核存储到它将执行的地址中时(例如在加载模块时),这个函数被调用。 + + 如果icache不对存储进行窥探,那么这个程序将需要对其进行刷新。 + + ``void flush_icache_page(struct vm_area_struct *vma, struct page *page)`` + + flush_icache_page的所有功能都可以在flush_dcache_page和update_mmu_cache + 中实现。在未来,我们希望能够完全删除这个接口。 + +最后一类API是用于I/O到内核内特意设置的别名地址范围。这种别名是通过使用 +vmap/vmalloc API设置的。由于内核I/O是通过物理页进行的,I/O子系统假定用户 +映射和内核偏移映射是唯一的别名。这对vmap别名来说是不正确的,所以内核中任何 +试图对vmap区域进行I/O的东西都必须手动管理一致性。它必须在做I/O之前刷新vmap +范围,并在I/O返回后使其失效。 + + ``void flush_kernel_vmap_range(void *vaddr, int size)`` + + 刷新vmap区域中指定的虚拟地址范围的内核缓存。这是为了确保内核在vmap范围 + 内修改的任何数据对物理页是可见的。这个设计是为了使这个区域可以安全地执 + 行I/O。注意,这个API并 *没有* 刷新该区域的偏移映射别名。 + + ``void invalidate_kernel_vmap_range(void *vaddr, int size) invalidates`` + + 在vmap区域的一个给定的虚拟地址范围的缓存,这可以防止处理器在物理页的I/O + 发生时通过投机性地读取数据而使缓存变脏。这只对读入vmap区域的数据是必要的。 diff --git a/Documentation/translations/zh_CN/core-api/cpu_hotplug.rst b/Documentation/translations/zh_CN/core-api/cpu_hotplug.rst new file mode 100644 index 000000000000..85a264287426 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/cpu_hotplug.rst @@ -0,0 +1,348 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/cpu_hotplug.rst +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +.. _cn_core_api_cpu_hotplug: + +================= +内核中的CPU热拔插 +================= + +:时间: 2016年12月 +:作者: Sebastian Andrzej Siewior <bigeasy@linutronix.de>, + Rusty Russell <rusty@rustcorp.com.au>, + Srivatsa Vaddagiri <vatsa@in.ibm.com>, + Ashok Raj <ashok.raj@intel.com>, + Joel Schopp <jschopp@austin.ibm.com> + +简介 +==== + +现代系统架构的演进已经在处理器中引入了先进的错误报告和纠正能力。有一些OEM也支 +持可热拔插的NUMA(Non Uniform Memory Access,非统一内存访问)硬件,其中物理 +节点的插入和移除需要支持CPU热插拔。 + +这样的进步要求内核可用的CPU被移除,要么是出于配置的原因,要么是出于RAS的目的, +以保持一个不需要的CPU不在系统执行路径。因此需要在Linux内核中支持CPU热拔插。 + +CPU热拔插支持的一个更新颖的用途是它在SMP的暂停恢复支持中的应用。双核和超线程支 +持使得即使是笔记本电脑也能运行不支持这些方法的SMP内核。 + + +命令行开关 +========== + +``maxcpus=n`` + 限制启动时的CPU为 *n* 个。例如,如果你有四个CPU,使用 ``maxcpus=2`` 将只能启 + 动两个。你可以选择稍后让其他CPU上线。 + +``nr_cpus=n`` + 限制内核将支持的CPU总量。如果这里提供的数量低于实际可用的CPU数量,那么其他CPU + 以后就不能上线了。 + +``additional_cpus=n`` + 使用它来限制可热插拔的CPU。该选项设置 + ``cpu_possible_mask = cpu_present_mask + additional_cpus`` + + 这个选项只限于IA64架构。 + +``possible_cpus=n`` + 这个选项设置 ``cpu_possible_mask`` 中的 ``possible_cpus`` 位。 + + 这个选项只限于X86和S390架构。 + +``cpu0_hotplug`` + 允许关闭CPU0。 + + 这个选项只限于X86架构。 + +CPU位图 +======= + +``cpu_possible_mask`` + 系统中可能可用CPU的位图。这是用来为per_cpu变量分配一些启动时的内存,这些变量 + 不会随着CPU的可用或移除而增加/减少。一旦在启动时的发现阶段被设置,该映射就是静态 + 的,也就是说,任何时候都不会增加或删除任何位。根据你的系统需求提前准确地调整它 + 可以节省一些启动时的内存。 + +``cpu_online_mask`` + 当前在线的所有CPU的位图。在一个CPU可用于内核调度并准备接收设备的中断后,它被 + 设置在 ``__cpu_up()`` 中。当使用 ``__cpu_disable()`` 关闭一个CPU时,它被清 + 空,在此之前,所有的操作系统服务包括中断都被迁移到另一个目标CPU。 + +``cpu_present_mask`` + 系统中当前存在的CPU的位图。它们并非全部在线。当物理热拔插被相关的子系统 + (如ACPI)处理时,可以改变和添加新的位或从位图中删除,这取决于事件是 + hot-add/hot-remove。目前还没有定死规定。典型的用法是在启动时启动拓扑结构,这时 + 热插拔被禁用。 + +你真的不需要操作任何系统的CPU映射。在大多数情况下,它们应该是只读的。当设置每个 +CPU资源时,几乎总是使用 ``cpu_possible_mask`` 或 ``for_each_possible_cpu()`` +来进行迭代。宏 ``for_each_cpu()`` 可以用来迭代一个自定义的CPU掩码。 + +不要使用 ``cpumask_t`` 以外的任何东西来表示CPU的位图。 + + +使用CPU热拔插 +============= + +内核选项 *CONFIG_HOTPLUG_CPU* 需要被启用。它目前可用于多种架构,包括ARM、MIPS、 +PowerPC和X86。配置是通过sysfs接口完成的:: + + $ ls -lh /sys/devices/system/cpu + total 0 + drwxr-xr-x 9 root root 0 Dec 21 16:33 cpu0 + drwxr-xr-x 9 root root 0 Dec 21 16:33 cpu1 + drwxr-xr-x 9 root root 0 Dec 21 16:33 cpu2 + drwxr-xr-x 9 root root 0 Dec 21 16:33 cpu3 + drwxr-xr-x 9 root root 0 Dec 21 16:33 cpu4 + drwxr-xr-x 9 root root 0 Dec 21 16:33 cpu5 + drwxr-xr-x 9 root root 0 Dec 21 16:33 cpu6 + drwxr-xr-x 9 root root 0 Dec 21 16:33 cpu7 + drwxr-xr-x 2 root root 0 Dec 21 16:33 hotplug + -r--r--r-- 1 root root 4.0K Dec 21 16:33 offline + -r--r--r-- 1 root root 4.0K Dec 21 16:33 online + -r--r--r-- 1 root root 4.0K Dec 21 16:33 possible + -r--r--r-- 1 root root 4.0K Dec 21 16:33 present + +文件 *offline* 、 *online* 、*possible* 、*present* 代表CPU掩码。每个CPU文件 +夹包含一个 *online* 文件,控制逻辑上的开(1)和关(0)状态。要在逻辑上关闭CPU4:: + + $ echo 0 > /sys/devices/system/cpu/cpu4/online + smpboot: CPU 4 is now offline + +一旦CPU被关闭,它将从 */proc/interrupts* 、*/proc/cpuinfo* 中被删除,也不应该 +被 *top* 命令显示出来。要让CPU4重新上线:: + + $ echo 1 > /sys/devices/system/cpu/cpu4/online + smpboot: Booting Node 0 Processor 4 APIC 0x1 + +CPU又可以使用了。这应该对所有的CPU都有效。CPU0通常比较特殊,被排除在CPU热拔插之外。 +在X86上,内核选项 *CONFIG_BOOTPARAM_HOTPLUG_CPU0* 必须被启用,以便能够关闭CPU0。 +或者,可以使用内核命令选项 *cpu0_hotplug* 。CPU0的一些已知的依赖性: + +* 从休眠/暂停中恢复。如果CPU0处于离线状态,休眠/暂停将失败。 +* PIC中断。如果检测到PIC中断,CPU0就不能被移除。 + +如果你发现CPU0上有任何依赖性,请告知Fenghua Yu <fenghua.yu@intel.com>。 + +CPU的热拔插协作 +=============== + +下线情况 +-------- + +一旦CPU被逻辑关闭,注册的热插拔状态的清除回调将被调用,从 ``CPUHP_ONLINE`` 开始,在 +``CPUHP_OFFLINE`` 状态结束。这包括: + +* 如果任务因暂停操作而被冻结,那么 *cpuhp_tasks_frozen* 将被设置为true。 + +* 所有进程都会从这个将要离线的CPU迁移到新的CPU上。新的CPU是从每个进程的当前cpuset中 + 选择的,它可能是所有在线CPU的一个子集。 + +* 所有针对这个CPU的中断都被迁移到新的CPU上。 + +* 计时器也会被迁移到新的CPU上。 + +* 一旦所有的服务被迁移,内核会调用一个特定的例程 ``__cpu_disable()`` 来进行特定的清 + 理。 + +使用热插拔API +------------- + +一旦一个CPU下线或上线,就有可能收到通知。这对某些需要根据可用CPU数量执行某种设置或清 +理功能的驱动程序来说可能很重要:: + + #include <linux/cpuhotplug.h> + + ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "X/Y:online", + Y_online, Y_prepare_down); + +*X* 是子系统, *Y* 是特定的驱动程序。 *Y_online* 回调将在所有在线CPU的注册过程中被调用。 +如果在线回调期间发生错误, *Y_prepare_down* 回调将在所有之前调用过在线回调的CPU上调 +用。注册完成后,一旦有CPU上线, *Y_online* 回调将被调用,当CPU关闭时, *Y_prepare_down* +将被调用。所有之前在 *Y_online* 中分配的资源都应该在 *Y_prepare_down* 中释放。如果在 +注册过程中发生错误,返回值 *ret* 为负值。否则会返回一个正值,其中包含动态分配状态 +( *CPUHP_AP_ONLINE_DYN* )的分配热拔插。对于预定义的状态,它将返回0。 + +该回调可以通过调用 ``cpuhp_remove_state()`` 来删除。如果是动态分配的状态 +( *CPUHP_AP_ONLINE_DYN* ),则使用返回的状态。在移除热插拔状态的过程中,将调用拆解回调。 + +多个实例 +~~~~~~~~ + +如果一个驱动程序有多个实例,并且每个实例都需要独立执行回调,那么很可能应该使用 +``multi-state`` 。首先需要注册一个多状态的状态:: + + ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, "X/Y:online, + Y_online, Y_prepare_down); + Y_hp_online = ret; + +``cpuhp_setup_state_multi()`` 的行为与 ``cpuhp_setup_state()`` 类似,只是它 +为多状态准备了回调,但不调用回调。这是一个一次性的设置。 +一旦分配了一个新的实例,你需要注册这个新实例:: + + ret = cpuhp_state_add_instance(Y_hp_online, &d->node); + +这个函数将把这个实例添加到你先前分配的 ``Y_hp_online`` 状态,并在所有在线的 +CPU上调用先前注册的回调( ``Y_online`` )。 *node* 元素是你的每个实例数据结构 +中的一个 ``struct hlist_node`` 成员。 + +在移除该实例时:: + + cpuhp_state_remove_instance(Y_hp_online, &d->node) + +应该被调用,这将在所有在线CPU上调用拆分回调。 + +手动设置 +~~~~~~~~ + +通常情况下,在注册或移除状态时调用setup和teamdown回调是很方便的,因为通常在CPU上线 +(下线)和驱动的初始设置(关闭)时需要执行该操作。然而,每个注册和删除功能也有一个 +_nocalls的后缀,如果不希望调用回调,则不调用所提供的回调。在手动设置(或关闭)期间, +应该使用 ``get_online_cpus()`` 和 ``put_online_cpus()`` 函数来抑制CPU热插拔操作。 + + +事件的顺序 +---------- + +热插拔状态被定义在 ``include/linux/cpuhotplug.h``: + +* ``CPUHP_OFFLINE`` ... ``CPUHP_AP_OFFLINE`` 状态是在CPU启动前调用的。 + +* ``CPUHP_AP_OFFLINE`` ... ``CPUHP_AP_ONLINE`` 状态是在CPU被启动后被调用的。 + 中断是关闭的,调度程序还没有在这个CPU上活动。从 ``CPUHP_AP_OFFLINE`` 开始, + 回调被调用到目标CPU上。 + +* ``CPUHP_AP_ONLINE_DYN`` 和 ``CPUHP_AP_ONLINE_DYN_END`` 之间的状态被保留 + 给动态分配。 + +* 这些状态在CPU关闭时以相反的顺序调用,从 ``CPUHP_ONLINE`` 开始,在 ``CPUHP_OFFLINE`` + 停止。这里的回调是在将被关闭的CPU上调用的,直到 ``CPUHP_AP_OFFLINE`` 。 + +通过 ``CPUHP_AP_ONLINE_DYN`` 动态分配的状态通常已经足够了。然而,如果在启动或关闭 +期间需要更早的调用,那么应该获得一个显式状态。如果热拔插事件需要相对于另一个热拔插事 +件的特定排序,也可能需要一个显式状态。 + +测试热拔插状态 +============== + +验证自定义状态是否按预期工作的一个方法是关闭一个CPU,然后再把它上线。也可以把CPU放到某 +些状态(例如 ``CPUHP_AP_ONLINE`` ),然后再回到 ``CPUHP_ONLINE`` 。这将模拟在 +``CPUHP_AP_ONLINE`` 之后的一个状态出现错误,从而导致回滚到在线状态。 + +所有注册的状态都被列举在 ``/sys/devices/system/cpu/hotplug/states`` :: + + $ tail /sys/devices/system/cpu/hotplug/states + 138: mm/vmscan:online + 139: mm/vmstat:online + 140: lib/percpu_cnt:online + 141: acpi/cpu-drv:online + 142: base/cacheinfo:online + 143: virtio/net:online + 144: x86/mce:online + 145: printk:online + 168: sched:active + 169: online + +要将CPU4回滚到 ``lib/percpu_cnt:online`` ,再回到在线状态,只需发出:: + + $ cat /sys/devices/system/cpu/cpu4/hotplug/state + 169 + $ echo 140 > /sys/devices/system/cpu/cpu4/hotplug/target + $ cat /sys/devices/system/cpu/cpu4/hotplug/state + 140 + +需要注意的是,状态140的清除回调已经被调用。现在重新上线:: + + $ echo 169 > /sys/devices/system/cpu/cpu4/hotplug/target + $ cat /sys/devices/system/cpu/cpu4/hotplug/state + 169 + +启用追踪事件后,单个步骤也是可见的:: + + # TASK-PID CPU# TIMESTAMP FUNCTION + # | | | | | + bash-394 [001] 22.976: cpuhp_enter: cpu: 0004 target: 140 step: 169 (cpuhp_kick_ap_work) + cpuhp/4-31 [004] 22.977: cpuhp_enter: cpu: 0004 target: 140 step: 168 (sched_cpu_deactivate) + cpuhp/4-31 [004] 22.990: cpuhp_exit: cpu: 0004 state: 168 step: 168 ret: 0 + cpuhp/4-31 [004] 22.991: cpuhp_enter: cpu: 0004 target: 140 step: 144 (mce_cpu_pre_down) + cpuhp/4-31 [004] 22.992: cpuhp_exit: cpu: 0004 state: 144 step: 144 ret: 0 + cpuhp/4-31 [004] 22.993: cpuhp_multi_enter: cpu: 0004 target: 140 step: 143 (virtnet_cpu_down_prep) + cpuhp/4-31 [004] 22.994: cpuhp_exit: cpu: 0004 state: 143 step: 143 ret: 0 + cpuhp/4-31 [004] 22.995: cpuhp_enter: cpu: 0004 target: 140 step: 142 (cacheinfo_cpu_pre_down) + cpuhp/4-31 [004] 22.996: cpuhp_exit: cpu: 0004 state: 142 step: 142 ret: 0 + bash-394 [001] 22.997: cpuhp_exit: cpu: 0004 state: 140 step: 169 ret: 0 + bash-394 [005] 95.540: cpuhp_enter: cpu: 0004 target: 169 step: 140 (cpuhp_kick_ap_work) + cpuhp/4-31 [004] 95.541: cpuhp_enter: cpu: 0004 target: 169 step: 141 (acpi_soft_cpu_online) + cpuhp/4-31 [004] 95.542: cpuhp_exit: cpu: 0004 state: 141 step: 141 ret: 0 + cpuhp/4-31 [004] 95.543: cpuhp_enter: cpu: 0004 target: 169 step: 142 (cacheinfo_cpu_online) + cpuhp/4-31 [004] 95.544: cpuhp_exit: cpu: 0004 state: 142 step: 142 ret: 0 + cpuhp/4-31 [004] 95.545: cpuhp_multi_enter: cpu: 0004 target: 169 step: 143 (virtnet_cpu_online) + cpuhp/4-31 [004] 95.546: cpuhp_exit: cpu: 0004 state: 143 step: 143 ret: 0 + cpuhp/4-31 [004] 95.547: cpuhp_enter: cpu: 0004 target: 169 step: 144 (mce_cpu_online) + cpuhp/4-31 [004] 95.548: cpuhp_exit: cpu: 0004 state: 144 step: 144 ret: 0 + cpuhp/4-31 [004] 95.549: cpuhp_enter: cpu: 0004 target: 169 step: 145 (console_cpu_notify) + cpuhp/4-31 [004] 95.550: cpuhp_exit: cpu: 0004 state: 145 step: 145 ret: 0 + cpuhp/4-31 [004] 95.551: cpuhp_enter: cpu: 0004 target: 169 step: 168 (sched_cpu_activate) + cpuhp/4-31 [004] 95.552: cpuhp_exit: cpu: 0004 state: 168 step: 168 ret: 0 + bash-394 [005] 95.553: cpuhp_exit: cpu: 0004 state: 169 step: 140 ret: 0 + +可以看到,CPU4一直下降到时间戳22.996,然后又上升到95.552。所有被调用的回调, +包括它们的返回代码都可以在跟踪中看到。 + +架构的要求 +========== + +需要具备以下功能和配置: + +``CONFIG_HOTPLUG_CPU`` + 这个配置项需要在Kconfig中启用 + +``__cpu_up()`` + 调出一个cpu的架构接口 + +``__cpu_disable()`` + 关闭CPU的架构接口,在此程序返回后,内核不能再处理任何中断。这包括定时器的关闭。 + +``__cpu_die()`` + 这实际上是为了确保CPU的死亡。实际上,看看其他架构中实现CPU热拔插的一些示例代 + 码。对于那个特定的架构,处理器被从 ``idle()`` 循环中拿下来。 ``__cpu_die()`` + 通常会等待一些per_cpu状态的设置,以确保处理器的死亡例程被调用来保持活跃。 + +用户空间通知 +============ + +在CPU成功上线或下线后,udev事件被发送。一个udev规则,比如:: + + SUBSYSTEM=="cpu", DRIVERS=="processor", DEVPATH=="/devices/system/cpu/*", RUN+="the_hotplug_receiver.sh" + +将接收所有事件。一个像这样的脚本:: + + #!/bin/sh + + if [ "${ACTION}" = "offline" ] + then + echo "CPU ${DEVPATH##*/} offline" + + elif [ "${ACTION}" = "online" ] + then + echo "CPU ${DEVPATH##*/} online" + + fi + +可以进一步处理该事件。 + +内核内联文档参考 +================ + +该API在以下内核代码中: + +include/linux/cpuhotplug.h diff --git a/Documentation/translations/zh_CN/core-api/genericirq.rst b/Documentation/translations/zh_CN/core-api/genericirq.rst new file mode 100644 index 000000000000..05ccb954c18d --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/genericirq.rst @@ -0,0 +1,409 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/genericirq.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +:校译: + + 吴想成 Wu XiangCheng <bobwxc@email.cn> + +.. include:: <isonum.txt> + +.. _cn_core-api_genericirq: + +================ +Linux通用IRQ处理 +================ + +:版权: |copy| 2005-2010: Thomas Gleixner +:版权: |copy| 2005-2006: Ingo Molnar + +简介 +==== + +通用中断处理层是为了给设备驱动程序提供一个完整的中断处理抽象(层)。它能够处 +理所有不同类型的中断控制器硬件。设备驱动程序使用通用API函数来请求、启用、禁 +用和释放中断。驱动程序不需要知道任何关于硬件处理中断的细节,所以它们可以在不同的 +平台上使用而不需要修改代码。 + +本文档提供给那些希望在通用IRQ处理层的帮助下实现基于其架构的中断子系统的开发 +者。 + +理论依据 +======== + +Linux中中断处理的原始实现使用__do_IRQ()超级处理程序,它能够处理每种类型的 +中断逻辑。 + +最初,Russell King确定了不同类型的处理程序,以便为Linux 2.5/2.6中的ARM中 +断处理程序实现建立一个相当通用的集合。他区分了以下几种类型: + +- 电平触发型 + +- 边沿触发型 + +- 简单型 + +在实现过程中,我们发现了另一种类型: + +- 响应EOI(end of interrupt)型 + +在SMP的__do_IRQ()超级处理程序中,还需定义一种类型: + +- 每cpu型(针对CPU SMP) + +这种高层IRQ处理程序的拆分实现使我们能够为每个特定的中断类型优化中断处理的流 +程。这减少了该特定代码路径的复杂性,并允许对特定类型进行优化处理。 + +最初的通用IRQ实现使用hw_interrupt_type结构体及其 ``->ack`` ``->end`` 等回 +调来区分超级处理程序中的流控制。这导致了流逻辑和低级硬件逻辑的混合,也导致了 +不必要的代码重复:例如i386中的 ``ioapic_level_irq`` 和 ``ioapic_edge_irq`` , +这两个IRQ类型共享许多低级的细节,但有不同的流处理。 + +一个更自然的抽象是“irq流”和“芯片细节”的干净分离。 + +分析一些架构的IRQ子系统的实现可以发现,他们中的大多数可以使用一套通用的“irq +流”方法,只需要添加芯片级的特定代码。这种分离对于那些需要IRQ流本身而不需要芯 +片细节的特定(子)架构也很有价值——以提供了一个更透明的IRQ子系统设计。 + +每个中断描述符都被分配给它自己的高层流程处理程序,这通常是一个通用的实现。(这 +种高层次的流程处理程序的实现也使得提供解复用处理程序变得简单,这可以在各种架 +构的嵌入式平台上找到。) + +这种分离使得通用中断处理层更加灵活和可扩展。例如,一个(子)架构可以使用通用 +的IRQ流实现“电平触发型”中断,并添加一个(子)架构特定的“边沿型”实现。 + +为了使向新模型的过渡更容易,并防止破坏现有实现,__do_IRQ()超级处理程序仍然 +可用。这导致了一种暂时的双重性。随着时间的推移,新的模型应该在越来越多的架构中 +被使用,因为它能使IRQ子系统更小更干净。它已经被废弃三年了,即将被删除。 + +已知的缺陷和假设 +================ + +没有(但愿如此)。 + +抽象层 +====== + +中断代码中主要有三个抽象层次: + +1. 高级别的驱动API + +2. 高级别的IRQ流处理器 + +3. 芯片级的硬件封装 + +中断控制流 +---------- + +每个中断都由一个中断描述符结构体irq_desc来描述。中断是由一个“无符号整型”的数值来 +引用的,它在描述符结构体数组中选择相应的中断描述符结构体。描述符结构体包含状态 +信息和指向中断流方法和中断芯片结构的指针,这些都是分配给这个中断的。 + +每当中断触发时,低级架构代码通过调用desc->handle_irq()调用到通用中断代码中。 +这个高层IRQ处理函数只使用由分配的芯片描述符结构体引用的desc->irq_data.chip +基元。 + +高级驱动程序API +--------------- + +高层驱动API由以下函数组成: + +- request_irq() + +- request_threaded_irq() + +- free_irq() + +- disable_irq() + +- enable_irq() + +- disable_irq_nosync() (SMP only) + +- synchronize_irq() (SMP only) + +- irq_set_irq_type() + +- irq_set_irq_wake() + +- irq_set_handler_data() + +- irq_set_chip() + +- irq_set_chip_data() + +详见自动生成的函数文档。 + +.. note:: + + 由于文档构建流程所限,中文文档中并没有引入自动生成的函数文档,所以请读者直接 + 阅读源码注释。 + +电平触发型IRQ流处理程序 +----------------------- + +通用层提供了一套预定义的irq-flow方法: + +- handle_level_irq() + +- handle_edge_irq() + +- handle_fasteoi_irq() + +- handle_simple_irq() + +- handle_percpu_irq() + +- handle_edge_eoi_irq() + +- handle_bad_irq() + +中断流处理程序(无论是预定义的还是架构特定的)由架构在启动期间或设备初始化期间分配给 +特定中断。 + +默认流实现 +~~~~~~~~~~ + +辅助函数 +^^^^^^^^ + +辅助函数调用芯片基元,并被默认流实现所使用。以下是实现的辅助函数(简化摘录):: + + default_enable(struct irq_data *data) + { + desc->irq_data.chip->irq_unmask(data); + } + + default_disable(struct irq_data *data) + { + if (!delay_disable(data)) + desc->irq_data.chip->irq_mask(data); + } + + default_ack(struct irq_data *data) + { + chip->irq_ack(data); + } + + default_mask_ack(struct irq_data *data) + { + if (chip->irq_mask_ack) { + chip->irq_mask_ack(data); + } else { + chip->irq_mask(data); + chip->irq_ack(data); + } + } + + noop(struct irq_data *data)) + { + } + + + +默认流处理程序的实现 +~~~~~~~~~~~~~~~~~~~~ + +电平触发型IRQ流处理器 +^^^^^^^^^^^^^^^^^^^^^ + +handle_level_irq为电平触发型的中断提供了一个通用实现。 + +实现的控制流如下(简化摘录):: + + desc->irq_data.chip->irq_mask_ack(); + handle_irq_event(desc->action); + desc->irq_data.chip->irq_unmask(); + + +默认的需回应IRQ流处理器 +^^^^^^^^^^^^^^^^^^^^^^^ + +handle_fasteoi_irq为中断提供了一个通用的实现,它只需要在处理程序的末端有一个EOI。 + +实现的控制流如下(简化摘录):: + + handle_irq_event(desc->action); + desc->irq_data.chip->irq_eoi(); + + +默认的边沿触发型IRQ流处理器 +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +handle_edge_irq为边沿触发型的中断提供了一个通用的实现。 + +实现的控制流如下(简化摘录):: + + if (desc->status & running) { + desc->irq_data.chip->irq_mask_ack(); + desc->status |= pending | masked; + return; + } + desc->irq_data.chip->irq_ack(); + desc->status |= running; + do { + if (desc->status & masked) + desc->irq_data.chip->irq_unmask(); + desc->status &= ~pending; + handle_irq_event(desc->action); + } while (status & pending); + desc->status &= ~running; + + +默认的简单型IRQ流处理器 +^^^^^^^^^^^^^^^^^^^^^^^ + +handle_simple_irq提供了一个简单型中断的通用实现。 + +.. note:: + + 简单型的流处理程序不调用任何处理程序/芯片基元。 + +实现的控制流程如下(简化摘录):: + + handle_irq_event(desc->action); + + +默认的每CPU型流处理程序 +^^^^^^^^^^^^^^^^^^^^^^^ + +handle_percpu_irq为每CPU型中断提供一个通用的实现。 + +每个CPU中断只在SMP上可用,该处理程序提供了一个没有锁的简化版本。 + +以下是控制流的实现(简化摘录):: + + if (desc->irq_data.chip->irq_ack) + desc->irq_data.chip->irq_ack(); + handle_irq_event(desc->action); + if (desc->irq_data.chip->irq_eoi) + desc->irq_data.chip->irq_eoi(); + + +EOI边沿型IRQ流处理器 +^^^^^^^^^^^^^^^^^^^^ + +handle_edge_eoi_irq提供了一个异常的边沿触发型处理程序,它只用于拯救powerpc/cell +上的一个严重失控的irq控制器。 + +坏的IRQ流处理器 +^^^^^^^^^^^^^^^ + +handle_bad_irq用于处理没有真正分配处理程序的假中断。 + +特殊性和优化 +~~~~~~~~~~~~ + +通用函数是为“干净”的架构和芯片设计的,它们没有平台特定的IRQ处理特殊性。如果一 +个架构需要在“流”的层面上实现特殊性,那么它可以通过覆盖高层的IRQ-流处理程序来实 +现。 + +延迟中断禁用 +~~~~~~~~~~~~ + +每个中断可选择的功能是由Russell King在ARM中断实现中引入的,当调用disable_irq() +时,不会在硬件层面上屏蔽中断。中断保持启用状态,而在中断事件发生时在流处理器中被 +屏蔽。这可以防止在硬件上丢失边沿中断,因为硬件上不存储边沿中断事件,而中断在硬件 +级被禁用。当一个中断在IRQ_DISABLED标志被设置时到达,那么该中断在硬件层面被屏蔽, +IRQ_PENDING位被设置。当中断被enable_irq()重新启用时,将检查挂起位,如果它被设置, +中断将通过硬件或软件重发机制重新发送。(当你想使用延迟中断禁用功能,而你的硬件又不 +能重新触发中断时,有必要启用CONFIG_HARDIRQS_SW_RESEND。) 延迟中断禁止功能是不可 +配置的。 + +芯片级硬件封装 +-------------- + +芯片级硬件描述符结构体 :c:type:`irq_chip` 包含了所有与芯片直接相关的功能,这些功 +能可以被irq流实现所利用。 + +- ``irq_ack`` + +- ``irq_mask_ack`` - 可选的,建议使用的性能 + +- ``irq_mask`` + +- ``irq_unmask`` + +- ``irq_eoi`` - 可选的,EOI流处理程序需要 + +- ``irq_retrigger`` - 可选的 + +- ``irq_set_type`` - 可选的 + +- ``irq_set_wake`` - 可选的 + +这些基元的意思是严格意义上的:ack是指ACK,masking是指对IRQ线的屏蔽,等等。这取决 +于流处理器如何使用这些基本的低级功能单元。 + +__do_IRQ入口点 +============== + +最初的实现__do_IRQ()是所有类型中断的替代入口点。它已经不存在了。 + +这个处理程序被证明不适合所有的中断硬件,因此被重新实现了边沿/级别/简单/超高速中断 +的拆分功能。这不仅是一个功能优化。它也缩短了中断的代码路径。 + +在SMP上的锁 +=========== + +芯片寄存器的锁定是由定义芯片基元的架构决定的。每个寄存器的结构通过desc->lock,由 +通用层保护。 + +通用中断芯片 +============ + +为了避免复制相同的IRQ芯片实现,核心提供了一个可配置的通用中断芯片实现。开发者在自 +己实现相同的功能之前,应该仔细检查通用芯片是否符合他们的需求,并以稍微不同的方式实 +现相同的功能。 + +该API在以下内核代码中: + +kernel/irq/generic-chip.c + +结构体 +====== + +本章包含自动生成的结构体文档,这些结构体在通用IRQ层中使用。 + +该API在以下内核代码中: + +include/linux/irq.h + +include/linux/interrupt.h + +提供的通用函数 +============== + +这一章包含了自动生成的内核API函数的文档,这些函数被导出。 + +该API在以下内核代码中: + +kernel/irq/manage.c + +kernel/irq/chip.c + +提供的内部函数 +============== + +本章包含自动生成的内部函数的文档。 + +该API在以下内核代码中: + +kernel/irq/irqdesc.c + +kernel/irq/handle.c + +kernel/irq/chip.c + +鸣谢 +==== + +感谢以下人士对本文档作出的贡献: + +1. Thomas Gleixner tglx@linutronix.de + +2. Ingo Molnar mingo@elte.hu diff --git a/Documentation/translations/zh_CN/core-api/index.rst b/Documentation/translations/zh_CN/core-api/index.rst index f1fa71e45c77..72f0a36daa1c 100644 --- a/Documentation/translations/zh_CN/core-api/index.rst +++ b/Documentation/translations/zh_CN/core-api/index.rst @@ -1,10 +1,12 @@ .. include:: ../disclaimer-zh_CN.rst -:Original: :doc:`../../../core-api/irq/index` -:Translator: Yanteng Si <siyanteng@loongson.cn> +:Original: Documentation/core-api/index.rst -.. _cn_core-api_index.rst: +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> +.. _cn_core-api_index.rst: =========== 核心API文档 @@ -19,12 +21,13 @@ 来的大量 kerneldoc 信息;有朝一日,若有人有动力的话,应当把它们拆分 出来。 -Todolist: +.. toctree:: + :maxdepth: 1 kernel-api - workqueue printk-basics printk-formats + workqueue symbol-namespaces 数据结构和低级实用程序 @@ -32,9 +35,13 @@ Todolist: 在整个内核中使用的函数库。 -Todolist: +.. toctree:: + :maxdepth: 1 kobject + +Todolist: + kref assoc_array xarray @@ -58,12 +65,12 @@ Linux如何让一切同时发生。 详情请参阅 :maxdepth: 1 irq/index - -Todolist: - refcount-vs-atomic local_ops padata + +Todolist: + ../RCU/index 低级硬件管理 @@ -71,14 +78,22 @@ Todolist: 缓存管理,CPU热插拔管理等。 -Todolist: +.. toctree:: + :maxdepth: 1 cachetlb cpu_hotplug - memory-hotplug genericirq + memory-hotplug protection-keys +Todolist: + + + memory-hotplug + cpu_hotplug + genericirq + 内存管理 ======== diff --git a/Documentation/translations/zh_CN/core-api/irq/concepts.rst b/Documentation/translations/zh_CN/core-api/irq/concepts.rst index 41455bf0f783..9957f0453353 100644 --- a/Documentation/translations/zh_CN/core-api/irq/concepts.rst +++ b/Documentation/translations/zh_CN/core-api/irq/concepts.rst @@ -1,10 +1,12 @@ .. include:: ../../disclaimer-zh_CN.rst -:Original: :doc:`../../../../core-api/irq/concepts` -:Translator: Yanteng Si <siyanteng@loongson.cn> +:Original: Documentation/core-api/irq/concepts.rst -.. _cn_concepts.rst: +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> +.. _cn_concepts.rst: =========== 什么是IRQ? diff --git a/Documentation/translations/zh_CN/core-api/irq/index.rst b/Documentation/translations/zh_CN/core-api/irq/index.rst index 910ccabf041f..ba6acc4b48e5 100644 --- a/Documentation/translations/zh_CN/core-api/irq/index.rst +++ b/Documentation/translations/zh_CN/core-api/irq/index.rst @@ -1,7 +1,10 @@ .. include:: ../../disclaimer-zh_CN.rst -:Original: :doc:`../../../../core-api/irq/index` -:Translator: Yanteng Si <siyanteng@loongson.cn> +:Original: Documentation/core-api/irq/index.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> .. _cn_irq_index.rst: diff --git a/Documentation/translations/zh_CN/core-api/irq/irq-affinity.rst b/Documentation/translations/zh_CN/core-api/irq/irq-affinity.rst index 82a4428f22fd..7addd5f27a88 100644 --- a/Documentation/translations/zh_CN/core-api/irq/irq-affinity.rst +++ b/Documentation/translations/zh_CN/core-api/irq/irq-affinity.rst @@ -1,10 +1,12 @@ .. include:: ../../disclaimer-zh_CN.rst -:Original: :doc:`../../../../core-api/irq/irq-affinity` -:Translator: Yanteng Si <siyanteng@loongson.cn> +:Original: Documentation/core-api/irq/irq-affinity -.. _cn_irq-affinity.rst: +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> +.. _cn_irq-affinity.rst: ============== SMP IRQ 亲和性 diff --git a/Documentation/translations/zh_CN/core-api/irq/irq-domain.rst b/Documentation/translations/zh_CN/core-api/irq/irq-domain.rst index 3c82dd307a46..7d077742f758 100644 --- a/Documentation/translations/zh_CN/core-api/irq/irq-domain.rst +++ b/Documentation/translations/zh_CN/core-api/irq/irq-domain.rst @@ -1,10 +1,12 @@ .. include:: ../../disclaimer-zh_CN.rst -:Original: :doc:`../../../../core-api/irq/irq-domain` -:Translator: Yanteng Si <siyanteng@loongson.cn> +:Original: Documentation/core-api/irq/irq-domain.rst -.. _cn_irq-domain.rst: +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> +.. _cn_irq-domain.rst: ======================= irq_domain 中断号映射库 diff --git a/Documentation/translations/zh_CN/core-api/irq/irqflags-tracing.rst b/Documentation/translations/zh_CN/core-api/irq/irqflags-tracing.rst index c889bd0f65d9..9af50b4b8c2d 100644 --- a/Documentation/translations/zh_CN/core-api/irq/irqflags-tracing.rst +++ b/Documentation/translations/zh_CN/core-api/irq/irqflags-tracing.rst @@ -1,10 +1,12 @@ .. include:: ../../disclaimer-zh_CN.rst -:Original: :doc:`../../../../core-api/irq/irqflags-tracing` -:Translator: Yanteng Si <siyanteng@loongson.cn> +:Original: Documentation/core-api/irq/irqflags-tracing.rst -.. _cn_irqflags-tracing.rst: +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> +.. _cn_irqflags-tracing.rst: ================= IRQ-flags状态追踪 diff --git a/Documentation/translations/zh_CN/core-api/kernel-api.rst b/Documentation/translations/zh_CN/core-api/kernel-api.rst new file mode 100644 index 000000000000..ab7d81889340 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/kernel-api.rst @@ -0,0 +1,371 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/kernel-api.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_kernel-api.rst: + +============ +Linux内核API +============ + + +列表管理函数 +============ + +该API在以下内核代码中: + +include/linux/list.h + +基本的C库函数 +============= + +在编写驱动程序时,一般不能使用C库中的例程。部分函数通常很有用,它们在 +下面被列出。这些函数的行为可能会与ANSI定义的略有不同,这些偏差会在文中 +注明。 + +字符串转换 +---------- + +该API在以下内核代码中: + +lib/vsprintf.c + +include/linux/kernel.h + +include/linux/kernel.h + +lib/kstrtox.c + +lib/string_helpers.c + +字符串处理 +---------- + +该API在以下内核代码中: + +lib/string.c + +include/linux/string.h + +mm/util.c + +基本的内核库函数 +================ + +Linux内核提供了很多实用的基本函数。 + +位运算 +------ + +该API在以下内核代码中: + +include/asm-generic/bitops/instrumented-atomic.h + +include/asm-generic/bitops/instrumented-non-atomic.h + +include/asm-generic/bitops/instrumented-lock.h + +位图运算 +-------- + +该API在以下内核代码中: + +lib/bitmap.c + +include/linux/bitmap.h + +include/linux/bitmap.h + +include/linux/bitmap.h + +lib/bitmap.c + +lib/bitmap.c + +include/linux/bitmap.h + +命令行解析 +---------- + +该API在以下内核代码中: + +lib/cmdline.c + +排序 +---- + +该API在以下内核代码中: + +lib/sort.c + +lib/list_sort.c + +文本检索 +-------- + +该API在以下内核代码中: + +lib/textsearch.c + +lib/textsearch.c + +include/linux/textsearch.h + +Linux中的CRC和数学函数 +====================== + + +CRC函数 +------- + +*译注:CRC,Cyclic Redundancy Check,循环冗余校验* + +该API在以下内核代码中: + +lib/crc4.c + +lib/crc7.c + +lib/crc8.c + +lib/crc16.c + +lib/crc32.c + +lib/crc-ccitt.c + +lib/crc-itu-t.c + +基数为2的对数和幂函数 +--------------------- + +该API在以下内核代码中: + +include/linux/log2.h + +整数幂函数 +---------- + +该API在以下内核代码中: + +lib/math/int_pow.c + +lib/math/int_sqrt.c + +除法函数 +-------- + +该API在以下内核代码中: + +include/asm-generic/div64.h + +include/linux/math64.h + +lib/math/div64.c + +lib/math/gcd.c + +UUID/GUID +--------- + +该API在以下内核代码中: + +lib/uuid.c + +内核IPC设备 +=========== + +IPC实用程序 +----------- + +该API在以下内核代码中: + +ipc/util.c + +FIFO 缓冲区 +=========== + +kfifo接口 +--------- + +该API在以下内核代码中: + +include/linux/kfifo.h + +转发接口支持 +============ + +转发接口支持旨在为工具和设备提供一种有效的机制,将大量数据从内核空间 +转发到用户空间。 + +转发接口 +-------- + +该API在以下内核代码中: + +kernel/relay.c + +kernel/relay.c + +模块支持 +======== + +模块加载 +-------- + +该API在以下内核代码中: + +kernel/kmod.c + +模块接口支持 +------------ + +更多信息请参考文件kernel/module.c。 + +硬件接口 +======== + + +该API在以下内核代码中: + +kernel/dma.c + +资源管理 +-------- + +该API在以下内核代码中: + +kernel/resource.c + +kernel/resource.c + +MTRR处理 +-------- + +该API在以下内核代码中: + +arch/x86/kernel/cpu/mtrr/mtrr.c + +安全框架 +======== + +该API在以下内核代码中: + +security/security.c + +security/inode.c + +审计接口 +======== + +该API在以下内核代码中: + +kernel/audit.c + +kernel/auditsc.c + +kernel/auditfilter.c + +核算框架 +======== + +该API在以下内核代码中: + +kernel/acct.c + +块设备 +====== + +该API在以下内核代码中: + +block/blk-core.c + +block/blk-core.c + +block/blk-map.c + +block/blk-sysfs.c + +block/blk-settings.c + +block/blk-exec.c + +block/blk-flush.c + +block/blk-lib.c + +block/blk-integrity.c + +kernel/trace/blktrace.c + +block/genhd.c + +block/genhd.c + +字符设备 +======== + +该API在以下内核代码中: + +fs/char_dev.c + +时钟框架 +======== + +时钟框架定义了编程接口,以支持系统时钟树的软件管理。该框架广泛用于系统级芯片(SOC)平 +台,以支持电源管理和各种可能需要自定义时钟速率的设备。请注意,这些 “时钟”与计时或实 +时时钟(RTC)无关,它们都有单独的框架。这些:c:type: `struct clk <clk>` 实例可用于管理 +各种时钟信号,例如一个96理例如96MHz的时钟信号,该信号可被用于总线或外设的数据交换,或以 +其他方式触发系统硬件中的同步状态机转换。 + +通过明确的软件时钟门控来支持电源管理:未使用的时钟被禁用,因此系统不会因为改变不在使用 +中的晶体管的状态而浪费电源。在某些系统中,这可能是由硬件时钟门控支持的,其中时钟被门控 +而不在软件中被禁用。芯片的部分,在供电但没有时钟的情况下,可能会保留其最后的状态。这种 +低功耗状态通常被称为*保留模式*。这种模式仍然会产生漏电流,特别是在电路几何结构较细的情 +况下,但对于CMOS电路来说,电能主要是随着时钟翻转而被消耗的。 + +电源感知驱动程序只有在其管理的设备处于活动使用状态时才会启用时钟。此外,系统睡眠状态通 +常根据哪些时钟域处于活动状态而有所不同:“待机”状态可能允许从多个活动域中唤醒,而 +"mem"(暂停到RAM)状态可能需要更全面地关闭来自高速PLL和振荡器的时钟,从而限制了可能 +的唤醒事件源的数量。驱动器的暂停方法可能需要注意目标睡眠状态的系统特定时钟约束。 + +一些平台支持可编程时钟发生器。这些可以被各种外部芯片使用,如其他CPU、多媒体编解码器以 +及对接口时钟有严格要求的设备。 + +该API在以下内核代码中: + +include/linux/clk.h + +同步原语 +======== + +读-复制-更新(RCU) +------------------- + +该API在以下内核代码中: + +include/linux/rcupdate.h + +kernel/rcu/tree.c + +kernel/rcu/tree_exp.h + +kernel/rcu/update.c + +include/linux/srcu.h + +kernel/rcu/srcutree.c + +include/linux/rculist_bl.h + +include/linux/rculist.h + +include/linux/rculist_nulls.h + +include/linux/rcu_sync.h + +kernel/rcu/sync.c diff --git a/Documentation/translations/zh_CN/core-api/kobject.rst b/Documentation/translations/zh_CN/core-api/kobject.rst new file mode 100644 index 000000000000..b7c37794cc7f --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/kobject.rst @@ -0,0 +1,381 @@ +.. 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; + struct attribute **default_attrs; + 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_attrs)控制这种 +类型的对象如何在 sysfs 中被表示;它们超出了本文的范围。 + +default_attrs 指针是一个默认属性的列表,它将为任何用这个 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 kset *kset, struct kobject *kobj); + const char *(* const name)(struct kset *kset, struct kobject *kobj); + int (* const uevent)(struct kset *kset, 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`` ,它们将被构建为可加载模块。 diff --git a/Documentation/translations/zh_CN/core-api/local_ops.rst b/Documentation/translations/zh_CN/core-api/local_ops.rst new file mode 100644 index 000000000000..41e4525038e8 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/local_ops.rst @@ -0,0 +1,196 @@ +.. 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) + { + del_timer_sync(&test_timer); + } + + module_init(test_init); + module_exit(test_exit); + + MODULE_LICENSE("GPL"); + MODULE_AUTHOR("Mathieu Desnoyers"); + MODULE_DESCRIPTION("Local Atomic Ops"); diff --git a/Documentation/translations/zh_CN/core-api/memory-hotplug.rst b/Documentation/translations/zh_CN/core-api/memory-hotplug.rst new file mode 100644 index 000000000000..161f4d2c18cc --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/memory-hotplug.rst @@ -0,0 +1,126 @@ +.. 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_high; + int status_change_nid; + } + +- start_pfn是在线/离线内存的start_pfn。 + +- nr_pages是在线/离线内存的页数。 + +- status_change_nid_normal是当nodemask的N_NORMAL_MEMORY被设置/清除时设置节 + 点id,如果是-1,则nodemask状态不改变。 + +- status_change_nid_high是当nodemask的N_HIGH_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实现,所以访问内存的代码可以防止该内存 +消失。 diff --git a/Documentation/translations/zh_CN/core-api/padata.rst b/Documentation/translations/zh_CN/core-api/padata.rst new file mode 100644 index 000000000000..781d30675afd --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/padata.rst @@ -0,0 +1,161 @@ +.. 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 diff --git a/Documentation/translations/zh_CN/core-api/printk-basics.rst b/Documentation/translations/zh_CN/core-api/printk-basics.rst new file mode 100644 index 000000000000..d574de3167c8 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/printk-basics.rst @@ -0,0 +1,112 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/printk-basics.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_printk-basics.rst: + +================== +使用printk记录消息 +================== + +printk()是Linux内核中最广为人知的函数之一。它是我们打印消息的标准工具,通常也是追踪和调试 +的最基本方法。如果你熟悉printf(3),你就能够知道printk()是基于它的,尽管它在功能上有一些不 +同之处: + + - printk() 消息可以指定日志级别。 + + - 格式字符串虽然与C99基本兼容,但并不遵循完全相同的规范。它有一些扩展和一些限制(没 + 有 ``%n`` 或浮点转换指定符)。参见:ref: `如何正确地获得printk格式指定符<printk-specifiers>` 。 + +所有的printk()消息都会被打印到内核日志缓冲区,这是一个通过/dev/kmsg输出到用户空间的环 +形缓冲区。读取它的通常方法是使用 ``dmesg`` 。 + +printk()的用法通常是这样的:: + + printk(KERN_INFO "Message: %s\n", arg); + +其中 ``KERN_INFO`` 是日志级别(注意,它与格式字符串连在一起,日志级别不是一个单独的参数)。 +可用的日志级别是: + + ++----------------+--------+-----------------------------------------------+ +| 名称 | 字符串 | 别名函数 | ++================+========+===============================================+ +| KERN_EMERG | "0" | pr_emerg() | ++----------------+--------+-----------------------------------------------+ +| KERN_ALERT | "1" | pr_alert() | ++----------------+--------+-----------------------------------------------+ +| KERN_CRIT | "2" | pr_crit() | ++----------------+--------+-----------------------------------------------+ +| KERN_ERR | "3" | pr_err() | ++----------------+--------+-----------------------------------------------+ +| KERN_WARNING | "4" | pr_warn() | ++----------------+--------+-----------------------------------------------+ +| KERN_NOTICE | "5" | pr_notice() | ++----------------+--------+-----------------------------------------------+ +| KERN_INFO | "6" | pr_info() | ++----------------+--------+-----------------------------------------------+ +| KERN_DEBUG | "7" | pr_debug() and pr_devel() 若定义了DEBUG | ++----------------+--------+-----------------------------------------------+ +| KERN_DEFAULT | "" | | ++----------------+--------+-----------------------------------------------+ +| KERN_CONT | "c" | pr_cont() | ++----------------+--------+-----------------------------------------------+ + + +日志级别指定了一条消息的重要性。内核根据日志级别和当前 *console_loglevel* (一个内核变量)决 +定是否立即显示消息(将其打印到当前控制台)。如果消息的优先级比 *console_loglevel* 高(日志级 +别值较低),消息将被打印到控制台。 + +如果省略了日志级别,则以 ``KERN_DEFAULT`` 级别打印消息。 + +你可以用以下方法检查当前的 *console_loglevel* :: + + $ cat /proc/sys/kernel/printk + 4 4 1 7 + +结果显示了 *current*, *default*, *minimum* 和 *boot-time-default* 日志级别 + +要改变当前的 console_loglevel,只需在 ``/proc/sys/kernel/printk`` 中写入所需的 +级别。例如,要打印所有的消息到控制台上:: + + # echo 8 > /proc/sys/kernel/printk + +另一种方式,使用 ``dmesg``:: + + # dmesg -n 5 + +设置 console_loglevel 打印 KERN_WARNING (4) 或更严重的消息到控制台。更多消息参 +见 ``dmesg(1)`` 。 + +作为printk()的替代方案,你可以使用 ``pr_*()`` 别名来记录日志。这个系列的宏在宏名中 +嵌入了日志级别。例如:: + + pr_info("Info message no. %d\n", msg_num); + +打印 ``KERN_INFO`` 消息。 + +除了比等效的printk()调用更简洁之外,它们还可以通过pr_fmt()宏为格式字符串使用一个通用 +的定义。例如,在源文件的顶部(在任何 ``#include`` 指令之前)定义这样的内容。:: + + #define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ + +会在该文件中的每一条 pr_*() 消息前加上发起该消息的模块和函数名称。 + +为了调试,还有两个有条件编译的宏: +pr_debug()和pr_devel(),除非定义了 ``DEBUG`` (或者在pr_debug()的情况下定义了 +``CONFIG_DYNAMIC_DEBUG`` ),否则它们会被编译。 + + +函数接口 +======== + +该API在以下内核代码中: + +kernel/printk/printk.c + +include/linux/printk.h diff --git a/Documentation/translations/zh_CN/core-api/printk-formats.rst b/Documentation/translations/zh_CN/core-api/printk-formats.rst new file mode 100644 index 000000000000..ce39c788cf5a --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/printk-formats.rst @@ -0,0 +1,597 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/printk-formats.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@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 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>。 + +谢谢你的合作和关注。 diff --git a/Documentation/translations/zh_CN/core-api/protection-keys.rst b/Documentation/translations/zh_CN/core-api/protection-keys.rst new file mode 100644 index 000000000000..d07830050153 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/protection-keys.rst @@ -0,0 +1,99 @@ +.. 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。 diff --git a/Documentation/translations/zh_CN/core-api/refcount-vs-atomic.rst b/Documentation/translations/zh_CN/core-api/refcount-vs-atomic.rst new file mode 100644 index 000000000000..e2467fd26fc0 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/refcount-vs-atomic.rst @@ -0,0 +1,156 @@ +.. 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顺序 + 控制依赖 + 持有 diff --git a/Documentation/translations/zh_CN/core-api/symbol-namespaces.rst b/Documentation/translations/zh_CN/core-api/symbol-namespaces.rst new file mode 100644 index 000000000000..6abf7ed534ca --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/symbol-namespaces.rst @@ -0,0 +1,144 @@ +.. 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.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 diff --git a/Documentation/translations/zh_CN/core-api/workqueue.rst b/Documentation/translations/zh_CN/core-api/workqueue.rst new file mode 100644 index 000000000000..e372fa5cf101 --- /dev/null +++ b/Documentation/translations/zh_CN/core-api/workqueue.rst @@ -0,0 +1,339 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/core-api/workqueue.rst + +:翻译: + + 司延腾 Yanteng Si <siyanteng@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来说是没有意义的。 + +请注意,标志 ``WQ_NON_REENTRANT`` 不再存在,因为现在所有的工作 +队列都是不可逆的——任何工作项都保证在任何时间内最多被整个系统的一 +个工作者执行。 + + +``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 + +工作项函数在堆栈追踪中应该是微不足道的。 + + +内核内联文档参考 +================ + +该API在以下内核代码中: + +include/linux/workqueue.h + +kernel/workqueue.c |