arm-linux之bootmem分配器

所谓bootmem分配器,是内核页表创建后的临时使用的内存分配器,它将在后面的mem_init函数初始化伙伴系统时被伙伴系统取代,即它是一个过渡手段。理解bootmem,一方面有利于对内存管理尤其内核启动阶段阶段内存管理的理解,另一方面也有利于伙伴系统的理解。

在函数bootmem_init中,对每一个node执行bootmem_init_node,bootmem_init_node又对该node的每一个bank执行map_memory_bank即创建内存映射后,接下来的内容是创建bootmem分配器,如下:

static void __init bootmem_init_node(int node, struct meminfo *mi,

         unsigned longstart_pfn, unsigned long end_pfn)

{

         unsigned longboot_pfn;

         unsigned intboot_pages;

         pg_data_t *pgdat;

         int i;

         /*

          * Map the memory banks for this node.

          */

         for_each_nodebank(i,mi, node) {

                   structmembank *bank = &mi->bank[i];

                   if(!bank->highmem)

                            map_memory_bank(bank);

         }

 

boot_pages = bootmem_bootmap_pages(end_pfn - start_pfn);

boot_pfn = find_bootmap_pfn(node, mi, boot_pages);

 

node_set_online(node);

pgdat = NODE_DATA(node);

init_bootmem_node(pgdat, boot_pfn, start_pfn, end_pfn);

 

for_each_nodebank(i, mi, node) {

                   struct membank*bank = &mi->bank[i];

                   if(!bank->highmem)

                            free_bootmem_node(pgdat,bank_phys_start(bank), bank_phys_size(bank));

}

reserve_bootmem_node(pgdat, boot_pfn << PAGE_SHIFT,

                                 boot_pages << PAGE_SHIFT,BOOTMEM_DEFAULT);

}

下面是详述:

1、  boot_pages = bootmem_bootmap_pages(end_pfn - start_pfn);

bootmem分配器是用bitmap即比特位图的方式标识每一个物理页的占用未占用的情况,这里就是计算这么些物理页需要多少个字节的bitmap,如128M物理内存,相当于32768个物理页,需要32768/8 = 4096个字节的bitmap,即4K的空间(一页)用于bitmap;

2、  boot_pfn = find_bootmap_pfn(node, mi, boot_pages);

找出内核镜像之后(即_end之后)的第一个物理页地址,找它是为了确定这个bitmap应该放在哪,它应该放在内核镜像之后;

3、  node_set_online(node);

设置这个node为online,这是个软件的伎俩,知道意思即可;

4、  pgdat = NODE_DATA(node);

取得该node的pg_data_t数据结构变量,每个node都有一个pg_data_t变量描述;今后会经常用到;

5、  init_bootmem_node(pgdat, boot_pfn, start_pfn, end_pfn);

它将直接调用init_bootmem_core (pgdat->bdata, freepfn, startpfn, endpfn);

这是直接创建bootmem分配器了,第一参数pgdat->bdata是bootmem_data_t类型,这个就是bootmem分配器成员:

下面是确定bootmem分配器的实体:bitmap的虚拟地址在哪里,参数mapstart就是前面第2步得到的boot_pfn即bitmap所在物理页地址,把它转换成虚拟地址;

bdata->node_bootmem_map = phys_to_virt(PFN_PHYS(mapstart));

下面是确定bootmem分配器的起始物理页地址、结尾物理地址

             bdata->node_min_pfn = start;

bdata->node_low_pfn = end;

把这个bootmem分配器加入到链表bdata_list中,链表头bdata_list就在本文件定义(这步无需重视);

link_bootmem(bdata);

下面是计算出该bootmem分配器的bitmap占用多少字节mapsize;然后根据字节数长度把bitmap全都置成1,意为当前所有页都是不可用(bootmem分配器,原理是用每一比特标识每一页的使用情况,置1为已占用,置0为可用)

mapsize = bootmap_bytes(end - start);

memset(bdata->node_bootmem_map, 0xff, mapsize);

6、  for_each_nodebank(i, mi, node) {

structmembank *bank = &mi->bank[i];

           if (!bank->highmem)

                    free_bootmem_node(pgdat,bank_phys_start(bank), bank_phys_size(bank));

}

对该node的每个bank,只要是低端内存,调用函数free_bootmem_node;

void__init free_bootmem_node(pg_data_t *pgdat, unsigned long physaddr,

                          unsigned long size)

{

unsigned long start, end;

kmemleak_free_part(__va(physaddr), size);

start = PFN_UP(physaddr);

end = PFN_DOWN(physaddr + size);

 mark_bootmem_node(pgdat->bdata, start, end,0, 0);

}

首先由start和end获取物理起始、结尾地址start和end;然后调用函数mark_bootmem_node:

mark_bootmem_node(pgdat->bdata, start, end, 0, 0);

这个函数的用途是把该bootmem分配器标识的相关内存(由start和end标识是哪些内存),进行标记,标记为可用或已用(通过标记node的boomtown的node_bootmem_map成员为1或0,为1为已用,为0为可用)

staticint __init mark_bootmem_node(bootmem_data_t *bdata,

                             unsigned longstart, unsigned long end,

                             int reserve, intflags)

