boot memory allocator——自举内存分配器(五:停用bootmem)
2012-08-17 15:10
423 查看
在系统初始化进行到伙伴系统分配器能够承担内存管理的责任后,必须停用bootmem分配器,毕竟不能同时用两个分配器管理内存。在UMA和NUMA系统上,停用分别由free_all_bootmem和free_all_bootmem_node完成。在伙伴系统建立以后,特定于体系结构的初始化代码需要调用这两个函数。本文还是选择讨论UMA系统。
free_all_bootmem如下:
问题1:上面第6行,为什么在释放该页之前又将该页的_count设置回1,设置回1之后该页还能被释放吗?那么前面做的工作岂不都是白费!
问题2:当order=0时,每释放一个页之前都将该页的_count设置回1,当order非0时,仅仅将要释放的一连串的页的最后一页的_count设置回1,为什么?
希望谁可以指点一下,不甚感激。下面附上我没有弄明白的那个函数,set_page_refcounted:
free_all_bootmem如下:
unsigned long __init free_all_bootmem(void) { return free_all_bootmem_core(NODE_DATA(0)); }它实际上将工作委托给free_all_bootmem_core,对free_all_bootmem_core源代码的分析如下:
static unsigned long __init free_all_bootmem_core(pg_data_t *pgdat) { struct page *page; unsigned long pfn; bootmem_data_t *bdata = pgdat->bdata; unsigned long i, count, total = 0; unsigned long idx; unsigned long *map; int gofast = 0;//gofast起一个标识的作用,当gofast等于1时就有可能一次性释放掉32个页面,gofast等于1的条件见下面 BUG_ON(!bdata->node_bootmem_map); count = 0; /* first extant page of the node */ pfn = PFN_DOWN(bdata->node_boot_start);//分配给该内存结点起始页的编号 idx = bdata->node_low_pfn - pfn;//计算出该内存结点所占的内存总页数,包含空洞 map = bdata->node_bootmem_map; /* Check physaddr is O(LOG2(BITS_PER_LONG)) page aligned */ if (bdata->node_boot_start == 0 || ffs(bdata->node_boot_start) - PAGE_SHIFT > ffs(BITS_PER_LONG))//ffs()函数可以求出一个数中置1的最低位,PAGE_SHIFT为12,BITS_PER_LONG为32,所以上述条件的语义为:当内存结点的起始地址是0或者至少是按2的17次方对齐的,也就是说页号是按至少是32对齐的,这样对应的页帧位码表也是按一个字对齐的 gofast = 1;//上述条件成立时,就可以一次性释放掉32个页面 for (i = 0; i < idx; ) { unsigned long v = ~map[i / BITS_PER_LONG];//此处我们得明白map是一个指向unsigned long的指针,那么,举个例子来说,map[0]就表示一个unsigned long类型的变量,在内存中占据32位,又因为map是由bdata->node_bootmem_map赋值过来的,在bdata->node_bootmem_map中一位就可以表示一页的使用情况,所以map[0]是由连续的32个可以表示内存使用情况的位构成的 if (gofast && v == ~0UL) {//当gofast=1也就是满足快速释放的基本条件并且原始位图中连续的32位都是0的情况下就可以一次性释放掉32页,注意上面有一个取反的运算 int order; page = pfn_to_page(pfn);//#define pfn_to_page(pfn) virt_to_page(pfn_to_virt(pfn)),#define pfn_to_virt(pfn) __va((pfn) << PAGE_SHIFT),#define __va(x) ((void *)((unsigned long)(x) | 0xc0000000)),#define virt_to_page(addr) (mem_map + (((unsigned long)(addr)-PAGE_OFFSET) >> PAGE_SHIFT)),这一连串宏的作用是先通过物理页帧找到物理地址,然后通过物理地址找到虚拟地址,最后通过虚拟地址找到映射的页号 count += BITS_PER_LONG;//由于一次释放了32页,所以count+32 order = ffs(BITS_PER_LONG) - 1;//order的含义是对需要连续释放的页取以2为底的对数,如现在需要连续释放32页,将32取以2为底的对数得5,正好等于ffs(BITS_PER_LONG)-1 __free_pages_bootmem(page, order);//释放页,page为释放的首个页的地址,order表示需要连续释放2的order次方个页,详细讨论见下文 i += BITS_PER_LONG;//i往后走,继续完成外层循环 page += BITS_PER_LONG;//page也需要往后走 } else if (v) {//即使满足快速释放的基本条件(gofast=1),但是原始位图中连续的32位不全为0时,也只能一个页一个页的释放 unsigned long m; page = pfn_to_page(pfn); for (m = 1; m && i < idx; m<<=1, page++, i++) {//通过这个循环就能将原始位图中(针对上面的32位)中位为0的页释放掉 if (v & m) { count++; __free_pages_bootmem(page, 0); } } } else {//在原始位图中该连续的32位全为1,即都在使用中,没有页可以释放 i += BITS_PER_LONG;//i往后走,继续完成外层循环 } pfn += BITS_PER_LONG;//往后走,继续完成外层循环 } total += count;//total记录释放的总页数,程序运行至此,就将位图中指示的空闲页全部释放了,下面还需释放位图本身占据的内存单元 /* * Now free the allocator bitmap itself, it's not * needed anymore: */ page = virt_to_page(bdata->node_bootmem_map);//将存储位图的内存区的首地址转换为页号 count = 0; idx = (get_mapsize(bdata) + PAGE_SIZE-1) >> PAGE_SHIFT;//get_mapsize()在前面的文章中已经讨论过,此语句就是计算页帧位码表占了多少页 for (i = 0; i < idx; i++, page++) {//然后通过此循环,从首页开始一页一页的释放 __free_pages_bootmem(page, 0); count++; } total += count; bdata->node_bootmem_map = NULL;//既然存放页帧位码表的空间已经不存在了,我们就是这个指针变量指向null return total; }对free_pages_bootmem的源代码分析如下:
void fastcall __init __free_pages_bootmem(struct page *page, unsigned int order) { if (order == 0) {//order=0,表示只释放一页 __ClearPageReserved(page);//将struct page结构flags标志字中的PG_reserved标志位清除 set_page_count(page, 0);//将page结构的_count位置0,使其可以被释放 set_page_refcounted(page);////////////////////////不明白为什么再释放之前又将该页的_count设置回1 __free_page(page);//#define __free_page(page) __free_pages((page), 0),_free_pages的分析见下文 } else {//如果order不为0,就一次性释放2的order次方个连续页 int loop; prefetchw(page);//由于是释放连续的页,所以可以先预取 for (loop = 0; loop < BITS_PER_LONG; loop++) { struct page *p = &page[loop]; if (loop + 1 < BITS_PER_LONG) prefetchw(p + 1); __ClearPageReserved(p); set_page_count(p, 0); } set_page_refcounted(page); __free_pages(page, order); } }
问题1:上面第6行,为什么在释放该页之前又将该页的_count设置回1,设置回1之后该页还能被释放吗?那么前面做的工作岂不都是白费!
问题2:当order=0时,每释放一个页之前都将该页的_count设置回1,当order非0时,仅仅将要释放的一连串的页的最后一页的_count设置回1,为什么?
希望谁可以指点一下,不甚感激。下面附上我没有弄明白的那个函数,set_page_refcounted:
static inline void set_page_refcounted(struct page *page) { VM_BUG_ON(PageCompound(page) && PageTail(page)); VM_BUG_ON(atomic_read(&page->_count)); set_page_count(page, 1); }对_free_pages源代码的分析如下:
fastcall void __free_pages(struct page *page, unsigned int order) { if (put_page_testzero(page)) {//把 page 的计数原子性的减 1 ,并测试是否为 0 ,如果为 0 ,返回 true,否则返回 false if (order == 0) free_hot_page(page);//把该页释放到该页所属内存node的内存页区的当前处理器的“热区”高数缓存内存中,后面的文章会详细讨论 else __free_pages_ok(page, order);//否者就调用伙伴系统内存释放操作函数,后面的文章会详细讨论 } }
相关文章推荐
- boot memory allocator——自举内存分配器(四:内存释放)
- boot memory allocator——自举内存分配器(三:内存分配)
- boot memory allocator——自举内存分配器(二:在IA-32系统下的初始化)
- boot memory allocator——自举内存分配器(一:基本介绍)
- 14.6.4 Configuring the Memory Allocator for InnoDB 配置InnoDB 内存分配器
- 内核内存分配器(Kernel Memory Allocator, KMA)
- 14.6.4 Configuring the Memory Allocator for InnoDB 配置InnoDB 内存分配器
- php Allocator Jemalloc TCMalloc那个内存分配器比较好?
- 内核的bootmem内存分配器【转】
- STL内存分配器:allocator
- Boost.Interprocess使用手册翻译之八:分配器,容器和内存分配算法(Allocators, containers and memory allocation algorithms)
- [置顶] 从零开始学C++之STL(二):实现简单容器模板类Vec(vector capacity 增长问题、allocator 内存分配器)
- 实现简单容器模板类Vec(vector capacity 增长问题、allocator 内存分配器)
- Boost.Interprocess使用手册翻译之八:分配器,容器和内存分配算法(Allocators, containers and memory allocation algorithms)
- 理解C/C++中的allocator——(内存)分配器
- 内核早期内存分配器 - memblock与bootmem
- C++中的allocator类(内存分配器)
- STL内存分配器:allocator
- bootmem_init分析(Bootmem Allocator)
- [置顶] 从零开始学C++之STL(二):实现简单容器模板类Vec(vector capacity 增长问题、allocator 内存分配器)