From 973cc2dda26589142b225978352d90b5645b1b60 Mon Sep 17 00:00:00 2001 From: Cheng Jian Date: Fri, 26 Feb 2021 22:23:44 +0800 Subject: [PATCH] memory/buddy: 02-initialization --- .../04-buddy/02-initialization/README.md | 176 +++++++++++++++++- .../04-buddy/04-alloc_page/README.md | 151 ++++++++++----- 2 files changed, 275 insertions(+), 52 deletions(-) mode change 100644 => 100755 study/kernel/02-memory/04-buddy/02-initialization/README.md diff --git a/study/kernel/02-memory/04-buddy/02-initialization/README.md b/study/kernel/02-memory/04-buddy/02-initialization/README.md old mode 100644 new mode 100755 index 1ee9901..23568d6 --- a/study/kernel/02-memory/04-buddy/02-initialization/README.md +++ b/study/kernel/02-memory/04-buddy/02-initialization/README.md @@ -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. + +很明显, 内核使用的是第二种方式, 不管是那种方式, 其实算法思想是一样的, 都是贪心, 只是实现上不一样. diff --git a/study/kernel/02-memory/04-buddy/04-alloc_page/README.md b/study/kernel/02-memory/04-buddy/04-alloc_page/README.md index cfc1504..83b17c7 100644 --- a/study/kernel/02-memory/04-buddy/04-alloc_page/README.md +++ b/study/kernel/02-memory/04-buddy/04-alloc_page/README.md @@ -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)
[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)
[__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)
[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)
[`__get_free_page(mask)`](http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7#L500) | 工作方式与上述函数相同, 但返回分配内存块的虚拟地址, 而不是page实例
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`实例计算相关页的线性内存地址即可. 对高端内存页这是有问题的 @@ -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)
[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 函数源代码注释 -------