linuxn内存管理之buddy

本文基于linux-3.11.1代码, arm cpu, flat memory model(平坦连续的内存,用于嵌入式)为背景来说明。

start_kernel() -> setup_arch()

arch/arm/kernel/setup.c

883行,调用setup_machine_fdt() -> early_init_dt_scan() -> early_init_dt_scan_memory() -> early_init_dt_add_memory_arch() -> arm_add_memory()去解析device tree文件中描述的内存信息,并保存到meminfo中。

904行,将解析出来的系统中拥有的内存bank[]按物理地址从低到高进行堆排序。这样就保存了bank[n]的起始地址+size < bank[n+1]的起始地址。

909行,调用arm_memblock_init()将系统中的所有内存都记录到memblock.memory中,同时记录系统当前被使用掉的内存信息到memblock.reserved中,比如内核镜像占据的内存范围、Initrd占据的内存范围,device tree占据的内存范围、mmu内存页表占据的内存范围、以及DMA占用的内存范围等。

911行调用paging_init(), paging_init()主要是配置一些虚地址映射,比如低端内存地址映射,dma内存地址映射等。最后,调用bootmem_init()。

arch/arm/mm/init.c

我们接着来看bootmem_init()。

start_kernel() -> setup_arch() -> paging_init() -> bootmem_init()

arch/arm/mm/init.c

find_limits()从meminfo.bank[]数组中低端内存里找出最小内存的物理帧号(bank[0]),低端内存的最大内存的物理帧号,以及高端内存的最大内存的物理帧号,并分别记录到min_low_pfn, max_low_pfn, max_pfn全局变量中。meminfo.bank[]各bank记录的内存有可能不连续,但我们讲述的是flat memory model,所以假定bank[]中的内存是前后衔接,且连续的。即bank[i].end == bank[i+1].start.

我们继续看bootmem_init(), 来看一下349行zone_sizes_init()做了什么。

arch/arm/mm/init.c

200-202行,计算flat mem model下的低端内存帧数和高端内存帧数,保存在zone_size[0]和zone_size[1]中。

209-224行,对于非flat mem model下,计算不连续的memory的帧数(hole pfn number),并保存于zhole_size[]中。对于flat mem model,zhole_size[]都为0.

236行,调用free_area_init_node().

mm/page_alloc.c

4935行,pgdat->node_start_pfn 表示物理内存的起始帧号(物理内存并非一定是从0地址开始的)。

4940行,调用calculate_node_totalpages(),用于计算flat mem model下的物理内存帧数,保存于pgdat-> node_spanned_pages, 同时计算非flat mem model下的实际的物理内存帧数,并保存于pgdat-> node_present_pages.在flat memory模型下,node_spanned_pages和node_present_pages相等。

4943行,alloc_node_mem_map(), 调用memblock_virt_alloc_node_nopanic(),为flat mem model下的所有物理内存从低端内存的高地址处申请管理结构体数组struct page[], 数组地址保存于pgdat->node_mem_map和mem_map中。memblock_virt_alloc_node_nopanic()分配时,从memblock.memory中寻找非memblock.reserved的且满足size的低端物理内存(0~768M),然后返回其虚拟地址。

内存视图如下:

我们回到函数free_area_init_node().

4950行,调用free_area_init_core()

mm/memblock.c

free_area_init_core()主要是初始化pgdat的list, lock等。

4802-4845, 循环,计算normal_zone和highmem_zone两个zone所用所有的内存页数,并记录在zone->spanned_pages和zone->present_pages.(flat memory模型中,这两个相等)。

4873行,调用setup_usemap()主要是构建该zone管理的内存页的使用情况的位图(bit map)。

mm/page_alloc.c

usemap_size()计算zonesize对应位图所需要的内存字节数,1bit对应1Kbytes(pageblock_order)。zone管理它的内存,分为了多个area,如下:

计算处mapsize后,调用memblock_virt_alloc_node_nopanic()从低端内存高地址处申请bitmap内存。

free_area_init_core()最后调用memmap_init().

mm/page_alloc.c

4060-4079行,定位zone中的每个内存页对应的struct page结构,将其设置为reserved状态。

4097行,调用set_pageblock_migratetype()。

mm/page_alloc.c

set_pageblock_migratetype()计算struct page所代表的物理内存帧pfn(6079行), 然后计算该pfn对应的位图段位置,将其对应的位图段清零。6088-6089行怎么理解?

假设,bitidx为40,即bitidx & (BITS_PER_LONG-1) = 8(取模运算),即该pfn对应位图字节的字节内的偏移为8,然后加上end_bitidx,即字节内偏移为8+4=12, 但是这个偏移是从高bit开始偏移的,即pfn对应的位图字节的字节内的bit位置为bit22 ~ bit19, 这样mask << (BITS_PER_LONG – bitidx - 1)就是该pfn对应的位图字节内的占据的bits。

最后,6093行,设置pfn占据的4bits(mask = 0b 111)中的PB_migrate - PB_migrate_end,PB_migrate_skip为0(word & ~mask),然后将这4bits设置为MIGRATE_MOVABLE(2).(|flags)

即表示这个pfn是movable的。

pfn bitmap所在位置如下:

