Memory management
===
記憶體是 Kernel 裡最複雜的一個章節,就讓我們一起體會他的奧妙
前面的介紹著重在 physical memory 是如何管理,後面則是 virtual kernel memory space 的記憶體管理,virtual user space 會在另外的章節提到
# NUMA
首先, memory 有分為 uniform memory access(UMA) 跟 non-uniform memory access(NUMA)兩種
UMA: 每個 processor accesses memory 的速度都是一樣的
NUMA: 每個 processor accesses memory 的速度不一樣,local 的比較快,以下圖為例
![](https://www.motioncontroltips.com/wp-content/uploads/2018/04/NUMA-Architecture.png)圖[1]
# Node
根據圖[1],把每個 memory 當成一個 node,這樣 UMA 只有一個 node,但是 NUMA 有四個
# Zone
# Node and Zone Initialization
## build_zonelists()
當我們 allocate memory,首先要先找 node,然後找 zone,最後透過 buddy system 找到 page。
node 透過 `NODE_DATA(nid)` 可以得知,那怎麼找到特定的 zone?
首先,Zone 分為以下的 type
```c=
enum zone_type {
#ifdef CONFIG_ZONE_DMA
ZONE_DMA,
#endif
#ifdef CONFIG_ZONE_DMA32
ZONE_DMA32,
#endif
ZONE_NORMAL,
#ifdef CONFIG_HIGHMEM
ZONE_HIGHMEM,
#endif
ZONE_MOVABLE,
MAX_NR_ZONES
};
```
每個 type 都會建立一個 `struct zone`,在 allocate memory 的時候,需要先知道我們想要的 memory type 是哪一種,然後就去對應的 zone 裡面開始找,找的時候要有個順序對照,要是在第一個 zone 找不到,還可以去下個 zone 找,這個順序就存在每個 `struct zone` 的 `zonelist->zones`裡面,`build_zonelists`會為所有的 zone 建立這個順序
Example
---
假設系統有 4 個 nodes ,分別叫做 A, B, C, D, zone_type 則有 highmem, normal, dma 三個 types
那麼以下是 node C 裡各別 zone 的 `zonelist->zones`
highmem:
| C2 | C1 | C0 | D2 | D1 | D0 | A2 | A1 | A0 | B2 | B1 | B0 | NULL |
| -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | ---- |
normal:
| C1 | C0 | D1 | D0 | A1 | A0 | B1 | B0 | NULL |
| -- | -- | -- | -- | -- | -- | -- | -- | ---- |
DMA:
| C0 | D0 | A0 | B0 | NULL |
| -- | -- | -- | -- | ---- |
# slab
kmem_cache_create 並不會建立 slab,只會建立一個 cache 的空殼,array_cache 裡也沒有可以用的 object!
## kmalloc
一開始用了 `__builtin_constant_p` 檢查`size`是不是 constant,是的話透過 MACRO 計算需要的 size,接著呼叫 `kmem_cache_alloc`,不是的話就呼叫 `__kmalloc`,最後還是會做一樣的檢查,不過是透過迴圈
`size`是 constant 時:
```c=
static inline void *kmalloc(size_t size, gfp_t flags)
{
if (__builtin_constant_p(size)) {
int i = 0;
#define CACHE(x) \
if (size <= x) \
goto found; \
else \
i++;
#include "kmalloc_sizes.h"
#undef CACHE
return kmem_cache_alloc(malloc_sizes[i].cs_cachep, flags);
}
```
`size`不是 constant 時:
```c=
static inline struct kmem_cache *__find_general_cachep(size_t size,
gfp_t gfpflags)
{
struct cache_sizes *csizep = malloc_sizes;
...
while (size > csizep->cs_size)
csizep++;
...
}
```
雖然都做一樣的事情,但是一個是在 compiler time 做檢查,令一個是在 execution time
# 有趣的程式碼
## Division of Address Space
`inline function` compiler 可以做最佳化,以以下例子為例
```c=
static __always_inline unsigned long fix_to_virt(const unsigned int idx) {
if (idx >= __end_of_fixed_addresses)
__this_fixmap_does_not_exist();
return __fix_to_virt(idx);
}
```
最佳化後 `if` branch 會被拿掉,因為全部都是 `constant`
virtual address space 可以先分為兩大類:
- kernel address
- user address
| kernel space |
| ------------ |
| user space |
其中 `kernel address`再分兩種
- kernel logical address
這塊空間是透過 direct mapping,而且是 contiguous,適用 DMA
如果 physical memory 少於1G,那麼全部的 `kernel address` 都是 `kernel logical address`,大於 1G 的話,就會有 `kernel virtual address`
- kernel virtual address
這就不是 contiguous
一般而言, kernel space 和 user space 的比例是 1:3,也有 2:2 的,像是手機,原因是 camera 需要大塊的 buffer
# 問題
## void pointer arithmetics
in slab.c
```c=
static struct slab *alloc_slabmgmt(struct kmem_cache *cachep, void *objp,
int colour_off, gfp_t local_flags,
int nodeid)
{
...
slabp = objp + colour_off;
...
}
```
void pointer `objp` does add arithmetic!
Is this correct?
## functions who invokes cache_estimate
呼叫 `cache_estimate` 的 function,都會檢查 `num`,如果 num == 0 就繼續呼叫,但是在 `cache_estimate` 裡面一定會給 num 一個值,不可能是 0,表示 order 0 的時候就會結束迴圈,這樣何必寫一個迴圈呢?程式碼如下
```c=
static void cache_estimate(unsigned long gfporder, size_t buffer_size,
size_t align, int flags, size_t *left_over,
unsigned int *num);
void __init kmem_cache_init(void) {
...
for (order = 0; order < MAX_ORDER; order++) {
cache_estimate(order, cache_cache.buffer_size,
cache_line_size(), 0, &left_over, &cache_cache.num);
if (cache_cache.num)
break;
}
...
}
```
# Reference
[1] https://www.motioncontroltips.com/wp-content/uploads/2018/04/NUMA-Architecture.png