Chinaunix首页 | 论坛 | 博客
  • 博客访问: 206674
  • 博文数量: 81
  • 博客积分: 55
  • 博客等级: 民兵
  • 技术积分: 26
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-21 17:54
文章分类
文章存档

2018年(1)

2017年(1)

2016年(5)

2015年(23)

2014年(51)

我的朋友

分类: LINUX

2014-03-13 21:08:43

 

bootmem内存的分配

 

__alloc_bootmem

通过bootmem分配器分配内存。

参数:

1)        size:分配内存的大小

2)        align:对齐大小

3)        goal:限定在某个范围内分配,goal是限定范围的起始地址

 

void * __init __alloc_bootmem(unsigned long size, unsigned long align,

                           unsigned long goal)

{

       /* limit为限定范围的截止地址,为0表示没有限制。 */

       unsigned long limit = 0;

 

#ifdef CONFIG_NO_BOOTMEM

       limit = -1UL;

#endif

       /* 在某个限定范围内分配内存 */

       return ___alloc_bootmem(size, align, goal, limit);

}

 

___alloc_bootmem

goallimit之间分配内存。

static void * __init ___alloc_bootmem(unsigned long size, unsigned long align,

                                   unsigned long goal, unsigned long limit)

{

       /* 直接调用___alloc_bootmem_nopanic */

       void *mem = ___alloc_bootmem_nopanic(size, align, goal, limit);

       /* 分配成功返回 */

       if (mem)

              return mem;

       /*

        * Whoops, we cannot satisfy the allocation request.

        */

       printk(KERN_ALERT "bootmem alloc of %lu bytes failed!\n", size);

       panic("Out of memory");

       return NULL;

}

 

___alloc_bootmem_nopanic

参数:

1)        size:分配内存的大小

2)        align:对齐大小

3)        goal:限定范围的起始地址

4)        limit:限定范围的截止地址

 

static void * __init ___alloc_bootmem_nopanic(unsigned long size,

                                   unsigned long align,

                                   unsigned long goal,

                                   unsigned long limit)

{

#ifdef CONFIG_NO_BOOTMEM

/* 如果没有bootmem,通过其他手段模拟bootmem,应该是针对x86架构的,不去管它 */

       void *ptr;

 

       if (WARN_ON_ONCE(slab_is_available()))

              return kzalloc(size, GFP_NOWAIT);

 

restart:

       ptr = __alloc_memory_core_early(MAX_NUMNODES, size, align, goal, limit);

 

       if (ptr)

              return ptr;

 

       if (goal != 0) {

              goal = 0;

              goto restart;

       }

 

       return NULL;

#else

       /* bootmem中分配 */

       bootmem_data_t *bdata;

       void *region;

 

restart:

       /* 从体系结构优先选择的节点分配bootmem */

       region = alloc_arch_preferred_bootmem(NULL, size, align, goal, limit);

       if (region)

              return region;

       /* 遍历bdata_list中的bootmemUMA只有一个节点 */

       list_for_each_entry(bdata, &bdata_list, list) {

/* 此节点bootmem的截止pfn在参数指定的起始pfn之下,不符合要求,跳过 */

              if (goal && bdata->node_low_pfn <= PFN_DOWN(goal))

                     continue;

/* 此节点bootmem的起始pfn在参数指定的截止pfn之上,不符合要求,跳过 */

              if (limit && bdata->node_min_pfn >= PFN_DOWN(limit))

                     break;

              /* 找到了符合条件的bootmem,从中分配内存 */

              region = alloc_bootmem_core(bdata, size, align, goal, limit);

              if (region)

                     return region;

       }

       /* 未找到符合条件的bootmem,去掉起始地址限制,重新扫描 */

       if (goal) {

              goal = 0;

              goto restart;

       }

 

       return NULL;

#endif

}

 

alloc_arch_preferred_bootmem

体系结构如果定义了优先选择的内存节点,从该节点的bootmem中分配。

static void * __init alloc_arch_preferred_bootmem(bootmem_data_t *bdata,

                                   unsigned long size, unsigned long align,

                                   unsigned long goal, unsigned long limit)