我们接着看相关代码:

start_kernel() -> mm_init() -> mem_init()

arch/arm/mm/init.c

532行,pfn_to_page(max_pfn)指向最大的物理内存帧对应的struct page[i],mem_map指向struct page[0].

535行,对于flat mem model相当于什么都不用做。对于非连续内存,它是用于释放meminfo.bank[]之间的hole的内存,将这些hole内存移到memblock.reserved中。

536行,调用free_all_bootmem().

mm/nobootmem.c

(linux早期版本时候用的bootmem,后面都用memblock了,我们基于memblock来讲解)

这个函数相等于buddy算法的初始化了,我们重点来看一下。

假设,我们的内存是flatten的,即平坦连续的,不存在hole。如下图所示:

free_all_bootmem()在174行,设置所有zone的managed_pages为0,即z->managed_pages = 0.().

之后调用free_low_memory_core_early()去释放所有的低端内存页。free_low_memory后,在182行,totalram_pages(系统总共free的pages数,包括低端内存页和高端内存页)记录了低端内存的free的pages数。

mm/nobootmem.c

122行,从memblock.memory中找出没有被reserved(memblock.reserved)的内存段的起始地址(start)和结束地址(end).

123行,调用__free_memory_core(start, end)去释放这些未被reserved的内存。

mm/nobootmem.c

__free_memory_core()将start, end地址转成pfn(物理内存帧号),且将结束帧限定在最大的地段内存帧内。

最后,调用__free_pages_memory(),去释放这段连续的内存页。

mm/nobootmem.c

__free_pages_memory()计算从start_pfn到end_pfn这段连续的内存的order的最大值(不超过10),然后调用__free_pages_bootmem()。注意,这里第一个参数为连续(1<<order)个内存页(start_pfn, start_pfn + (1<<order)-1)的首页,第二参数为order。__free_pages_bootmem()会将这一片的内存页的struct page结构中的reserved标记清除。

start += (1 << order)为下一个连续内存页的首页。

mm/page_alloc.c

__free_pages_bootmem() -> __free_pages() -> __free_pages_ok() -> free_one_page() -> __free_one_page().

最终,函数调用来到了__free_one_page(),第一个参数为当前连续的内存页的首页的帧号,第二个参数为order。我们来分析这个函数(略去不关注或不重点的代码)

mm/page_alloc.c

560行,将起始内存页的帧号取bit0~bit10。什么意思呢?就是将内存按2K划分成块,即2K个内存页为一块。这一行取起始内存页的块内的index.

562行,order为0-9的进入while()循环处理,order=10的不进while()。

563行,buddy_idx = page_idx ^ (1<<order),相当于buddy_idx = page_idx + 2order, page_idx为该片连续内存页的第一页的索引(2K块内的索引,即相对索引),故buddy_idx为该片(page_idx)连续内存页的紧接着的后面的连续内存页的首页,即这两个连续内存页为buddy关系(邻居?).

564行,buddy指向后面邻居连续内存页的首页的struct page结构。

566行,对于初始化过程,从这一行跳出。

586行,该片连续内存页的首页struct page设置order值,即表明从该页内存开始,以直到+ 2order - 1内存页,都是在同一order下管理的。

609行,将该片连续内存页的首页,放到zone->free_area[order].free_list[MOVEABLE]链表中。

之后程序回到__free_pages_memory(),将start到end之间剩余的内存页加到对应的order链表中。

最后,回到free_low_memory_core_early(),将其他的未reserved的内存段加到对应的order链表中。

处理完后的效果如下:

我们回到mem_init(),继续后面的代码。

538行,调用free_highpages() -> free_area_high()去释放高端内存中没有reserved的page,即将这些page中reserved标记去掉。

arch/arm/mm/init.c

mm/page_alloc.c

include/linux/mm/mm.h

include/linux/gpf.h

mm/page_alloc.c

由于order为0,故这里进入free_hot_cold_page()。

mm/page_alloc.c

所以,对于高端内存,都是放入了pcp链表list里面了。zone pcp初始化如下:

buddy内存相关的初始化就到这基本结束了,下面我们来看看buddy内存使用的内核API吧。

linux/gpf.h

__get_free_page(gfp_mask)

unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)

__get_free_pages()调用alloc_pages()来分配一个struct page,并返回这个page代表的内存页的虚地址。

mm/mm.h

alloc_page(gfp_mask)

alloc_pages(gfp_mask, order)

include/linux/gpf.h

mm/page_alloc.c

alloc_pages() -> alloc_pages_node() -> __alloc_pages() -> __alloc_pages_nodemask() ->get_page_from_freelist(), 这个get_page_from_freelist()就是从前面介绍的freelist(order > 0)或pcp(order ==0)中取空闲的page。代码如下:

mm/page_alloc.c

1545-1566, order == 0时从pcp中取page.

1567-1582, order > 0时,调用__rmqueue() -> __rmqueue_smallest()从freelist中取page.

mm/page_alloc.c

free_page(addr)

void free_pages(unsigned long addr, unsigned int order)

mm/page_alloc.c

__free_page(page)

__free_pages(struct page *page, unsigned int order)

__free_pages()就是将page对应的内存页释放到对应的freelist或pcp上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值