{

unsigned long sidx, eidx;

bdebug("nid=%td start=%lx end=%lxreserve=%d flags=%x\n",

           bdata - bootmem_node_data, start,end, reserve, flags);

BUG_ON(start < bdata->node_min_pfn);

BUG_ON(end > bdata->node_low_pfn);

 

sidx = start - bdata->node_min_pfn;

eidx = end - bdata->node_min_pfn;

 

if (reserve)

           return __reserve(bdata, sidx, eidx,flags);

else

           __free(bdata, sidx, eidx);

return 0;

}

由物理页地址和该node的bootmem分配器的首尾物理页地址的差值,获取索引sidx和eidx,最后通过__reserve或__free设置某块内存区域为已占用(reserve)或已可用(free);无需再细看__reserve和__free的实现,它们都是bitmap相应的位操作;

要注意这里的意义:执行到这里,该node的低端物理内存均已在bootmem分配器中标识为可用

         7、reserve_bootmem_node(pgdat,boot_pfn << PAGE_SHIFT,

                                 boot_pages << PAGE_SHIFT,BOOTMEM_DEFAULT);

reserve_bootmem_node和上面的free_bootmem_node功能相反,它是设置某块内存为已占用,这里把内核代码段之后(_end之后)的第一个物理页地址boot_pfn之后的一页(4K)物理内存设置为不可用,实际上这一页内容就是bootmem的bitmap表本身,所以不能被占用;

至此,除了bitmap本身所在的物理页为已占用,其他内存还是可用的

8、从bootmem_init_node还是返回,执行到:

if (node == 0)

                   reserve_node_zero(NODE_DATA(node));

这里是把内核页表(0xc0004000到0xc0008000)以及内核镜像([_stext:_end])部分所处的物理页面也在bootmem分配器的bitmap中标识为已占用;

至此,bitmap本身、内存页表本身、内核镜像本身所处的物理页已被标识为已占用,其他物理内存可用;到这里,bootmem分配器已创建好并且把不应被占用的物理页面保护起来了,接下来看看bootmem分配器的使用:

 

对于bootmem分配器的使用,重在知道原理和大概记住都用在了哪些地方,具体细节无需细究,因为伙伴系统初始化后bootmem就不用了,没意义

主要会用到alloc_bootmem_node函数和alloc_bootmem_low_pages函数等,它们最终使用的都是同一个函数alloc_bootmem_core(释放函数仍是free_bootmem_node,不过初始阶段都是在分配,基本没有用到释放),这个函数是从bootmem分配器的bitmap位图中申请内存,过程非常繁琐,网上也有不少关于它的注释和讲解,我个人认为无需仔细分析,因为bootmem分配器本身效率就比较低,又是临时使用的分配手段,重点看看都有哪些地方需要申请内存,至少有如下几处:

devicemaps_init中的为中断向量创建映射时需要映射在高端地址,中断向量本身需申请物理内存(1页),又因为所映射中断向量长度不足1M,不足一个段所以需要二级映射,创建二级页表又需申请1页物理内存;

此外其他IO寄存器映射中,不足1M的映射均需创建二级映射的,都需申请相应数量物理内存;

kmap_init中高端内存映射(当前这里marvell不存在高端内存),需要申请相关创建二级页表所需物理内存;

paging_init中最后需1页物理内存专门用作一个全0页empty_zero_page;

最早的初始化后面伙伴系统函数free_area_init_node中,调用函数alloc_node_mem_map为每一个物理内存页创建其struct page管理结构,所需申请的内存;

在最终start_kernel调用函数mm_init--->mem_init时,将把该node中所有可用的内存转交给伙伴系统:

for_each_online_node(node) {

                   pg_data_t*pgdat = NODE_DATA(node);

         free_unused_memmap_node(node, &meminfo);

       

                   if(pgdat->node_spanned_pages != 0)

                            totalram_pages+= free_all_bootmem_node(pgdat);

}

free_unused_memmap_node函数意思是,逐个bank遍历,/*释放每个bank之间的间隙(它们之间的间隙是下一个bank的描述部分),对于这里的marvell,只有1个bank,不存在此情况;

free_all_bootmem_node是把整个bootmem分配器掌管的内存包括bitmap本身统统都释放到伙伴系统中去。下面是该函数的注释片段:

while (start < end) {

                   unsignedlong *map, idx, vec;

                   map =bdata->node_bootmem_map;

                   idx = start- bdata->node_min_pfn;

                   vec =~map[idx / BITS_PER_LONG];

        /*end比start大32页时,释放内存到伙伴系统*/

                   if (aligned&& vec == ~0UL && start + BITS_PER_LONG < end) {

                            intorder = ilog2(BITS_PER_LONG);

                            __free_pages_bootmem(pfn_to_page(start),order);

                            count+= BITS_PER_LONG;

                   }

        /*end比start大不足32页时,释放内存到冷热页框*/

        else {

                            unsignedlong off = 0;

                            while(vec && off < BITS_PER_LONG) {

                                     if(vec & 1) {

                                               page= pfn_to_page(start + off);

                                               __free_pages_bootmem(page,0);

                                               count++;

                                     }

                                     vec>>= 1;

                                     off++;

                            }

                   }

                   start +=BITS_PER_LONG;

         }

/*最后把bootmem分配器的位图本身所占页也释放掉,释放给冷热页框*/

         page =virt_to_page(bdata->node_bootmem_map);

         pages =bdata->node_low_pfn - bdata->node_min_pfn;

         pages =bootmem_bootmap_pages(pages);

         count += pages;

         while (pages--)

                   __free_pages_bootmem(page++,0);

你可能感兴趣的:(bitmap,内存分配管理,伙伴系统,armlinux,bootmem)