memory/buddy: 02-initialization

This commit is contained in:
Cheng Jian
2021-02-28 00:34:50 +08:00
committed by gatieme
parent e4329f5a99
commit 973cc2dda2
2 changed files with 275 additions and 52 deletions
+168 -8
View File
@@ -11,10 +11,12 @@
------
##1.1 启动阶段的内存初始化
##1.1 启动阶段的物理内存初始化
-------
之前我们讲解了系统内存管理初始化的第二阶段(buddy的初始化), 但是我们讲解的很粗糙, 我们仅仅讲解了内存管理的主要流程创建
之前我们讲解了系统内存管理初始化的时候, 有简单提过第二阶段(buddy的初始化), 但是我们讲解的很粗糙, 我们仅仅讲解了内存管理的主要流程创建
```cpp
@@ -44,14 +46,20 @@ start_kernel()
|---->zone_sizes_init(min, max);
|
|---->free_area_init_node
|
|---->free_area_init
| ( MEM NODE)
|
|---->free_area_init_core
| zone
---->free_area_init_node(int nid)
| nid
|
|---->memmap_init
| page页面
|---->free_area_init_core(pgdat);
ZONE( ZONE)
|
|---->usemap_size
| ZONE pageblock_flags
|
|---->memmap_init
| page页面
|
|---->memblock_dump_all();
| , memblock的保留的所有内存信息
@@ -103,13 +111,165 @@ start_kernel()
##1.2 内存节点的初始化
-------
```cpp
|---->free_area_init
| ( MEM NODE)
|
---->free_area_init_node(int nid)
| nid
|
|---->free_area_init_core(pgdat);
ZONE( ZONE)
|
|---->usemap_size
| ZONE pageblock_flags
|
|---->memmap_init
| page页面
```
## 1.3 ZONE 初始化
-------
free_area_init 中完成了系统内存的初始化操作.
其中 zone 的初始化由 [`free_area_init_core()`, v5.10](https://elixir.bootlin.com/linux/v5.10/source/mm/page_alloc.c#L6834) 完成.
```cpp
|---->free_area_init_core
| ZONE( ZONE)
|
|---->memmap_pages = calc_memmap_size(size, freesize);
| nid
|
|---->zone_init_internals(zone, j, nid, freesize);
| ZONE( ZONE)
|
|---->set_pageblock_order();
|
|
|---->setup_usemap(pgdat, zone, zone_start_pfn, size);
| page页面
|
|---->init_currently_empty_zone(zone, zone_start_pfn, size);
|
|---->memmap_init(size, nid, j, zone_start_pfn);
```
* for (j = 0; j < MAX_NR_ZONES; j++) 依次遍历当前内存节点 pglist_data 的所有 ZONE
* [`setup_usemap()`](https://elixir.bootlin.com/linux/v5.10/source/mm/page_alloc.c#L6688) 函数中通过 usemap_size() 计算存储 pageblock_flags 所需的内存大小 usemapsize, 并通过 memblock_alloc_node 为其分配空间. 其中 [`usemap_size()`, v5.10](https://elixir.bootlin.com/linux/v5.10/source/mm/page_alloc.c#L6675)
* [`init_currently_empty_zone()`, v5.10](https://elixir.bootlin.com/linux/v5.10/source/mm/page_alloc.c#L6396) 函数完成了 ZONE 初始化的最后操作, 并将 zone->initialized 标记置为 1, 至此 ZONE 的初始化完成了, 其中通过 zone_init_free_lists() 将各个 ORDER 的各个 MIGRATE_TYPES 类型的 BUDDY 都进行了初始化, 将 `free_area->free_list[]` 置为空列表数组, nr_free 置为 0.
* [`memmap_init()`, v5.10](https://elixir.bootlin.com/linux/v5.10/source/mm/page_alloc.c#L6188) 中通过 [`memmap_init_zone()`](https://elixir.bootlin.com/linux/v5.10/source/mm/page_alloc.c#L6051)->[`set_pageblock_migratetype()`](https://elixir.bootlin.com/linux/v5.10/source/mm/page_alloc.c#L572) 设置了每个页面的 MIGRATE_TYPE 类型为 MIGRATE_MOVABLE 可迁移类型.
##1.3 今日内容(buddy的初始化)
-------
至此, 内存节点, ZONE, BUDDY 都已经初始化好了, 但是页面还没有加到伙伴系统 BUDDY 中, 此外可以发现初始化的时候系统所有的内存都是 MIGRATE_MOVABLE 可以迁移的.
那么我们就很好奇了 :
1. BUDDY 伙伴系统的 `free_area->free_list[]` 被初始化为 `[]` 数组了, nr_free 也是 0, 那么物理页面怎么加进来呢 ?
2. 系统初始化所有物理内存的迁移类型都是 MIGRATE_MOVABLE, 那如果想要分配 MIGRATE_UNMOVABLE 不可迁移的或者其他类型的内存, 该怎么搞 ?
真正完成伙伴系统中 `free_list` 初始化工作的是在 `free_low_memory_core_early()` 中,其代码调用图如下 :
```cpp
arch_call_rest_init
rest_init()
kernel_init_freeable()
page_alloc_init_late
memblock_discard
__memblock_free_late
memblock_free_pages
__free_pages_core
__free_pages_ok
free_one_page
__free_one_page
```
```cpp
|---->mem_init
|
|---->memblock_free_all
|
|---->free_low_memory_core_early
|
|---->free_memory_core
|
|--->__free_pages_memory
|
|---->memblock_free_pages
|
|---->free_pages_core
|
|---->__free_pages_ok
|
```
[`free_low_memory_core_early()`, v5.10](https://elixir.bootlin.com/linux/v5.10/source/mm/memblock.c#L1960) 就完成了两个工作:
1. [`reserve_bootmem_region()`](https://elixir.bootlin.com/linux/v5.10/source/mm/page_alloc.c#L1488) 把内核预留的内存 reserve 掉.
2. [`__free_memory_core()`](https://elixir.bootlin.com/linux/v5.10/source/mm/memblock.c#L1945) 通过释放页面的方式把页面归还到 BUDDY 伙伴系统中.
下面详细来看 [`__free_memory_core()`](https://elixir.bootlin.com/linux/v5.10/source/mm/memblock.c#L1945) 函数.
1. 通过 for_each_free_mem_range 遍历所有的内存块, 找到其起始的页帧号 start 和结束的页帧号 end
2. 对 [start, end) 的每一块内存, 通过 [`__free_pages_memory()`](https://elixir.bootlin.com/linux/v5.10/source/mm/memblock.c#L1929), 这个是 BUDDY 初始化过程中比较关键的函数.
```cpp
// https://elixir.bootlin.com/linux/v5.10/source/mm/memblock.c#L1929
static void __init __free_pages_memory(unsigned long start, unsigned long end)
{
int order;
while (start < end) {
// 每次计算出当前能归还的最大内存块的 order
order = min(MAX_ORDER - 1UL, __ffs(start));
while (start + (1UL << order) > end)
order--;
// 将这块 2^order 个 PAGE 归还到 BUDDY
memblock_free_pages(pfn_to_page(start), start, order);
// [start, start + 1 << order)
start += (1UL << order);
}
}
```
这段代码还是比较容易理解的, 我们要把页帧号 [start, end) 区域内的内存都归还给 BUDDY 伙伴系统, 而伙伴系统最大的内存块包含 2^(MAX_ORDER - 1) 个 PAGE, 那么我们要归还的 [start, end) 可能会被分类成多个不同或者 ORDER 的内存块放回到伙伴系统中. 因此使用贪心算法:
1. 每次计算出当前能归还的最大内存块的 order.
2. 通过 memblock_free_pages 将这块包含 2^order 个 PAGE 的内存块归还到伙伴系统, 归还的区域为 [start, start + 1 << order)
3. 将归还的内存从当前待归还区间抛弃, 继续执行流程 1.
循环往复, 这段区域的内存就被不断拆成一块块 2^order 的内存块, 并归还到伙伴系统中.
计算 [start, end) 能归还的最大的 order 的时候内核用了一个巧妙的方式, 我们要知道一个 size 的内存块包含了多少个 2^order 的 PAGE, 其实就是找到 size 其二进制位最高位 1 出现的位置. 那么对应的有两种方式来算:
1. 从 size 入手, 每次 end - start, 得到 size, 计算出 size 最高位 1 出现的位置, 就是当前能归还的最大 ORDER.
2. 从 start 入手, 这时候假设 end 无限大, 算出 [start, ∞) 能归还的最大的 order( start 高位的 1 出现的位置), 然后接着不断缩小 order, 保证 start + (1UL << order) 范围不超出 end.
很明显, 内核使用的是第二种方式, 不管是那种方式, 其实算法思想是一样的, 都是贪心, 只是实现上不一样.
@@ -6,7 +6,11 @@
| 2016-06-14 | [Linux-4.7](http://lxr.free-electrons.com/source/?v=4.7) | X86 & arm | [gatieme](http://blog.csdn.net/gatieme) | [LinuxDeviceDrivers](https://github.com/gatieme/LDD-LinuxDeviceDrivers) | [Linux内存管理](http://blog.csdn.net/gatieme/article/category/6393814) |
# 1 页面分配
-------
## 1.1 页面分配概述
-------
在内核初始化完成之后, 内存管理的责任就由伙伴系统来承担. 伙伴系统基于一种相对简单然而令人吃惊的强大算法.
@@ -23,32 +27,28 @@ Linux内核使用二进制伙伴算法来管理和分配物理内存页面, 该
* 内存碎片的问题和分配器如何处理碎片
#内存分配API
-------
##2.1 内存分配器API
## 1.2 内存分配 API
-------
就伙伴系统的接口而言, NUMA 或 UMA 体系结构是没有差别的, 二者的调用语法都是相同的.
就伙伴系统的接口而言, NUMA或UMA体系结构是没有差别的, 二者的调用语法都是相同的.
所有函数的一个共同点是 : 只能分配 2 的整数幂个页.
所有函数的一个共同点是 : 只能分配2的整数幂个页.
因此,接口中不像C标准库的malloc函数或bootmem和memblock分配器那样指定了所需内存大小作为参数. 相反, 必须指定的是分配阶, 伙伴系统将在内存中分配$2^order$页. 内核中细粒度的分配只能借助于slab分配器(或者slub、slob分配器), 后者基于伙伴系统
因此, 接口中不像C标准库的 malloc 函数或 bootmem 和 memblock 分配器那样指定了所需内存大小作为参数. 相反, 必须指定的是分配阶, 伙伴系统将在内存中分配 $2^order$ 页. 内核中细粒度的分配只能借助于 slab 分配器(或者 slub、slob 分配器), 后者基于伙伴系统
| 内存分配函数 | 功能 | 定义 |
|:-----:|:-----:|
| alloc_pages(mask, order) | 分配$2^order$并返回一个struct page的实例表示分配的内存块的起始页 | [NUMA-include/linux/gfp.h, line 466](http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L466)<br>[UMA-include/linux/gfp.h?v=4.7, line 476](http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L476) |
| alloc_page(mask) | 是前者在order = 0情况下的简化形式,只分配一页 | [include/linux/gfp.h?v=4.7, line 483](http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L483) |
| get_zeroed_page(mask) | 分配一页并返回一个page实例,页对应的内存填充0(所有其他函数,分配之后页的内容是未定义的) | [mm/page_alloc.c?v=4.7, line 3900](http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L3900)| |
| [__get_free_pages(mask, order)](http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L3883)<br>[__get_free_page(mask)](http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L500) | 工作方式与上述函数相同,但返回分配内存块的虚拟地址,而不是page实例 |
| get_dma_pages(gfp_mask, order) | 用来获得适用于DMA的页. | [include/linux/gfp.h?v=4.7, line 503](http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L503) |
| alloc_pages(mask, order) | 分配$2^order$ 个连续的物理页面, 并返回一个页面的 struct page 的实例, 表示分配的内存块的起始页 | [NUMA-include/linux/gfp.h, line 466](http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L466)<br>[UMA-include/linux/gfp.h?v=4.7, line 476](http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L476) |
| [`__get_free_pages(mask, order)`](http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L3883)<br>[`__get_free_page(mask)`](http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L500) | 工作方式与上述函数相同, 但返回分配内存块的虚拟地址, 而不是page实例<br>32 位系统中, 该函数不会使用高端内存, 如果一定要使用高端内存, 最佳的办法是使用 alloc_pages 和 kmap 函数. |
| alloc_page(mask) | 是前者在order = 0情况下的简化形式, 只分配一页 | [include/linux/gfp.h?v=4.7, line 483](http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L483) |
| `__get_free_page(gfp_mask)` | 是 `__get_free_pages``order = 0` 情况下的简化形式, 只分配一页 | [include/linux/gfp.h?v=4.7, line 483](http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L483) |
| get_zeroed_ page(mask) | 分配一页并返回一个page实例, 页对应的内存填充0(所有其他函数, 分配之后页的内容是未定义的) | [mm/page_alloc.c?v=4.7, line 3900](http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L3900)| |
| get_dma_pages(gfp_mask, order) | 用来获得适用于DMA的页. | [`include/linux/gfp.h?v=4.7, line 503`](http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L503) |
在空闲内存无法满足请求以至于分配失败的情况下所有上述函数都返回空指针(比如alloc_pages和alloc_page)或者0(比如get_zeroed_page__get_free_pages__get_free_page).
在空闲内存无法满足请求以至于分配失败的情况下, 所有上述函数都返回空指针(比如alloc_pages和alloc_page)或者0(比如 `get_zeroed_page``__get_free_pages``__get_free_page`).
因此内核在各次分配之后都必须检查返回的结果. 这种惯例与设计得很好的用户层应用程序没什么不同, 但在内核中忽略检查会导致严重得多的故障
@@ -57,13 +57,57 @@ Linux内核使用二进制伙伴算法来管理和分配物理内存页面, 该
还有一组kmalloc类型的函数, 用于分配小于一整页的内存区. 其实现将在以后分别讨论。
##2.2 内存分配API统一到alloc_pages接口
# 2 alloc_page
-------
通过使用标志、内存域修饰符和各个分配函数,内核提供了一种非常灵活的内存分配体系.尽管如此, 所有接口函数都可以追溯到一个简单的基本函数(alloc_pages_node)
## 2.1 alloc_page 的流程
-------
##2.2 伙伴系统的心脏__alloc_pages_nodemask
-------
内核源代码将`__alloc_pages_nodemask`称之为"伙伴系统的心脏"(`the 'heart' of the zoned buddy allocator``), 因为它处理的是实质性的内存分配.
由于"心脏"的重要性, 我将在下文详细介绍该函数.
`__alloc_pages_nodemask`函数定义在[include/linux/gfp.h?v=4.7#L428](http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L428)
通过使用标志、内存域修饰符和各个分配函数, 内核提供了一种非常灵活的内存分配体系.尽管如此, 所有接口函数都可以追溯到一个简单的基本函数(alloc_pages_node)
```cpp
#ifdef CONFIG_NUMA
extern struct page *alloc_pages_current(gfp_t gfp_mask, unsigned order);
static inline struct page *
alloc_pages(gfp_t gfp_mask, unsigned int order)
{
return alloc_pages_current(gfp_mask, order);
}
extern struct page *alloc_pages_vma(gfp_t gfp_mask, int order,
struct vm_area_struct *vma, unsigned long addr,
int node, bool hugepage);
#define alloc_hugepage_vma(gfp_mask, vma, addr, order) \
alloc_pages_vma(gfp_mask, order, vma, addr, numa_node_id(), true)
#else
static inline struct page *alloc_pages(gfp_t gfp_mask, unsigned int order)
{
return alloc_pages_node(numa_node_id(), gfp_mask, order);
}
#define alloc_pages_vma(gfp_mask, order, vma, addr, node, false)\
alloc_pages(gfp_mask, order)
#define alloc_hugepage_vma(gfp_mask, vma, addr, order) \
alloc_pages(gfp_mask, order)
#endif
```
分配单页的函数[`alloc_page`](http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L483)和[`__get_free_page`](http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L500), 还有[`__get_dma_pages`](http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L503)是借助于宏定义的.
@@ -80,7 +124,7 @@ Linux内核使用二进制伙伴算法来管理和分配物理内存页面, 该
__get_free_pages((gfp_mask) | GFP_DMA, (order))
```
[`get_zeroed_page`](http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L3900)的实现也没什么困难, 对`__get_free_pages`使用`__GFP_ZERO`标志即可分配填充字节0的页. 再返回与页关联的内存区地址即可.
[`get_zeroed_page`](http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L3900)的实现也没什么困难, 对`__get_free_pages`使用`__GFP_ZERO`标志, 即可分配填充字节0的页. 再返回与页关联的内存区地址即可.
```cpp
@@ -117,9 +161,9 @@ unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
EXPORT_SYMBOL(__get_free_pages);
```
在这种情况下 使用了一个普通函数而不是宏 因为`alloc_pages`返回的`page`实例需要使用辅助
在这种情况下, 使用了一个普通函数而不是宏, 因为`alloc_pages`返回的`page`实例需要使用辅助
函数`page_address`转换为内存地址. 在这里只要知道该函数可根据`page`实例计算相关页的线性内存地址即可. 对高端内存页这是有问题的
函数`page_address`转换为内存地址. 在这里, 只要知道该函数可根据`page`实例计算相关页的线性内存地址即可. 对高端内存页这是有问题的
<font color = 0x00ffff>
@@ -136,11 +180,15 @@ EXPORT_SYMBOL(__get_free_pages);
| [arch/x86/include/asm/page_32.h?v=4.7, line 24](http://lxr.free-electrons.com/source/arch/x86/include/asm/page_32.h?v=4.7#L24) | [arch/arm/include/asm/page.h?v=4.7#L14](http://lxr.free-electrons.com/source/arch/arm/include/asm/page.h?v=4.7#L142)<br>[arch/arm/include/asm/page-nommu.h](http://lxr.free-electrons.com/source/arch/arm/include/asm/page-nommu.h?v=4.7#L20) |
##2.2 alloc_pages函数分配页
# 3 alloc_pages函数分配页
-------
## 3.1 alloc_pages 接口实现
-------
既然所有的内存分配API函数都可以追溯掉`alloc_page`函数, 从某种意义上说该函数是伙伴系统主要实现的"发射台".
既然所有的内存分配API函数都可以追溯掉`alloc_page`函数, 从某种意义上说, 该函数是伙伴系统主要实现的"发射台".
`alloc_pages`函数的定义是依赖于NUMA或者UMA架构的, 定义如下
@@ -165,6 +213,19 @@ alloc_pages(gfp_t gfp_mask, unsigned int order)
```
## 3.2 UMA
-------
```cpp
alloc_pages
alloc_pages_node
__alloc_pages_node
__alloc_pages
__alloc_pages_nodemask
```
UMA结构下的`alloc_pages`是通过`alloc_pages_node`函数实现的, 下面我们看看`alloc_pages_node`函数的定义, 在[include/linux/gfp.h?v=4.7, line 448](http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L448)
@@ -222,17 +283,19 @@ __alloc_pages(gfp_t gfp_mask, unsigned int order,
}
```
##2.3 伙伴系统的心脏__alloc_pages_nodemask
## 3.3 NUMA
-------
内核源代码将`__alloc_pages_nodemask`称之为"伙伴系统的心脏"(`the 'heart' of the zoned buddy allocator``), 因为它处理的是实质性的内存分配.
由于"心脏"的重要性, 我将在下文详细介绍该函数.
```cpp
alloc_pages
alloc_pages_current
alloc_page_interleave
__alloc_pages_nodemask
```
`__alloc_pages_nodemask`函数定义在[include/linux/gfp.h?v=4.7#L428](http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L428)
#3 选择页
@@ -260,7 +323,7 @@ enum zone_watermarks {
内核需要定义一些函数使用的标志,用于控制到达各个水印指定的临界状态时的行为, 这些标志用宏来定义, 定义在[mm/internal.h?v=4.7, line 453](http://lxr.free-electrons.com/source/mm/internal.h?v=4.7#L453)
使, , , [mm/internal.h?v=4.7, line 453](http://lxr.free-electrons.com/source/mm/internal.h?v=4.7#L453)
```cpp
/* The ALLOC_WMARK bits are used as an index to zone->watermark */
@@ -361,7 +424,7 @@ bool __zone_watermark_ok(struct zone *z, unsigned int order, unsigned long mark,
return true;
/* For a high-order request, check at least one suitable page is free
* 在下一阶当前阶的页是不可用的 */
* 在下一阶, 当前阶的页是不可用的 */
for (o = order; o < MAX_ORDER; o++) {
struct free_area *area = &z->free_area[o];
int mt;
@@ -396,7 +459,7 @@ bool __zone_watermark_ok(struct zone *z, unsigned int order, unsigned long mark,
free_pages -= zone_page_state(z, NR_FREE_CMA_PAGES);
```
在解释了`ALLOC_HIGH`和`ALLOC_HARDER`标志之后(将最小值标记降低到当前值的一半或四分之一使得分配过程努力或更加努力),
在解释了`ALLOC_HIGH``ALLOC_HARDER`标志之后(将最小值标记降低到当前值的一半或四分之一, 使得分配过程努力或更加努力),
```cpp
if (alloc_flags & ALLOC_HIGH)
min -= min / 2;
@@ -443,7 +506,7 @@ for (o = order; o < MAX_ORDER; o++) {
}
```
如果内核遍历所有的低端内存域之后发现内存不足, 则不进行内存分配.
如果内核遍历所有的低端内存域之后, 发现内存不足, 则不进行内存分配.
@@ -454,7 +517,7 @@ for (o = order; o < MAX_ORDER; o++) {
http://blog.csdn.net/yuzhihui_no1/article/details/50776826
http://bbs.chinaunix.net/thread-3769001-1-1.html
`get_page_from_freelist`是伙伴系统使用的另一个重要的辅助函数. 它通过标志集和分配阶来判断是否能进行分配。如果可以则发起实际的分配操作. 该函数定义在[mm/page_alloc.c?v=4.7, line 2905](http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L2905)
`get_page_from_freelist`是伙伴系统使用的另一个重要的辅助函数. 它通过标志集和分配阶来判断是否能进行分配。如果可以, 则发起实际的分配操作. 该函数定义在[mm/page_alloc.c?v=4.7, line 2905](http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L2905)
这个函数的参数很有意思, 之前的时候这个函数的参数只能用复杂来形容
@@ -504,21 +567,21 @@ struct alloc_context {
| 字段 | 描述 |
|:-----:|:-----:|
| zonelist | 当perferred_zone上没有合适的页可以分配时就要按zonelist中的顺序扫描该zonelist中备用zone列表一个个的试用 |
| nodemask | 表示节点的mask就是是否能在该节点上分配内存这是个bit位数组 |
| preferred_zone | 表示从high_zoneidx后找到的合适的zone一般会从该zone分配;分配失败的话就会在zonelist再找一个preferred_zone = 合适的zone |
| migratetype | 迁移类型在zone->free_area.free_list[XXX] 作为分配下标使用这个是用来反碎片化的修改了以前的free_area结构体在该结构体中再添加了一个数组该数组以迁移类型为下标每个数组元素都挂了对应迁移类型的页链表 |
| high_zoneidx | 是表示该分配时所能分配的最高zone一般从high-->normal-->dma 内存越来越昂贵所以一般从high到dma分配依次分配 |
| zonelist | 当perferred_zone上没有合适的页可以分配时, 就要按zonelist中的顺序扫描该zonelist中备用zone列表, 一个个的试用 |
| nodemask | 表示节点的mask, 就是是否能在该节点上分配内存, 这是个bit位数组 |
| preferred_zone | 表示从high_zoneidx后找到的合适的zone, 一般会从该zone分配;分配失败的话, 就会在zonelist再找一个preferred_zone = 合适的zone |
| migratetype | 迁移类型, 在zone->free_area.free_list[XXX] 作为分配下标使用, 这个是用来反碎片化的, 修改了以前的free_area结构体, 在该结构体中再添加了一个数组, 该数组以迁移类型为下标, 每个数组元素都挂了对应迁移类型的页链表 |
| high_zoneidx | 是表示该分配时, 所能分配的最高zone, 一般从high-->normal-->dma 内存越来越昂贵, 所以一般从high到dma分配依次分配 |
| spread_dirty_pages | |
zonelist是指向备用列表的指针. 在预期内存域没有空闲空间的情况下, 该列表确定了扫描系统其他内存域(和结点)的顺序.
随后的for循环所作的基本上与直觉一致, 遍历备用列表的所有内存域用最简单的方式查找一个适当的空闲内存块
随后的for循环所作的基本上与直觉一致, 遍历备用列表的所有内存域, 用最简单的方式查找一个适当的空闲内存块
* 首先解释ALLOC_*标志(\__cpuset_zone_allowed_softwall是另一个辅助函数, 用于检查给定内存域是否属于该进程允许运行的CPU).
* 首先, 解释ALLOC_*标志(\__cpuset_zone_allowed_softwall是另一个辅助函数, 用于检查给定内存域是否属于该进程允许运行的CPU).
* zone_watermark_ok接下来检查所遍历到的内存域是否有足够的空闲页并试图分配一个连续内存块。如果两个条件之一不能满足即或者没有足够的空闲页或者没有连续内存块可满足分配请求则循环进行到备用列表中的下一个内存域作同样的检查. 直到找到一个合适的页面, 在进行try_this_node进行内存分配
* zone_watermark_ok接下来检查所遍历到的内存域是否有足够的空闲页, 并试图分配一个连续内存块。如果两个条件之一不能满足, 即或者没有足够的空闲页, 或者没有连续内存块可满足分配请求, 则循环进行到备用列表中的下一个内存域, 作同样的检查. 直到找到一个合适的页面, 在进行try_this_node进行内存分配
* 如果内存域适用于当前的分配请求, 那么buffered_rmqueue试图从中分配所需数目的页
@@ -670,7 +733,7 @@ reset_fair:
-------
如前所述, `__alloc_pages_nodemask`是伙伴系统的心脏. 我们已经处理了所有的准备工作并描述了所有可能的标志, 现在我们把注意力转向相对复杂的部分 : 函数`__alloc_pages_nodemask`的实现, 这也是内核中比较冗长的部分
之一. 特别是在可用内存太少或逐渐用完时, 函数就会比较复杂. 如果可用内存足够则必要的工作会很快完成就像下述代码
之一. 特别是在可用内存太少或逐渐用完时, 函数就会比较复杂. 如果可用内存足够, 则必要的工作会很快完成, 就像下述代码
##4.1 函数源代码注释
-------