{

       /* 如果slab可用,使用slab分配器 */

       if (WARN_ON_ONCE(slab_is_available()))

              return kzalloc(size, GFP_NOWAIT);

 

#ifdef CONFIG_HAVE_ARCH_BOOTMEM

       {

              bootmem_data_t *p_bdata;

              /* 如果体系结构定义了优先选择的节点,获得该节点的bootmem */

              p_bdata = bootmem_arch_preferred_node(bdata, size, align,

                                                 goal, limit);

              /* 如果该节点bootmem有效,从中分配内存 */

              if (p_bdata)

                     return alloc_bootmem_core(p_bdata, size, align,

                                                 goal, limit);

       }

#endif

       return NULL;

}

 

__alloc_bootmem_core

Bootmem分配器的核心函数,从某内存节点的bootmem中分配内存,即将位图相关比特位置1,设为保留的内存区。

参数:

1)        bdata:某内存节点的bootmem

2)        size:分配内存的大小

5)        align:对齐大小

6)        goal:限定范围的起始地址

7)        limit:限定范围的截止地址

 

static void * __init alloc_bootmem_core(struct bootmem_data *bdata,

                                   unsigned long size, unsigned long align,

                                   unsigned long goal, unsigned long limit)

{

       unsigned long fallback = 0;

       unsigned long min, max, start, sidx, midx, step;

 

       bdebug("nid=%td size=%lx [%lu pages] align=%lx goal=%lx limit=%lx\n",

              bdata - bootmem_node_data, size, PAGE_ALIGN(size) >> PAGE_SHIFT,

              align, goal, limit);

       /* 分配的内存大小不能为0 */

       BUG_ON(!size);

       /* 对齐大小必须是2的幂数 */

       BUG_ON(align & (align - 1));

       /* 起始地址+申请的内存大小>截止地址,参数错误 */

       BUG_ON(limit && goal + size > limit);

       /* 未初始化此bootmem的位图,返回 */

       if (!bdata->node_bootmem_map)

              return NULL;

       /* min表示bootmem的起始pfn */

       min = bdata->node_min_pfn;

/* max表示bootmem的截止pfn */

       max = bdata->node_low_pfn;

       /* 限定范围的起止地址转换为pfn */

       goal >>= PAGE_SHIFT;

       limit >>= PAGE_SHIFT;

       /* 参数指定的截止地址更小,以参数指定的截止地址为准 */

       if (limit && max > limit)

              max = limit;

       /* 起止地址错误,返回 */

       if (max <= min)

              return NULL;

/* step为扫描位图时,每次递增的页数/pfn偏移数。由对齐大小计算而来,不足一页时,step1,表示每次递增一页 */

       step = max(align >> PAGE_SHIFT, 1UL);

      

       if (goal && min < goal && goal < max)

/* 参数指定的起始pfn更大,根据它计算起始pfn,并按申请的页数对齐*/

              start = ALIGN(goal, step);

       else

              /* bootmem的起始pfn */

              start = ALIGN(min, step);

       /* 计算起始pfnbootmem起始pfn的偏移,后面扫描位图时从这个sidx开始 */

       sidx = start - bdata->node_min_pfn;

/* 计算截止pfnbootmem起始pfn的偏移。扫描的就是sidxmidx之间的这部分bootmem内存区 */

       midx = max - bdata->node_min_pfn;

       /* hint_idx表示优先扫描的偏移,如果大于sidx,则从hint_idx处开始扫描。

何为优先扫描呢?由于分配是从低地址开始,显然下一次扫描时,从上一次分配的截止地址开始扫描成功率会更高。hint_idx之前的空间可能由于对齐的原因仍是空闲的。释放bootmem时会更新hint_idx的值,指向释放的位置。仅当优先扫描失败,才需要回过头来扫描以前分配过的区域。

*/

       if (bdata->hint_idx > sidx) {

              /*

               * Handle the valid case of sidx being zero and still

               * catch the fallback below.

               */

              /* fallback表示首次扫描是否优先扫描 */

              fallback = sidx + 1;

              /* 从优先扫描偏移处开始扫描 */

              sidx = align_idx(bdata, bdata->hint_idx, step);

       }

 

       while (1) {

              int merge;

              void *region;

              unsigned long eidx, i, start_off, end_off;

/* 从位图中找到满足对齐要求的、为0的、连续的比特位 */

find_block:

              /* 从位图中找到下一个为0的比特位 */

              sidx = find_next_zero_bit(bdata->node_bootmem_map, midx, sidx);

              /* sidx按照申请的页数对齐,对齐后的比特位不一定是0 */

              sidx = align_idx(bdata, sidx, step);

              /* 计算结尾pfn */

              eidx = sidx + PFN_UP(size);

/* 如果超出了截止pfn,整个位图扫描完毕,未找到符合条件的空闲内存区,跳出循环 */

              if (sidx >= midx || eidx > midx)

                     break;

              /* 检查sidxeidx之间的所有比特位是否全部为0 */

              for (i = sidx; i < eidx; i++)

                     if (test_bit(i, bdata->node_bootmem_map)) {

                            /* 某比特位为1,这段区间不符合条件,从下一个对齐偏移处继续                                       扫描 */

                            sidx = align_idx(bdata, i, step);

/* 前面对齐采用的是入式对齐,如果是第一个比特位为1,即对齐余数为0,没有入上去,需要加上step */

                            if (sidx == i)

                                   sidx += step;

                            /* 继续扫描 */

                            goto find_block;

                     }

/* last_end_off表示上一次分配截止地址的偏移。如果其不是页面大小对齐的,说明该页中还有空闲的空间。并且如果其所在的页面就是扫描到的内存区的上一页,则可以利用该页的空闲空间。*/

              if (bdata->last_end_off & (PAGE_SIZE - 1) &&

                            PFN_DOWN(bdata->last_end_off) + 1 == sidx)

/* start_off表示本次分配的内存区起始处的地址偏移,需要按照指定方式对齐 */

                     start_off = align_off(bdata, bdata->last_end_off, align);

              else

/* 否则的话,就是从新的页面开始分配了,直接通过pfn偏移计算地址偏移 */

                     start_off = PFN_PHYS(sidx);

              /* 如果使用了上一页的空闲空间,该页对应的比特位无需置1,已经置过了 */

              merge = PFN_DOWN(start_off) < sidx;

              /* 计算本次分配的截止地址偏移 */

              end_off = start_off + size;

/* 用本次分配的截止地址偏移更新last_end_off */

              bdata->last_end_off = end_off;

/* 更新hint_idxlast_end_off之后第一个可用的pfn,即下一次优先扫描的位置 */

              bdata->hint_idx = PFN_UP(end_off);

 

              /*

               * Reserve the area now:

               */

/* 将要分配出去的内存区对应的位图比特位置1BOOTMEM_EXCLUSIVE表示此次分配是排它的 */

              if (__reserve(bdata, PFN_DOWN(start_off) + merge,

                            PFN_UP(end_off), BOOTMEM_EXCLUSIVE))

                     BUG();

              /* 将要分配出去的内存区清0 */

              region = phys_to_virt(PFN_PHYS(bdata->node_min_pfn) +

                            start_off);

              memset(region, 0, size);

              /*

               * The min_count is set to 0 so that bootmem allocated blocks

               * are never reported as leaks.

               */

              /* 内存泄露检测相关,不去管它 */

              kmemleak_alloc(region, size, 0, 0);

              return region;

       }

/* 走到这说明优先扫描没有找到符合条件的空闲内存区,扫描初始sidxfallback记录)之前的区域 */

       if (fallback) {

              sidx = align_idx(bdata, fallback - 1, step);

              /* 扫描一次即可 */

              fallback = 0;

              goto find_block;

       }

       /* 还没有找到,分配失败 */

       return NULL;

}

 

 __reserve

设置某块bootmem内存为保留区。

static int __init __reserve(bootmem_data_t *bdata, unsigned long sidx,

                     unsigned long eidx, int flags)

{

       unsigned long idx;

       /* BOOTMEM_EXCLUSIVE表示是否要求独占这块bootmem */

       int exclusive = flags & BOOTMEM_EXCLUSIVE;

 

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

              bdata - bootmem_node_data,

              sidx + bdata->node_min_pfn,

              eidx + bdata->node_min_pfn,

              flags);

 

       for (idx = sidx; idx < eidx; idx++)

              if (test_and_set_bit(idx, bdata->node_bootmem_map)) {

                     /* 在这之前已经置为1,说明有人正在使用这块内存 */

                     if (exclusive) {

                            /* 如果是独占型的,独占失败,将之前已经置1的比特位清0 */

                            __free(bdata, sidx, idx);

                            /* 返回错误 */

                            return -EBUSY;

                     }

                     /* 非独占,打印重复置1的提示信息 */

                     bdebug("silent double reserve of PFN %lx\n",

                            idx + bdata->node_min_pfn);

              }

       return 0;

}

 

阅读(834) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~