为什么要使用bootmem分配器,内存管理不是有buddy系统和slab分配器吗?由于在系统初始化的时候需要执行一些内存管理,内存分配的任务,这个时候buddy系统,slab分配器等并没有被初始化好,此时就引入了一种内存管理器bootmem分配器在系统初始化的时候进行内存管理与分配,当buddy系统和slab分配器初始化好后,在mem_init()中对bootmem分配器进行释放,内存管理与分配由buddy系统,slab分配器等进行接管。

bootmem分配器使用一个bitmap来标记物理页是否被占用,分配的时候按照第一适应的原则,从bitmap中进行查找,如果这位为1,表示已经被占用,否则表示未被占用。为什么系统运行的时候不使用bootmem分配器呢?bootmem分配器每次在bitmap中进行线性搜索,效率非常低,而且在内存的起始端留下许多小的空闲碎片,在需要非常大的内存块的时候,检查位图这一过程就显得代价很高。bootmem分配器是用于在启动阶段分配内存的,对该分配器的需求集中于简单性方面,而不是性能和通用性。

一、bootmem分配器核心数据结构

bootmem核心数据结构bootmem_data:

typedef struct bootmem_data {
unsigned long node_boot_start;
unsigned long node_low_pfn;
void *node_bootmem_map;
unsigned long last_offset;
unsigned long last_pos;
unsigned long last_success;
} bootmem_data_t;

系统内存的中每一个结点(如果是内存是UMA,就只有一个这样的结构)都有一个bootmem_data_t结构,它含有bootmem分配器给结点分配内存时所需的信息。

1、node_boot_start是这个结点内存的起始地址(如果内存是UMA的话,为0);

2、node_low_pfn是低端内存最后一个page的页帧号;

3、node_bootmem_map指向内存中bitmap所在的位置;

4、last_offset是分配的最后一个页内的偏移,如果该页完全使用,则offset为0;

5、last_pos是分配最后一个页帧号;

6、last_success是最后一次成功分配的位置。

二、bootmem分配器初始化

是在init_bootmem_core函数对bootmem分配器进行初始化的。

static unsigned long __init init_bootmem_core (pg_data_t *pgdat,
unsigned long mapstart, unsigned long start, unsigned long end)
{
bootmem_data_t *bdata = pgdat->bdata;
unsigned long mapsize = ((end - start)+)/; /*映射的字节数*/ pgdat->pgdat_next = pgdat_list;
pgdat_list = pgdat; mapsize = (mapsize + (sizeof(long) - 1UL)) & ~(sizeof(long) - 1UL);
bdata->node_bootmem_map = phys_to_virt(mapstart << PAGE_SHIFT);
bdata->node_boot_start = (start << PAGE_SHIFT);
bdata->node_low_pfn = end; memset(bdata->node_bootmem_map, 0xff, mapsize); //初始化所有内存都保留 return mapsize;
}

linux系统初始化过程中通过init_bootmem_core(NODE_DATA(0), min_low_pfn, 0, max_low_pfn)来调用这个函数初始化bootmem分配器。其中min_low_pfn是临时页表后的第一个可用页框,max_low_pfn是低端内存结束的页框。

前面说的bootmem分配器核心数据结构bootmem_data_t是内嵌在pg_data_t数据结构中(也就是节点的描述符中)。内存中的节点描述符是通过链表pgdat_list链起来的,如果内存为UMA的话,这个链表就只有一个节点。

mapsize按字节来计算系统低端内存对应的bitmap的大小,并且保证它是4的倍数。bitmap存放在min_low_pfn开始的页框中,并将bitmap的每一位置1,表示所有的内存页保留(为1是表示bootmem分配器是不能分配该页框的,为0表示可以分配)。最后函数返回bitmap的大小。

三、bootmem分配器可使用内存注册

在bootmem分配器初始化中把所有的页都标记为保留,即不可分配的,所以必须要给bootmem分配器注册可使用的内存。Linux在初始化中通过调用函数register_bootmem_low_pages(max_low_pfn)来向bootmem分配器注册低端内存中可使用的内存,该函数根据e820检测到的内存信息(e820.map[]),将类型为E820_RAM的page页框对应的位图中的位置为0,表示对应的page页框为空闲状态。然后调用reserve_bootmem(1024*1024, (PFN_PHYS(start_pfn) + bootmap_size + PAGE_SIZE-1) – 1024*1024)来保留bootmem分配器中内存从1MB到bitmap所用的内存页框,这些保留就包括了kernel内核,临时页表,bitmap。

四、bootmem分配器分配内存

内核在初始化阶段使用的分配内存函数alloc_bootmem,alloc_bootmem_low,alloc_bootmem_pages,alloc_bootmem_low_pages都会调用__alloc_bootmem,只是一层封装,实际上是传递不同的参数调用__alloc_bootmem。

