Skip to content

Commit d07a462

Browse files
mm: apart the slab paragraph from mm to an individual page
1 parent 8947aba commit d07a462

File tree

3 files changed

+254
-8
lines changed

3 files changed

+254
-8
lines changed

kernel/mm/mm.md

-2
Original file line numberDiff line numberDiff line change
@@ -527,8 +527,6 @@ static inline void __kunmap_atomic(void *addr)
527527
* Per-CPU 的新接口并不兼容之前的内核。
528528
529529
# 参考资料
530-
* [Linux slab 分配器剖析](https://www.ibm.com/developerworks/cn/linux/l-linux-slab-allocator/)
531530
* [/PROC/MEMINFO之谜](http://linuxperf.com/?p=142)
532-
* [怎样诊断SLAB泄露问题](http://linuxperf.com/?p=148)
533531
* [Linux内核高端内存](http://ilinuxkernel.com/?p=1013)
534532
* [Per-CPU variables](https://0xax.gitbooks.io/linux-insides/content/Concepts/linux-cpu-1.html)

kernel/mm/slab.md

+249-6
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
* 不同对象划分为 **高速缓存组**,其中每个高速缓存组都存放 **不同类型** 的对象。**每种对象类型** 对应一个高速缓存。
1616
* `kmalloc()`接口建立在slab层之上,使用了一组通用高速缓存。
1717
* 高速缓存被划分为 **slab**,slab由一个或多个物理上连续的页组成。
18-
* 一般情况下,slab也就仅仅由一页组成。每个高速缓存可以由多个slab组成
19-
* 每个slab都包含一些被缓存的数据结构
20-
* 每个slab处于三种状态之一:满,部分满,空。
18+
* 一般情况下,slab 也就仅仅由一页组成。每个高速缓存可以由多个 slab 组成
19+
* 每个 slab 都包含一些被缓存的数据结构
20+
* 每个 slab 处于三种状态之一:满,部分使用,空。
2121
* 当内核某一部分需要一个新对象时:
22-
* 先从 *部分满* 的slab中进行分配。
23-
* 没有 *部分满*,则从 ** slab中进行分配。
22+
* 先从 *部分使用* 的slab中进行分配。
23+
* 没有 *部分使用*,则从 ** slab中进行分配。
2424
* 没有 ** slab,则创建一个slab。
2525
* 注意 slabs_empty 链表中的 slab 是进行 **回收(reaping)** 的主要备选对象。正是通过此过程,slab 所使用的内存被返回给操作系统供其他用户使用。
2626
* slab 链表中的每个 slab 都是一个连续的内存块(一个或多个连续页),它们被划分成一个个对象。这些对象是从特定缓存中进行分配和释放的基本元素。
@@ -93,10 +93,252 @@ struct kmem_cache {
9393
...__```
9494
```
9595
### Slab 空闲对象数组
96+
#### v3.13 前空闲对象数组
9697
![https://flylib.com/books/4/454/1/html/2/images/04fig08.jpg](pic/slab_bufctl.jpg)
9798
* slab cache 在创建的时候会通过`calculate_slab_order() -> cache_estimate()`计算出`cachep->num`,即每个 slab 可存放的 object 数目
9899
* slab 管理数据除了`struct slab`结构外,还包含一个有`cachep->num`个元素的数组来指示 slab object 的使用情况
100+
* 数组元素只有对应的对象是空闲时才是有意义的,元素的值记录下一个空闲对象的位置,这就好像一个单向的“链表”,*链表头*`slabp->free`来指示
101+
* 如下以表 2->3->0->1 (先不管最后一列)的顺序释放对象时`slab_bufctl(slabp)`数组的变化(以加粗方式突显每次 put 时变化的元素值)
102+
103+
`slab_bufctl(slabp)[n]`\put | init | 2 | 3 | 0 | 1 | 3
104+
----------------------------|------|---|---|---|---|---
105+
`slab_bufctl(slabp)[0]` | 1 | 1 | 1 | **3** | 3 | 3
106+
`slab_bufctl(slabp)[1]` | 2 | 2 | 2 | 2 | **0** | 0
107+
`slab_bufctl(slabp)[2]` | 3 | **END** |END|END|END|END
108+
`slab_bufctl(slabp)[3]` | END |END| **2** | 2 | 2 | **1**
109+
`slabp->free` | END | 2 | 3 | 0 | 1 | 3
110+
111+
* 随后分配对象的时候会以 1->0->3->2 的顺序获得对象
112+
* 这里用数组而不是位图来记录对象的使用情况,原因是要利用 LIFO 来更有效率地使用 CPU cache
113+
* 但也给了 double free 破坏 slab 管理结构的机会,只要出现 double free,这个“链表”会形成环,这块 slab 就被破坏了
114+
* 比如上面的表的最后一列,double free 了第三个对象,形成了 3->1->0->3 的环
115+
* 使能`CONFIG_DEBUG_SLAB_LEAK``CONFIG_DEBUG_SLAB`会在对象被分配的时候将数组元素设置成`BUFCTL_FREE`,我们可以通过在 free 的时候先检测这个值来补获 double free 的第一现场
99116
* 这个数组原来放在 `struct slab`结构之后,着色区之前
117+
#### v3.13 后空闲对象数组组织方式的变化
118+
* 然而这一切在 v3.13 后改变了,准确地说是以下 commit 之后开始发生一些变化
119+
```
120+
commit b1cb0982bdd6f57fed690f796659733350bb2cae
121+
Author: Joonsoo Kim <[email protected]>
122+
Date: Thu Oct 24 10:07:45 2013 +0900
123+
124+
slab: change the management method of free objects of the slab
125+
```
126+
* 简单地说就是,就是把“链表”改成了“栈”,`page->active`指示 *栈顶*
127+
* 如下以表 2->3->0->1 的顺序释放对象时`page->freelist[]`数组的变化(以加粗方式突显每次 put 时变化的元素值)
128+
129+
`page->freelist[n]`\put | init | 2 | 3 | 0 | 1
130+
------------------------|------|---|---|---|---
131+
`page->freelist[0]` | 0 | 0 | 0 | 0 | **1**
132+
`page->freelist[1]` | 1 | 1 | 1 | **0** | 0
133+
`page->freelist[2]` | 2 | 2 | **3** | 3 | 3
134+
`page->freelist[3]` | 3 | **2** | 2 | 2 | 2
135+
`page->active` | 4 | 3 | 2 | 1 | 0
136+
137+
* 随后分配对象的时候会依然是 1->0->3->2 的顺序获得对象
138+
* 然而,这并没有解决 double free 对 slab 管理结构的破坏
139+
* `__free_one()`里对`ac->entry`的检查仅限于连续两次的 free,对于有间隔的 double free 无能为力
140+
* 随后`cache_flusharray()->free_block()->slab_put_obj()``page->active--`更是有可能让`active`发生下溢
141+
* 更严重的是后面`set_free_obj(page, page->active, objnr)`是类似`((freelist_idx_t *)(page->freelist))[page->active] = objnr`的操作,更不知道把`objnr`写到哪里去了!!!
142+
143+
#### v4.6 后空闲对象数组位置的变化
144+
* v4.6 后,空闲数组的位置也发生了变化,准确地说是以下 commit 之后
145+
```
146+
commit b03a017bebc403d40aa53a092e79b3020786537d
147+
Author: Joonsoo Kim <[email protected]>
148+
Date: Tue Mar 15 14:54:50 2016 -0700
149+
150+
mm/slab: introduce new slab management type, OBJFREELIST_SLAB
151+
```
152+
* 这个改变是基于这样的考虑,只有当有空闲对象时才会用到空闲数组,所以我们可以用空闲对象自己来存储这个数组。
153+
* 此后 slab 的管理类型由以下 4 个逻辑决定:
154+
1. 如果管理数组的大小 **小于** 对象的大小,且没有构造函数,它属于`OBJFREELIST_SLAB`
155+
2. 如果管理数组的大小小于 slab 在存放完对象后的剩余空间,它属于`NORMAL_SLAB`,且用剩余空间存储数组
156+
3. 如果`OFF_SLAB`的方式比方式 4) 节省内存,那么它属于`OFF_SLAB`。这种类型需要从其他 cache 分配内存来存储管理数组,所以会有额外的内存消耗。
157+
4. 其他的方式属于`NORMAL_SLAB`. 这种方式使用一个 slab 的专门的内部空间作为管理数组,因此也会有额外的内存消耗。
158+
* 对应的判断在`__kmem_cache_create()`里由`set_objfreelist_slab_cache()``set_off_slab_cache()``set_on_slab_cache()`决定。
159+
* 剩余空间`left_over``calculate_slab_order()->cache_estimate()`里计算。
160+
#### 代码解析
161+
* mm/slab.c
162+
```c
163+
/*
164+
* Get the memory for a slab management obj.
165+
*
166+
* For a slab cache when the slab descriptor is off-slab, the
167+
* slab descriptor can't come from the same cache which is being created,
168+
* Because if it is the case, that means we defer the creation of
169+
* the kmalloc_{dma,}_cache of size sizeof(slab descriptor) to this point.
170+
* And we eventually call down to __kmem_cache_create(), which
171+
* in turn looks up in the kmalloc_{dma,}_caches for the disired-size one.
172+
* This is a "chicken-and-egg" problem.
173+
*
174+
* So the off-slab slab descriptor shall come from the kmalloc_{dma,}_caches,
175+
* which are all initialized during kmem_cache_init().
176+
*/
177+
static void *alloc_slabmgmt(struct kmem_cache *cachep,
178+
struct page *page, int colour_off,
179+
gfp_t local_flags, int nodeid)
180+
{
181+
void *freelist;
182+
void *addr = page_address(page); /*根据 struct page 指针得到该结构所管理的虚拟地址*/
183+
/*slab 的管理信息存储在 struct page里*/
184+
page->s_mem = addr + colour_off; /*该 slab 里的对象起始地址在着色区之后,由 s_mem 成员指出*/
185+
page->active = 0;
186+
187+
if (OBJFREELIST_SLAB(cachep))
188+
/*对于空闲数组在空闲对象里的类型,这里先将空闲数组指针设为空,由后面 cache_init_objs() 处理*/
189+
freelist = NULL;
190+
else if (OFF_SLAB(cachep)) {
191+
/* Slab management obj is off-slab. */
192+
freelist = kmem_cache_alloc_node(cachep->freelist_cache,
193+
local_flags, nodeid);
194+
if (!freelist)
195+
return NULL;
196+
} else {
197+
/*对于空闲数组大于一个对象大小的类型,从分配的边界往回找到偏移空闲数组大小的地址,作为空闲数组指针*/
198+
/* We will use last bytes at the slab for freelist */
199+
freelist = addr + (PAGE_SIZE << cachep->gfporder) -
200+
cachep->freelist_size;
201+
}
202+
203+
return freelist;
204+
}
205+
...
206+
static void cache_init_objs(struct kmem_cache *cachep,
207+
struct page *page)
208+
{
209+
int i;
210+
void *objp;
211+
bool shuffled;
212+
213+
cache_init_objs_debug(cachep, page);
214+
215+
/* Try to randomize the freelist if enabled */
216+
shuffled = shuffle_freelist(cachep, page);
217+
218+
if (!shuffled && OBJFREELIST_SLAB(cachep)) {
219+
/*index_to_obj(cachep, page, cachep->num - 1) 为 slab 最后一个对象。
220+
obj_offset(cachep) 指向对象的起始地址,在 slab debug 开启时会跳过填充区和警戒区。
221+
因此,对于空闲数组在空闲对象里的类型,空闲数组指针指向最后一个对象的起始地址*/
222+
page->freelist = index_to_obj(cachep, page, cachep->num - 1) +
223+
obj_offset(cachep);
224+
}
225+
226+
for (i = 0; i < cachep->num; i++) {
227+
objp = index_to_obj(cachep, page, i);
228+
objp = kasan_init_slab_obj(cachep, objp);
229+
230+
/* constructor could break poison info */
231+
if (DEBUG == 0 && cachep->ctor) {
232+
kasan_unpoison_object_data(cachep, objp);
233+
cachep->ctor(objp);
234+
kasan_poison_object_data(cachep, objp);
235+
}
236+
237+
if (!shuffled)
238+
set_free_obj(page, i, i); /*初始化空闲数组*/
239+
}
240+
}
241+
242+
static void *slab_get_obj(struct kmem_cache *cachep, struct page *page)
243+
{
244+
void *objp;
245+
246+
objp = index_to_obj(cachep, page, get_free_obj(page, page->active));
247+
page->active++;
248+
/*这里只单纯地返回 object,调整链表的事留给 fixup_slab_list()*/
249+
return objp;
250+
}
251+
static void slab_put_obj(struct kmem_cache *cachep,
252+
struct page *page, void *objp)
253+
{
254+
unsigned int objnr = obj_to_index(cachep, page, objp);
255+
#if DEBUG
256+
unsigned int i;
257+
258+
/* Verify double free bug */
259+
for (i = page->active; i < cachep->num; i++) {
260+
if (get_free_obj(page, i) == objnr) {
261+
pr_err("slab: double free detected in cache '%s', objp %px\n",
262+
cachep->name, objp);
263+
BUG();
264+
}
265+
}
266+
#endif
267+
page->active--;
268+
if (!page->freelist)
269+
/*只有 OBJFREELIST_SLAB 类型的 slab 才会有空的 freelist 域,
270+
此条件下 objp 指向最后一个空闲对象,所以用来存放空闲数组*/
271+
page->freelist = objp + obj_offset(cachep);
272+
273+
set_free_obj(page, page->active, objnr);
274+
}
275+
276+
/*
277+
* Map pages beginning at addr to the given cache and slab. This is required
278+
* for the slab allocator to be able to lookup the cache and slab of a
279+
* virtual address for kfree, ksize, and slab debugging.
280+
*/
281+
static void slab_map_pages(struct kmem_cache *cache, struct page *page,
282+
void *freelist)
283+
{
284+
page->slab_cache = cache;
285+
page->freelist = freelist; /*对于 OBJFREELIST_SLAB 类型的 slab,freelist 这里还是空*/
286+
}
287+
288+
/*
289+
* Grow (by 1) the number of slabs within a cache. This is called by
290+
* kmem_cache_alloc() when there are no active objs left in a cache.
291+
*/
292+
static struct page *cache_grow_begin(struct kmem_cache *cachep,
293+
gfp_t flags, int nodeid)
294+
{
295+
void *freelist;
296+
size_t offset;
297+
gfp_t local_flags;
298+
int page_node;
299+
struct kmem_cache_node *n;
300+
struct page *page;
301+
...
302+
/* Get slab management. */
303+
freelist = alloc_slabmgmt(cachep, page, offset,
304+
local_flags & ~GFP_CONSTRAINT_MASK, page_node);
305+
if (OFF_SLAB(cachep) && !freelist)
306+
goto opps1; /*管理数据在外部的 slab,freelist 是额外分配的,为空表示分配不成功*/
307+
308+
slab_map_pages(cachep, page, freelist);
309+
310+
cache_init_objs(cachep, page);
311+
...
312+
return page;
313+
314+
opps1:
315+
...
316+
return NULL;
317+
}
318+
...
319+
/*slab_get_obj() 往往和 fixup_slab_list() 一起出现,该函数判断 slab 需不需要调整到 full
320+
或者 partial 链表*/
321+
static inline void fixup_slab_list(struct kmem_cache *cachep,
322+
struct kmem_cache_node *n, struct page *page,
323+
void **list)
324+
{
325+
/* move slabp to correct slabp list: */
326+
list_del(&page->slab_list);
327+
if (page->active == cachep->num) {
328+
list_add(&page->slab_list, &n->slabs_full);
329+
if (OBJFREELIST_SLAB(cachep)) {
330+
#if DEBUG
331+
...
332+
#endif
333+
/*OBJFREELIST_SLAB 类型的 slab 在满的时候会把 freelist 域置为空,
334+
这就与 slab_put_obj() 那个判断对上了*/
335+
page->freelist = NULL;
336+
}
337+
} else
338+
list_add(&page->slab_list, &n->slabs_partial);
339+
}
340+
```
341+
100342
### 三个 slab 链表和 Per-CPU 的 array_cache
101343
102344
* mm/slab.h
@@ -168,7 +410,7 @@ struct kmem_cache_node {
168410
* slab 层的管理是在每个高速缓存的基础上,通过提供给整个系统一个简单的接口来完成的。通过接口就可以:
169411
* 创建和撤销新的高速缓存。
170412
* 在高速缓存内分配和释放对象。
171-
* 创建一个高速缓存后,slab层所起的作用就像一个专用的分配器,可以为具体的对象类型进行分配。
413+
* 创建一个高速缓存后,slab 层所起的作用就像一个专用的分配器,可以为具体的对象类型进行分配。
172414
#### array_cache
173415
* 每个 slab 会建立一个 Per-CPU 的`array_cache``kmem_cache``cpu_cache`域指向这个Per-CPU变量
174416
* 每个 Per-CPU 变量`array_cache`里又含有一个数组,数组有若干条目,指向该 slab 的对象
@@ -328,5 +570,6 @@ static __always_inline void __SetPageSlab(struct page *page)
328570
* [`slbtop`](http://man7.org/linux/man-pages/man1/slabtop.1.html)
329571

330572
# 参考资料
573+
* [Linux slab 分配器剖析](https://www.ibm.com/developerworks/cn/linux/l-linux-slab-allocator/)
331574
* [The Slab Allocator in the Linux kernel](https://hammertux.github.io/slab-allocator)
332575
* [Section 4.4. Slab Allocator _ The Linux Kernel Primer. A Top-Down Approach for x86 and PowerPC Architectures](https://flylib.com/books/en/4.454.1.55/1/)

kernel/time.md

+5
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,10 @@
211211
* **RTC**,用来持久存放系统时间的设备,系统关闭后也可靠主板上的微型电池供电,保持系统的计时。
212212
* PC架构中,RTC与CMOS集成在一起,RTC的运行与BIOS的保存设置都是通过同一电池供电。
213213
* 系统启动时,内核通过读取RTC来初始化墙上时间,该时间存放在`xtime`变量中。这是它最主要的作用。
214+
* 可以通过`ioctl`来让 RTC 产生以下几种类型的 RTC 中断
215+
* `RTC_AIE_ON` 支持定时器功能的 RTC 在指定时间产生 RTC 中断
216+
* `RTC_UIE_ON` 每秒产生一次 RTC 中断
217+
* `RTC_PIE_ON` 按指定周期产生 RTC 中断
214218

215219
## 系统定时器
216220
* 系统定时器的根本思想——提供一种周期性触发中断机制
@@ -1468,3 +1472,4 @@ Collection: active
14681472
- [Linux时间子系统之七:定时器的应用--msleep(),hrtimer_nanosleep()](http://blog.csdn.net/DroidPhone/article/details/8104433)
14691473
- [Linux时间子系统之八:动态时钟框架(CONFIG_NO_HZ、tickless)](http://blog.csdn.net/DroidPhone/article/details/8112948)
14701474
- [Linux时间子系统之(十五):clocksource](http://www.wowotech.net/timer_subsystem/clocksource.html)
1475+
- [rtc(4) - Linux manual page](https://man7.org/linux/man-pages/man4/rtc.4.html)

0 commit comments

Comments
 (0)