bootmem

来源:互联网 发布:2017个人发卡平台源码 编辑:程序博客网 时间:2024/06/05 18:05

如有问题,欢迎一起讨论:-)

 

 为了对页面管理机制作初步准备,Linux使用了一种叫bootmem分配器的机制,它仅仅用在系统引导时,为整个物理内存建立一个页面位图,位图建立开始于start_pfn,即内核映像终点_end,结束是max_low_pfn,主要用来管理0-896MB的范围。目的:在这个范围有的页面可能保留,有些页面可能是空洞,要搞清楚哪些物理页面是可以动态分配的。

      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;         /*上一次分配的位图的位置*/
      }bootmem_data_t;
mapsize = (mapsize + (sizeof(long) - 1UL)) & ~(sizeof(long) - 1UL)
memsize成为下一个4的倍数(4为CPU的字长)。例如,假设有40个物理页面,因此,我们可以得出memsize为5个字节。所以,上面的操作就变为(5+(4-1))&~(4-1)即(00001000&11111100),最低的两位变为0,其结果为8。这就有效地使memsize变为4的倍数
 
#define PFN_UP(x)       (((x) + PAGE_SIZE-1) >> PAGE_SHIFT) 地址页面向上取整
#define PFN_DOWN(x)     ((x) >> PAGE_SHIFT)   地址页面向下取整
 
max_pfn 对应的页框为地址最高的一个可用页框
max_low_pfn 对应的页框为可直接寻址的最后一个页框,属于低端内存
highstart_pfn 同max_low_pfn
highend_pfn 同max_pfn
 
 1、  初始化:
            bootmem_data_t   *bdata = pgdat->bdata;  pgdat是pg_data_t数据结构,它代表一片均匀、连续的内存,对于UMA,只有一个结点
            mapsize = (mapsize + (sizeof(long) - 1UL)) & ~(sizeof(long) - 1UL); 见前面笔记,为了向上取4倍数,位图字节对齐
            bdata->node_bootmem_map = phys_to_virt(mapstart<<PAGE_SHIFT); mapstart为start_pfn,转换为虚地址,为了在后面直接使用其地址,进行初始化
            bdata->node_boot_start = (start<<PAGE_SHIFT);
            bdata->node_low_pfn = end;  初始化开始地址0x00000000,结束页面
            memset(bdata->node_bootmem_map, 0xff, mapsize); 位图全部初始化为1,表示都保留使用
 
 2、 free_bootmem() 这个函数把给定范围内的页面标记为空闲(即可用)
            unsigned long eidx = (addr + size -  bdata->node_boot_start)/PAGE_SIZE;
            unsigned start = (addr + PAGE_SIZE -1) /PAGE_SIZE;
            unsigned long   sidx = start - (bdata->node_boot_start)/PAGE_SIZE;
            for(i = sidx; i < eidx ; i++)
                     test_and_clear_bit(i, bdata->node_boot_mem_map);
 
 3、reserve_bootmem()函数 函数用来保留一部分页面,其实直接可以在位图中置1
 
        传入参数:bdata、addr、size
         首先得到要进行保留的起始页面:
         unsigned long sidx = (addr - bdata->node_boot_start)/PAGE_SIZE;
         然后是size后的页面:
         unsigned long eidx = (addr + size - bdata->node_boot_start + PAGE_SIZE - 1)/PAGE_SIZE;
         然后进行循环进行置位:
         for(i = sidx; i < eidx; i++)
                  if(test_and_set_bit(i, bdata->node_bootmem_map))
 4、__alloc_bootmem函数,用来在节点上分配页面
         传入参数:bdata, size, align, goal
         goal是在分配时,尽量去主动选择页面,即在goal地址之上的,所以首先要判断这个值存在与否:
         if(goal && (goal >= bdata->node_boot_start) && ((goal >> PAGE_SHIFT) < bdata->node_low_pfn))
                     preferred = goal - bdata->node_boot_start;
         else
                     preferred = 0;
         preferred = ((preferred + align - 1) & ~(align - 1)) >> PAGE_SHIFT; 根据align对齐得到物理地址
         下面计算要分配的页面数
         areasize = (size + PAGE_SIZE - 1)/PAGE_SIZE;
         incr = align>>PAGE_SHIFT ? : 1;   根据对齐大小来选择增加值
         restart_scan:
                  for(i = perferred; i < edix; i+=incr)
                  {
                        unsigned long j;
                        if(test_bit(i, bdata->node_bootmem_map))
                                       continue;
                  
         首先,函数查找从perferred开始,第一个位图为0的,即空闲的页面,如果置1,则continue。
                  for(j = i + 1; j < i + areasize; j++)
                  {
                        if(j >= edix)
                              goto fail_block;
                        if(test_bit(j, bdata->node_bootmem_map))
                              goto   fail_block;
                  }
         函数要找到联系的areasize页面都是空闲的,如果不满足要求,就goto   fail_block;
                 start = i;
                  goto found;
          表示已经找到,从页面i开始
         found:
               if(align < PAGE_SIZE && bdata->last_offset && bdata->last_pos + 1 == start)
                     offset = (bdata->last_offset + align - 1) & ~(align - 1);  
               如果满足上面的,则offset 调整为align 对齐(注意这时候align < PAGE_SIZE),这样为了合并
               下面应该知道了吧,猜想应该是,首先会判断所得出上次分配页面剩余的大小,然后和现在分配大小
         进行比较,根据大小来分配不同页面
                     remaining_size = PAGE_SIZE - offset;
                     if(size < remaining_size)
                     {
                            areasize = 0;
                            bdata->last_offset = offset + size;
                            ret = phys_to_virt(bdata->last_pos * PAGE_SIZE + offset + bdata->node_boot_start);
                     }         
              如果size大小小于剩余大小,则不用分配页面,直接在当前页面中移动last_offset
                    else
                     {
                             remaining_size = size - remaining_size;
                             areasize = (remaining_size + PAGE_SIZE - 1)/PAGE_SIZE;
                             ret = phys_to_virt(bdata->last_pos * PAGE_SIZE + offset + bdata->node_boot_start);
                             bdata->last_pos = start + areasize - 1;
                             bdata->last_offset = remaining_size;                         
                     }
          如果上面的if语句三个条件有一个不满足,或者都不满足,则直接修改bdata里面的数据即可:
                else
                 {
                          bdata->last_pos = start + areasize - 1;
                          bdata->last_offset = size & ~PAGE_MASK;
                          ret = phys_to_virt(start * PAGE_SIZE + bdata->node_boot_start);
                 }
            #define   PAGE_MASK (~(PAGE_SIZE - 1))
            #define   PAGE_SIZE   (1UL<<PAGE_SHIFT)
            #define   PAGE_SHIFT      12  
       
原创粉丝点击