static void * __init
__alloc_bootmem_core(struct bootmem_data *bdata, unsigned long size,
unsigned long align, unsigned long goal)
{
unsigned long offset, remaining_size, areasize, preferred;
unsigned long i, start = , incr, eidx;
void *ret; eidx = bdata->node_low_pfn - (bdata->node_boot_start >> PAGE_SHIFT);
offset = ;
if (align &&
(bdata->node_boot_start & (align - 1UL)) != )
offset = (align - (bdata->node_boot_start & (align - 1UL)));
offset >>= PAGE_SHIFT; if (goal && (goal >= bdata->node_boot_start) &&
((goal >> PAGE_SHIFT) < bdata->node_low_pfn)) {
preferred = goal - bdata->node_boot_start; if (bdata->last_success >= preferred)
preferred = bdata->last_success;
} else
preferred = ; preferred = ((preferred + align - ) & ~(align - )) >> PAGE_SHIFT;
preferred += offset;
areasize = (size+PAGE_SIZE-)/PAGE_SIZE;
incr = align >> PAGE_SHIFT ? : ;

参数size表示需要分配的内存大小,align表示对齐的大小,goal指定了希望分配内存的起始地址,会从这个位置开始查找。eidx表示在bootmem中页框的最大索引号。如果align不为0,则计算出还需要偏移的页框数offset来满足align的要求。如果goal不为0,且goal在bootmem分配的内存范围内,则将preferred设置为相对于内存起始地址的偏移量,如果最近一次分配成功的地址偏移量大于preferred,则修改preferred为这个地址偏移量,其它情况都将preferred设置为0。然后preferred转化为相对于起始地址的页索引号,并加上offset来满足align的对齐要求。

restart_scan:
for (i = preferred; i < eidx; i += incr) {
unsigned long j;
i = find_next_zero_bit(bdata->node_bootmem_map, eidx, i);
i = ALIGN(i, incr);
if (test_bit(i, bdata->node_bootmem_map))
continue;
for (j = i + ; j < i + areasize; ++j) {
if (j >= eidx)
goto fail_block;
if (test_bit (j, bdata->node_bootmem_map))
goto fail_block;
}
start = i;
goto found;
fail_block:
i = ALIGN(j, incr);
} if (preferred > offset) {
preferred = offset;
goto restart_scan;
}
return NULL;
}

find_next_zero_bit函数从第i个页框开始搜索空的页框,并返回空的页框i,如果后面有连续areasize个空的页,则找到了并记下起始位置start,如果没有连续的areasize个页框,就从新查找连续areasize个空的页。

found:
bdata->last_success = start << PAGE_SHIFT;
if (align < PAGE_SIZE &&
bdata->last_offset && bdata->last_pos+ == start) {
offset = (bdata->last_offset+align-) & ~(align-);
BUG_ON(offset > PAGE_SIZE);
remaining_size = PAGE_SIZE-offset;
if (size < remaining_size) {
areasize = ;
/* last_pos unchanged */
bdata->last_offset = offset+size;
ret = phys_to_virt(bdata->last_pos*PAGE_SIZE + offset +
bdata->node_boot_start);
} else {
remaining_size = size - remaining_size;
areasize = (remaining_size+PAGE_SIZE-)/PAGE_SIZE;
ret = phys_to_virt(bdata->last_pos*PAGE_SIZE + offset +
bdata->node_boot_start);
bdata->last_pos = start+areasize-;
bdata->last_offset = remaining_size;
}
bdata->last_offset &= ~PAGE_MASK;
} else {
bdata->last_pos = start + areasize - ;
bdata->last_offset = size & ~PAGE_MASK;
ret = phys_to_virt(start * PAGE_SIZE + bdata->node_boot_start);
} for (i = start; i < start+areasize; i++)
if (unlikely(test_and_set_bit(i, bdata->node_bootmem_map)))
BUG();
memset(ret, , size);
return ret;

找到连续areasize个空的页后,重新设置last_success。如果align小于PAGE_SIZE并且上一次分配的页框就是这次分配页框的上一个页框并且上一次分配的页框还没用完,则合并前面分配的未用的页框。合并有两种情况,如果前一个页框剩下未用的大小remaining_size大于这次需要请求分配的空间大小size,则直接从上一个页框未使用的空间分配给这次需要的size空间;如果前一个页框剩下未用的大小remaining_size不大于这次需要请求分配的空间大小size,则将请求的一部分分在前一个页框中,另一部分从新计算需要分配的页框数。然后更新相应的last_pos,last_offset字段。如果不能合并,就从start开始分配,更新last_pos,last_offset字段。

