xref: /openbmc/linux/Documentation/translations/zh_CN/mm/vmalloced-kernel-stacks.rst (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
1*6614a3c3SLinus Torvalds.. SPDX-License-Identifier: GPL-2.0
2*6614a3c3SLinus Torvalds.. include:: ../disclaimer-zh_CN.rst
3*6614a3c3SLinus Torvalds
4*6614a3c3SLinus Torvalds:Original: Documentation/mm/vmalloced-kernel-stacks.rst
5*6614a3c3SLinus Torvalds
6*6614a3c3SLinus Torvalds:翻译:
7*6614a3c3SLinus Torvalds
8*6614a3c3SLinus Torvalds 司延腾 Yanteng Si <siyanteng@loongson.cn>
9*6614a3c3SLinus Torvalds
10*6614a3c3SLinus Torvalds:校译:
11*6614a3c3SLinus Torvalds
12*6614a3c3SLinus Torvalds====================
13*6614a3c3SLinus Torvalds支持虚拟映射的内核栈
14*6614a3c3SLinus Torvalds====================
15*6614a3c3SLinus Torvalds
16*6614a3c3SLinus Torvalds:作者: Shuah Khan <skhan@linuxfoundation.org>
17*6614a3c3SLinus Torvalds
18*6614a3c3SLinus Torvalds.. contents:: :local:
19*6614a3c3SLinus Torvalds
20*6614a3c3SLinus Torvalds概览
21*6614a3c3SLinus Torvalds----
22*6614a3c3SLinus Torvalds
23*6614a3c3SLinus Torvalds这是介绍 `虚拟映射内核栈功能 <https://lwn.net/Articles/694348/>` 的代码
24*6614a3c3SLinus Torvalds和原始补丁系列的信息汇总。
25*6614a3c3SLinus Torvalds
26*6614a3c3SLinus Torvalds简介
27*6614a3c3SLinus Torvalds----
28*6614a3c3SLinus Torvalds
29*6614a3c3SLinus Torvalds内核堆栈溢出通常难以调试,并使内核容易被(恶意)利用。问题可能在稍后的时间出现,使其难以
30*6614a3c3SLinus Torvalds隔离和究其根本原因。
31*6614a3c3SLinus Torvalds
32*6614a3c3SLinus Torvalds带有保护页的虚拟映射内核堆栈如果溢出,会被立即捕获,而不会放任其导致难以诊断的损
33*6614a3c3SLinus Torvalds坏。
34*6614a3c3SLinus Torvalds
35*6614a3c3SLinus TorvaldsHAVE_ARCH_VMAP_STACK和VMAP_STACK配置选项能够支持带有保护页的虚拟映射堆栈。
36*6614a3c3SLinus Torvalds当堆栈溢出时,这个特性会引发可靠的异常。溢出后堆栈跟踪的可用性以及对溢出本身的
37*6614a3c3SLinus Torvalds响应取决于架构。
38*6614a3c3SLinus Torvalds
39*6614a3c3SLinus Torvalds.. note::
40*6614a3c3SLinus Torvalds        截至本文撰写时, arm64, powerpc, riscv, s390, um, 和 x86 支持VMAP_STACK。
41*6614a3c3SLinus Torvalds
42*6614a3c3SLinus TorvaldsHAVE_ARCH_VMAP_STACK
43*6614a3c3SLinus Torvalds--------------------
44*6614a3c3SLinus Torvalds
45*6614a3c3SLinus Torvalds能够支持虚拟映射内核栈的架构应该启用这个bool配置选项。要求是:
46*6614a3c3SLinus Torvalds
47*6614a3c3SLinus Torvalds- vmalloc空间必须大到足以容纳许多内核堆栈。这可能排除了许多32位架构。
48*6614a3c3SLinus Torvalds- vmalloc空间的堆栈需要可靠地工作。例如,如果vmap页表是按需创建的,当堆栈指向
49*6614a3c3SLinus Torvalds  具有未填充页表的虚拟地址时,这种机制需要工作,或者架构代码(switch_to()和
50*6614a3c3SLinus Torvalds  switch_mm(),很可能)需要确保堆栈的页表项在可能未填充的堆栈上运行之前已经填
51*6614a3c3SLinus Torvalds  充。
52*6614a3c3SLinus Torvalds- 如果堆栈溢出到一个保护页,就应该发生一些合理的事情。“合理”的定义是灵活的,但
53*6614a3c3SLinus Torvalds  在没有记录任何东西的情况下立即重启是不友好的。
54*6614a3c3SLinus Torvalds
55*6614a3c3SLinus TorvaldsVMAP_STACK
56*6614a3c3SLinus Torvalds----------
57*6614a3c3SLinus Torvalds
58*6614a3c3SLinus TorvaldsVMAP_STACK bool配置选项在启用时分配虚拟映射的任务栈。这个选项依赖于
59*6614a3c3SLinus TorvaldsHAVE_ARCH_VMAP_STACK。
60*6614a3c3SLinus Torvalds
61*6614a3c3SLinus Torvalds- 如果你想使用带有保护页的虚拟映射的内核堆栈,请启用该选项。这将导致内核栈溢出
62*6614a3c3SLinus Torvalds  被立即捕获,而不是难以诊断的损坏。
63*6614a3c3SLinus Torvalds
64*6614a3c3SLinus Torvalds.. note::
65*6614a3c3SLinus Torvalds
66*6614a3c3SLinus Torvalds        使用KASAN的这个功能需要架构支持用真实的影子内存来支持虚拟映射,并且
67*6614a3c3SLinus Torvalds        必须启用KASAN_VMALLOC。
68*6614a3c3SLinus Torvalds
69*6614a3c3SLinus Torvalds.. note::
70*6614a3c3SLinus Torvalds
71*6614a3c3SLinus Torvalds        启用VMAP_STACK时,无法在堆栈分配的数据上运行DMA。
72*6614a3c3SLinus Torvalds
73*6614a3c3SLinus Torvalds内核配置选项和依赖性不断变化。请参考最新的代码库:
74*6614a3c3SLinus Torvalds
75*6614a3c3SLinus Torvalds`Kconfig <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/Kconfig>`
76*6614a3c3SLinus Torvalds
77*6614a3c3SLinus Torvalds分配方法
78*6614a3c3SLinus Torvalds--------
79*6614a3c3SLinus Torvalds
80*6614a3c3SLinus Torvalds当一个新的内核线程被创建时,线程堆栈是由页级分配器分配的虚拟连续的内存页组成。这
81*6614a3c3SLinus Torvalds些页面被映射到有PAGE_KERNEL保护的连续的内核虚拟空间。
82*6614a3c3SLinus Torvalds
83*6614a3c3SLinus Torvaldsalloc_thread_stack_node()调用__vmalloc_node_range()来分配带有PAGE_KERNEL
84*6614a3c3SLinus Torvalds保护的栈。
85*6614a3c3SLinus Torvalds
86*6614a3c3SLinus Torvalds- 分配的堆栈被缓存起来,以后会被新的线程重用,所以在分配/释放堆栈给任务时,要手动
87*6614a3c3SLinus Torvalds  进行memcg核算。因此,__vmalloc_node_range被调用时没有__GFP_ACCOUNT。
88*6614a3c3SLinus Torvalds- vm_struct被缓存起来,以便能够找到在中断上下文中启动的空闲线程。 free_thread_stack()
89*6614a3c3SLinus Torvalds  可以在中断上下文中调用。
90*6614a3c3SLinus Torvalds- 在arm64上,所有VMAP的堆栈都需要有相同的对齐方式,以确保VMAP的堆栈溢出检测正常
91*6614a3c3SLinus Torvalds  工作。架构特定的vmap堆栈分配器照顾到了这个细节。
92*6614a3c3SLinus Torvalds- 这并不涉及中断堆栈--参考原始补丁
93*6614a3c3SLinus Torvalds
94*6614a3c3SLinus Torvalds线程栈分配是由clone()、fork()、vfork()、kernel_thread()通过kernel_clone()
95*6614a3c3SLinus Torvalds启动的。留点提示在这,以便搜索代码库,了解线程栈何时以及如何分配。
96*6614a3c3SLinus Torvalds
97*6614a3c3SLinus Torvalds大量的代码是在:
98*6614a3c3SLinus Torvalds`kernel/fork.c <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/fork.c>`.
99*6614a3c3SLinus Torvalds
100*6614a3c3SLinus Torvaldstask_struct中的stack_vm_area指针可以跟踪虚拟分配的堆栈,一个非空的stack_vm_area
101*6614a3c3SLinus Torvalds指针可以表明虚拟映射的内核堆栈已经启用。
102*6614a3c3SLinus Torvalds
103*6614a3c3SLinus Torvalds::
104*6614a3c3SLinus Torvalds
105*6614a3c3SLinus Torvalds        struct vm_struct *stack_vm_area;
106*6614a3c3SLinus Torvalds
107*6614a3c3SLinus Torvalds堆栈溢出处理
108*6614a3c3SLinus Torvalds------------
109*6614a3c3SLinus Torvalds
110*6614a3c3SLinus Torvalds前守护页和后守护页有助于检测堆栈溢出。当堆栈溢出到守护页时,处理程序必须小心不要再
111*6614a3c3SLinus Torvalds次溢出堆栈。当处理程序被调用时,很可能只留下很少的堆栈空间。
112*6614a3c3SLinus Torvalds
113*6614a3c3SLinus Torvalds在x86上,这是通过处理表明内核堆栈溢出的双异常堆栈的缺页异常来实现的。
114*6614a3c3SLinus Torvalds
115*6614a3c3SLinus Torvalds用守护页测试VMAP分配
116*6614a3c3SLinus Torvalds--------------------
117*6614a3c3SLinus Torvalds
118*6614a3c3SLinus Torvalds我们如何确保VMAP_STACK在分配时确实有前守护页和后守护页的保护?下面的 lkdtm 测试
119*6614a3c3SLinus Torvalds可以帮助检测任何回归。
120*6614a3c3SLinus Torvalds
121*6614a3c3SLinus Torvalds::
122*6614a3c3SLinus Torvalds
123*6614a3c3SLinus Torvalds        void lkdtm_STACK_GUARD_PAGE_LEADING()
124*6614a3c3SLinus Torvalds        void lkdtm_STACK_GUARD_PAGE_TRAILING()
125*6614a3c3SLinus Torvalds
126*6614a3c3SLinus Torvalds结论
127*6614a3c3SLinus Torvalds----
128*6614a3c3SLinus Torvalds
129*6614a3c3SLinus Torvalds- vmalloced堆栈的percpu缓存似乎比高阶堆栈分配要快一些,至少在缓存命中时是这样。
130*6614a3c3SLinus Torvalds- THREAD_INFO_IN_TASK完全摆脱了arch-specific thread_info,并简单地将
131*6614a3c3SLinus Torvalds  thread_info(仅包含标志)和'int cpu'嵌入task_struct中。
132*6614a3c3SLinus Torvalds- 一旦任务死亡,线程栈就可以被释放(无需等待RCU),然后,如果使用vmapped栈,就
133*6614a3c3SLinus Torvalds  可以将整个栈缓存起来,以便在同一cpu上重复使用。
134