Linux如何为其物理分配器分配内存?

5

最近我在深入研究Linux的内存管理,因为我想为自己的玩具内核实现类似的功能,所以我希望熟悉细节的人能帮助我理解一件事情。显然,物理内存管理器是一个伙伴算法,进一步专门化为返回特定顺序(0到9,其中0只是一个单独的页面)的页面块。对于每个顺序,块都存储为链接列表。例如,如果请求顺序5的块但未在顺序5的块列表中找到,则算法会在顺序6中搜索块,将其分成两半,给出请求的一半并将另一半移动到更低的顺序(因为它的大小减半了)。

我不明白的是内核如何存储这些结构,或者内核如何为它们分配空间。因为对于顺序0的页面,您需要1M个条目(每个条目是4KiB的页面),这是否意味着内核分配了1MiB * sizeof(struct page)的空间?那么顺序1及以上的块呢?内核是否通过将它们标记为更高的顺序来重用已分配的块,并在需要将其分成两半时返回块并获取未使用的块?


1
一些有用的链接:Buddy http://en.wikipedia.org/wiki/Buddy_memory_allocation,Mel Gorman的书《Understanding the Linux Virtual Memory Manager》-第2章“描述物理内存”https://www.kernel.org/doc/gorman/html/understand/understand005.html和Mauerer的书《Professional Linux Kernel Architecture》,第3.5节“物理内存管理”。 - osgx
我已经通过这些链接进行了研究,但它们并没有回答我的问题,因为它们只是理论,而不是实际实现。我想知道Linux如何设置内存结构,将它们放置在物理内存中,如何为代表页面的这些块保留内存...因为为了将一个块分成两个块,它必须有两个新块来表示它们(或者如果可以重用分裂块,则只需要一个新块)。所有这些理论都没有解释如何实际做到这一点!在内核源代码中没有一个简单的init()函数... - Ivan Stanev
1
请查看《专业 Linux 内核架构》一书,其中所有代码都有详细描述(3.5、3.5.3 - 第209页)。我会尽快发布答案。 - osgx
如果实现简单,例如存储位图,每个位表示一个节点,您可以保留2M-1位,即大约256KiB的内存。子节点将被找到为parent2和parent2 + 1,而子节点的父节点将被找到为child/2。查找伙伴可能只需要确定当前节点位是奇数还是偶数,并添加或减去1以找到伙伴。该映射可以存储在物理范围0x00100000至0x00140000中!但是Linux并不这么简单,节点是结构体,而不是位!我正在努力理解它们在内存中如何操作... - Ivan Stanev
@osgx 好的,我会做! - Ivan Stanev
1个回答

5
我不明白的是内核如何存储这些结构体,或者它如何为它们分配空间。因为对于0级页面,您需要1M个条目(每个条目是4KiB页面),这是否意味着内核分配了1MiB * sizeof(struct page)的空间?

通过调用paging_init()(arch/x86/mm/init_32.c; 一些描述 - https://www.kernel.org/doc/gorman/html/understand/understand005.html 2.3区域初始化和http://repo.hackerzvoice.net/depot_madchat/ebooks/Mem_virtuelle/linux-mm/vminit.html初始化内核页表)从setup_arch()中进行区域初始化,通过(native_pagetable_init()和间接调用1166 x86_init.paging.pagetable_init();):

690 /*
691  * paging_init() sets up the page tables - note that the first 8MB are
692  * already mapped by head.S.
...*/
697 void __init paging_init(void)
698 {
699         pagetable_init();
...
711         zone_sizes_init();
712 }

pagetable_init()swapper_pg_dir 数组的 1024 个 pgd_t 中创建内核页表。

zone_sizes_init() 实际上定义了物理内存的区域,并调用 free_area_init_nodes() 来初始化它们,对于每个 NUMA 节点for_each_online_node(nid) {...}free_area_init_node() 中实际执行,该函数调用了三个函数:

  • calculate_node_totalpages() 打印每个节点在 dmesg 中的页面计数
  • alloc_node_mem_map() 实际上为该节点中的每个物理页面分配 struct page;它们的内存是由 bootmem 分配器 doc1 doc2 分配的(您可以使用 bootmem_debug=1 内核启动选项查看其调试信息):

4936 size = (end - start) * sizeof(struct page);

4937 map = alloc_remap(pgdat->node_id, size);

if (!map) map = memblock_virt_alloc_node_nopanic(size, pgdat->node_id);

free_area_init_core()在其中构建内存映射,并初始化自由列表和伙伴位图。

每个区域的订单免费列表被初始化,并将订单标记为没有任何免费页面:free_area_init_core() -> init_currently_empty_zone() -> zone_init_free_lists

4147 static void __meminit zone_init_free_lists(struct zone *zone)
4148 {
4149         unsigned int order, t;
4150         for_each_migratetype_order(order, t) {
4151                 INIT_LIST_HEAD(&zone->free_area[order].free_list[t]);
4152                 zone->free_area[order].nr_free = 0;
4153         }
4154 }

PS:在内核中有一个init()函数,它被称为start_kernel(),而LXR(Linux交叉引用)可以帮助您在函数之间导航(我发布了lxr.free-electrons.com的链接,但有几个在线LXR):

501 asmlinkage __visible void __init start_kernel(void)
...
528         boot_cpu_init();
529         page_address_init();
530         pr_notice("%s", linux_banner);
531         setup_arch(&command_line);

谢谢你的回答,非常完美!我花了一些时间来理解所有不同组件的逻辑,但现在我有了更清晰的画面。现在我只需要弄清楚位图和节点是如何操作的,因为对我来说似乎并不清楚...如果一个节点被分成两半,并将其中一半移动到较低的顺序列表中,它是被追加还是放在空闲列表的中间?当需要合并两个节点时,如何找到伙伴? - Ivan Stanev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接