五、bootmem分配器释放内存

释放bootmem分配器分配的内存,很简单,直接把对应的bitmap位清0,并更新last_success,代码如下:

static void __init free_bootmem_core(bootmem_data_t *bdata, unsigned long addr, unsigned long size)
{
unsigned long i;
unsigned long start;
unsigned long sidx;
unsigned long eidx = (addr + size - bdata->node_boot_start)/PAGE_SIZE;
unsigned long end = (addr + size)/PAGE_SIZE; if (addr < bdata->last_success)
bdata->last_success = addr;
start = (addr + PAGE_SIZE-) / PAGE_SIZE;
sidx = start - (bdata->node_boot_start/PAGE_SIZE); for (i = sidx; i < eidx; i++) {
if (unlikely(!test_and_clear_bit(i, bdata->node_bootmem_map)))
BUG();
}
}

六、bootmem分配器的销毁

在buddy系统和slab分配器初始化之后,就可以把bootmem分配器销毁,把内存的管理交给buddy系统和slab分配器。bootmem分配器销毁是在mem_init函数中调用free_all_bootmem_core函数实现。

static unsigned long __init free_all_bootmem_core(pg_data_t *pgdat)
{
struct page *page;
bootmem_data_t *bdata = pgdat->bdata;
unsigned long i, count, total = ;
unsigned long idx;
unsigned long *map;
int gofast = ;
count = ; page = virt_to_page(phys_to_virt(bdata->node_boot_start)); //bootmem管理的第一个页框
idx = bdata->node_low_pfn - (bdata->node_boot_start >> PAGE_SHIFT); //最大bootmem索引号
map = bdata->node_bootmem_map;
/*如果起始物理地址页框号是32对齐的*/
if (bdata->node_boot_start == ||
ffs(bdata->node_boot_start) - PAGE_SHIFT > ffs(BITS_PER_LONG))
gofast = ;
for (i = ; i < idx; ) {
unsigned long v = ~map[i / BITS_PER_LONG];
if (gofast && v == ~0UL) { //从i开始的32个页都未使用
int j, order;
count += BITS_PER_LONG;
__ClearPageReserved(page); //清除页框描述符的保留位
order = ffs(BITS_PER_LONG) - ;
set_page_refs(page, order); //设置从page开始的32个页的引用为1
for (j = ; j < BITS_PER_LONG; j++) {
if (j + < BITS_PER_LONG)
prefetchw(page + j + );
__ClearPageReserved(page + j);
}
__free_pages(page, order); //将这些页回收到buddy系统中
i += BITS_PER_LONG;
page += BITS_PER_LONG;
} else if (v) { //从i开始的32页存在未使用的
unsigned long m;
for (m = ; m && i < idx; m<<=, page++, i++) { //每次移动一位
if (v & m) {
count++;
__ClearPageReserved(page);
set_page_refs(page, );
__free_page(page);
}
}
} else { //从i开始的32页都使用
i+=BITS_PER_LONG;
page += BITS_PER_LONG;
}
}
total += count;

如果起始物理地址页框号是32对齐的,设置gofast为1。如果gofast等于1,并且从page开始的32个页都未使用,则将page开始的32个页框回收到buddy系统中,这个通过调用函数__free_pages(page, order),一次回收32个页框。如果从page开始的32个页框中存在未使用的,查找这32个页框中空的页框,并回收到buddy系统中,一次回收一个页框。如果32个页框都使用了,则什么都不做。然后查找下一组32个页框回收,直到查找完bootmem分配器管理的所有页框。

total += count;
/*
释放bootmem的bitmap所在的页
*/
page = virt_to_page(bdata->node_bootmem_map);
count = ;
for (i = ; i < ((bdata->node_low_pfn-(bdata->node_boot_start >> PAGE_SHIFT))/ + PAGE_SIZE-)/PAGE_SIZE; i++,page++) {
count++;
__ClearPageReserved(page);
set_page_count(page, );
__free_page(page);
}
total += count;
bdata->node_bootmem_map = NULL; return total;
}

既然bootmem分配器要释放了,那么bootmem分配器所使用的bitmap所在的页框也应该回收到buddy系统中去。((bdata->node_low_pfn-(bdata->node_boot_start >> PAGE_SHIFT))/8 + PAGE_SIZE-1)/PAGE_SIZE计算bitmap所占的页框数,然后从bitmap所在的起始页框开始回收bitmap占用的所有页框到buddy系统中,一次回收一个页框。

