本贴主要讲述如何构建 BiscuitOS 或 Linux 上的 GFP_ZONE_TABLE 表.
当在内核中分配内存的时候,必须指定分配的标志,如:
kmalloc(size,gfp)
kmem_cache_alloc(cache,gfp)
内核通过 gfp 标志来判断从哪个区间分配内存,内核将物理内存按区间分配,在 Linux 中存在 ZONE_DMA,ZONE_NORMAL,ZONE_DMA32 和 ZONE_HIGHMEM 几个 zone.
每个 zone 管理各自内存,调用者只要使用 gfp 标志就可以从不同的 gfp 区间获得内存.
内核使用 gfp_zone(gfp) 函数来将 gfp 标志转换为对应的 zone.其实现如下:
-
static inline enum zone_type gfp_zone(gfp_t flags)
-
{
-
enum zone_type z;
-
int bit = (int)(flags & GFP_ZONEMASK);
-
-
z = (GFP_ZONE_TABLE >> (bit * ZONES_SHIFT)) &
-
((1 << ZONES_SHIFT) - 1);
-
-
if(__builtin_constant_p(bit))
-
BUILD_BUG_ON((GFP_ZONE_BAD >> bit) & 1);
-
else {
-
#ifdef CONFIG_DEBUG_VM
-
BUG_ON((GFP_ZONE_BAD >> bit) & 1);
-
#endif
-
}
-
return z;
-
}
从上面代码实现过程中可以看出这个转换依赖 GFP_ZONE_TABLE 表.其定义如下:
#define GFP_ZONE_TABLE ( \
(ZONE_NORMAL << 0 * ZONES_SHIFT) \
| (OPT_ZONE_DMA << ___GFP_DMA * ZONES_SHIFT) \
| (OPT_ZONE_HIGHMEM << ___GFP_HIGHMEM * ZONES_SHIFT) \
| (OPT_ZONE_DMA32 << ___GFP_DMA32 * ZONES_SHIFT) \
| (ZONE_NORMAL << ___GFP_MOVABLE * ZONES_SHIFT) \
| (OPT_ZONE_DMA << (___GFP_MOVABLE | ___GFP_DMA) * ZONES_SHIFT) \
| (ZONE_MOVABLE << (___GFP_MOVABLE | ___GFP_HIGHMEM) * ZONES_SHIFT) \
| (OPT_ZONE_DMA32 << (___GFP_MOVABLE | ___GFP_DMA32) * ZONES_SHIFT) \
)
以及另外一个表 GFP_ZONE_BAD.
#define GFP_ZONE_BAD ( \
1 << (___GFP_DMA | ___GFP_HIGHMEM) \
| 1 << (___GFP_DMA | ___GFP_DMA32) \
| 1 << (___GFP_DMA32 | ___GFP_HIGHMEM) \
| 1 << (___GFP_DMA | ___GFP_DMA32 | ___GFP_HIGHMEM) \
| 1 << (___GFP_MOVABLE | ___GFP_HIGHMEM | ___GFP_DMA) \
| 1 << (___GFP_MOVABLE | ___GFP_DMA32 | ___GFP_DMA) \
| 1 << (___GFP_MOVABLE | ___GFP_DMA32 | ___GFP_HIGHMEM) \
| 1 << (___GFP_MOVABLE | ___GFP_DMA32 | ___GFP_DMA | ___GFP_HIGHMEM) \
)
内核是如何构建这两张表?本贴重点讲述内核如何构建这两张表.
首先内核将一个节点分作不同的管理区,每个管理区使用 struct zone 进行管理,内核在 zone 基本分为:
ZONE_DMA,ZONE_NORMAL,ZONE_DMA32 和 ZONE_HIGHMEM,每个管理区使用 gfp 标志表示为:
#define ___GFP_DMA 0x01u
#define ___GFP_HIGHMEM 0x02u
#define ___GFP_DMA32 0x04u
#define ___GFP_MOVABLE 0x08u
在 gfp 标志中低 3 位来表示内存从哪个 zone 获得,其掩码为:
#define GFP_ZONEMASK (__GFP_DMA | __GFP_HIGHMEM | __GFP_DMA32 | __GFP_MOVABLE)
内核规定 ___GFP_DMA,___GFP_HIGHMEM 和 ___GFP_DMA32 其两个或全部不能同时存在于 gfp 标志中.
四个标志排列组合后可以获得下表:
序号
|
___GFP_DMA
|
___GFP_HIGHMEM
|
___GFP_DMA32
|
___GFP_MOVABLE
|
组合结果
|
0
|
0
|
0
|
0
|
0
|
从 ZONE_NORMAL 中分配
|
1
|
1
|
0
|
0
|
0
|
从 ZONE_NORMAL 或
ZONE_DMA 中分配
|
2
|
0
|
1
|
0
|
0
|
从 ZONE_NORMAL 或
ZONE_HIGHMEM 中分配
|
3
|
1
|
1
|
0
|
0
|
不能同时满足,错误
|
4
|
0
|
0
|
1
|
0
|
从 ZONE_NORMAL 或
ZONE_DMA32 中分配
|
5
|
1
|
0
|
1
|
0
|
不能同时满足,错误
|
6
|
0
|
1
|
1
|
0
|
不能同时满足,错误
|
7
|
1
|
1
|
1
|
0
|
不能同时满足,错误
|
8
|
0
|
0
|
0
|
1
|
从 ZONE_NORMAL 或
ZONE_MOVABLE 获得
|
9
|
1
|
0
|
0
|
1
|
从 ZONE_NORMAL 或
(ZONE_DMA + ZONE_MOVALE) 获得
|
a
|
0
|
1
|
0
|
1
|
从 ZONE_MOVABLE 获得
|
b
|
1
|
1
|
0
|
1
|
不能同时满足,错误
|
c
|
0
|
0
|
1
|
1
|
从 ZONE_DMA
|
d
|
1
|
0
|
1
|
1
|
不能同时满足,错误
|
e
|
0
|
1
|
1
|
1
|
不能同时满足,错误
|
f
|
1
|
1
|
1
|
1
|
不能同时满足,错误
|
从上面的表很容易构建 GFP_ZONE_BAD,将所有错误情况或起来就行.
1. (___GFP_DMA | ___GFP_HIGHMEM)
2. (___GFP_DMA | ___GFP_DMA32)
3. (___GFP_DMA32 | ___GFP_HIGHMEM )
4. (___GFP_DMA32 | ___GFP_HIGHMEM | ___GFP_DMA)
5. (___GFP_DMA | ___GFP_HIGHMEM | ___GFP_MOVABLE )
6. (___GFP_DMA | ___GFP_DMA32 | ___GFP_MOVABLE)
7. (___GFP_DMA32 | ___GFP_HIGHMEM | ___GFP_MOVABLE)
8. (___GFP_DMA32 | ___GFP_DMA32 | ___GFP_HIGHMEM | ___GFP_MOVABLE)
将上面 8 种情况合成 BAD TABLE 如下:
#define GFP_ZONE_BAD ( \
1 << (___GFP_DMA | ___GFP_HIGHMEM) \
| 1 << (___GFP_DMA | ___GFP_DMA32) \
| 1 << (___GFP_DMA32 | ___GFP_HIGHMEM) \
| 1 << (___GFP_DMA | ___GFP_DMA32 | ___GFP_HIGHMEM) \
| 1 << (___GFP_MOVABLE | ___GFP_HIGHMEM | ___GFP_DMA) \
| 1 << (___GFP_MOVABLE | ___GFP_DMA32 | ___GFP_DMA) \
| 1 << (___GFP_MOVABLE | ___GFP_DMA32 | ___GFP_HIGHMEM) \
| 1 << (___GFP_MOVABLE | ___GFP_DMA32 | ___GFP_DMA | ___GFP_HIGHMEM) \
)
剩下的 8 种情况就是可以分配.分别为:
1. 0
2. (___GFP_DMA)
3. (___GFP_HIGHMEM)
4. (___GFP_DMA32)
5. (___GFP_MOVABLE)
6. (___GFP_DMA | ___GFP_MOVABLE)
7. (___GFP_HIGHMEM | ___GFP_MOVABLE)
8. (___GFP_DMA32 | ___GFP_MOVABLE)
构建初期表为
\)
#define TABLE ( \
(1 << 0) \
| (1<< ___GFP_DMA) \
| (1 << ___GFP_HIGHMEM) \
| (1 << ___GFP_DMA32) \
| (1 << ___GFP_MOVABLE) \
| (1 << ___GFP_DMA | ___GFP_MOVABLE) \
| (1 << ___GFP_HIGHMEM | ___GFP_MOVABLE) \
| (1 << ___GFP_DMA32 | ___GFP_MOVABLE) \
)将对应的 zone 填充进去,其中
OPT_ZONE_DMA 代表 ___GFP_NORMAL 或者 ___GFP_NORMAL
OPT_ZONE_HGIHMEM 代表 ___GFP_NORMAL 或者 ___GFP_HIGHMEM
OPT_ZONE_DMA32 代表 ___GFP_NORMAL 或者 ___GFP_DMA32
根据表的分析,可获得下面结论:
#define TABLE ( \
(ZONE_NORMAL << 0) \
| (OPT_ZONE_DMA << ___GFP_DMA) \
| (OPT_ZONE_HIGHMEM << ___GFP_HIGHMEM) \
| (OPT_ZONE_DMA32 << ___GFP_DMA32) \
| (ZONE_NORMAL << ___GFP_MOVABLE) \
| (OPT_ZONE_DMA << ___GFP_DMA | ___GFP_MOVABLE) \
| (ZONE_MOVABLE << ___GFP_HIGHMEM | ___GFP_MOVABLE) \
| (OPT_ZONE_DMA32 << ___GFP_DMA32 | ___GFP_MOVABLE) \
)
由于不同的平台会使用不同数量的 zone 管理区,常见的 zone 分配为 ZONE_NORMAL 和 ZONE_HIGHMEM 搭配.
于是内核将 TABLE 的位宽使用 ZONES_SHIFT 表示,如一个具有 ZONE_DMA,ZONE_DMA32,ZONE_NORMAL 和 ZONE_HIGHMEM 的平台
ZONES_SHIFT 为 2,表示 GFP_ZONE_TABLE 每个选项的位宽为 2.于是最终的表为:
#define GFP_ZONE_TABLE ( \
(ZONE_NORMAL << 0 * ZONES_SHIFT) \
| (OPT_ZONE_DMA << ___GFP_DMA * ZONES_SHIFT) \
| (OPT_ZONE_HIGHMEM << ___GFP_HIGHMEM * ZONES_SHIFT) \
| (OPT_ZONE_DMA32 << ___GFP_DMA32 * ZONES_SHIFT) \
| (ZONE_NORMAL << ___GFP_MOVABLE * ZONES_SHIFT) \
| (OPT_ZONE_DMA << (___GFP_MOVABLE | ___GFP_DMA) * ZONES_SHIFT) \
| (ZONE_MOVABLE << (___GFP_MOVABLE | ___GFP_HIGHMEM) * ZONES_SHIFT) \
| (OPT_ZONE_DMA32 << (___GFP_MOVABLE | ___GFP_DMA32) * ZONES_SHIFT) \
)