Linux内存管理之bootmem分配器的更多相关文章

  1. Linux内存管理之slab分配器

    slab分配器是什么? 参考:http://blog.csdn.net/vanbreaker/article/details/7664296 slab分配器是Linux内存管理中非常重要和复杂的一部分 ...

  2. Linux内存管理 (5)slab分配器

    专题:Linux内存管理专题 关键词:slab/slub/slob.slab描述符.kmalloc.本地/共享对象缓冲池.slabs_partial/slabs_full/slabs_free.ava ...

  3. 启动期间的内存管理之引导分配器bootmem--Linux内存管理(十)

    在内存管理的上下文中, 初始化(initialization)可以有多种含义. 在许多CPU上, 必须显式设置适用于Linux内核的内存模型. 例如在x86_32上需要切换到保护模式, 然后内核才能检 ...

  4. Linux内存管理 - slab分配器和kmalloc

    本文目的在于分析Linux内存管理机制的slab分配器.内核版本为2.6.31.1. SLAB分配器 内核需要经常分配内存,我们在内核中最常用的分配内存的方式就是kmalloc了.前面讲过的伙伴系统只 ...

  5. 伙伴系统之伙伴系统概述--Linux内存管理(十五)

    在内核初始化完成之后, 内存管理的责任就由伙伴系统来承担. 伙伴系统基于一种相对简单然而令人吃惊的强大算法. Linux内核使用二进制伙伴算法来管理和分配物理内存页面, 该算法由Knowlton设计, ...

  6. 启动期间的内存管理之bootmem_init初始化内存管理–Linux内存管理(十二)

    1. 启动过程中的内存初始化 首先我们来看看start_kernel是如何初始化系统的, start_kerne定义在init/main.c?v=4.7, line 479 其代码很复杂, 我们只截取 ...

  7. 启动期间的内存管理之初始化过程概述----Linux内存管理(九)

    在内存管理的上下文中, 初始化(initialization)可以有多种含义. 在许多CPU上, 必须显式设置适用于Linux内核的内存模型. 例如在x86_32上需要切换到保护模式, 然后内核才能检 ...

  8. Linux内存管理 (1)物理内存初始化

    专题:Linux内存管理专题 关键词:用户内核空间划分.Node/Zone/Page.memblock.PGD/PUD/PMD/PTE.lowmem/highmem.ZONE_DMA/ZONE_NOR ...

  9. Linux内存描述之内存区域zone–Linux内存管理(三)

    服务器体系与共享存储器架构 日期 内核版本 架构 作者 GitHub CSDN 2016-06-14 Linux-4.7 X86 & arm gatieme LinuxDeviceDriver ...

随机推荐

  1. linux下tomcat安装

    1.先安装jdk,我们这里用yum进行安装: yum -y install java-1.7.0-openjdk* 确定是否安装成功: java -version 如果显示jdk的版本信息,说明安装成 ...

  2. 我的window10

    前言 这个一时半会写不完,也比较耗费时间,留着以后,每周更新一些新的技巧. 折腾了3天多时间的成果——>window10 的全新桌面,不比苹果差!不要说 windows 不能用 mac . 既然 ...

  3. String对象方法扩展

    /** *字符串-格式化 */ String.prototype.format = function(){ var args = arguments;//获取函数传递参数数组,以便在replace回调 ...

  4. HTTP协议详解(转)

    转自:http://blog.csdn.net/gueter/archive/2007/03/08/1524447.aspx Author :Jeffrey 引言 HTTP是一个属于应用层的面向对象的 ...

  5. dialog

    function showDialog(){ var $by = $(window), obj = $('.dialog'), brsW = $by.width(), brsH = $by.heigh ...

  6. Swift库运行崩溃

    报错如下: 解决方法: 退出 Xcode 找到 DerivedData 文件夹 删除 (路径: ~/Library/Developer/Xcode/DerivedData) 删除 com.apple. ...

  7. 使用nmap工具查询局域网某个网段正在使用的ip地址

    linux下nmap工具可扫描局域网正在使用的ip地址 查询局域网某网段正在使用的ip地址: nmap -sP .* 以上命令,将打印10.10.70.*/24网络所有正在使用的ip地址

  8. SQL Server2014 哈希索引原理

    SQL Server2014 哈希索引原理 翻译自:http://www.sqlservercentral.com/blogs/sql-and-sql-only/2015/09/08/hekaton- ...

  9. Remote Desktop Connection Manager

    通过Remote Desktop Connection Manager(RDCMan),当前最新版本是 v2.7. 通过这款软件,我们便可以轻松的管理和访问数个RDP.左边的列表中我们可以创建总的分区 ...

  10. Dropbox创造共享新思维——Datastore API

    7月9日,第一届Dropbox开发者大会上,Dropbox发布了Datastore API的beta版本,通过这个API,原始的结构化数据可以在多个设备间的Dropbox内同步.CEO Drew Ho ...