• 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:分配内存的大小
    3. align:对齐大小
    4. goal:限定范围的起始地址
    5. 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;

    }

     

    http://blog.chinaunix.net/uid-7588746-id-1629810.html

  • 相关阅读:
    DateTime类型的一个Bug
    无痛苦的软件维护——被遗忘的需求
    完全命令行.NET开发
    无痛苦的软件维护——文档和代码
    .NET初学者架构设计指南(一)Hello world的时代
    NGOSS的一点简单概念
    软件的逻辑层次
    VSTS for Testers学习笔记目录
    How Google Tests Software (出书,停止更新)
    推荐——《浪潮之巅》(据传稍后会出书,停止更新)
  • 原文地址:https://www.cnblogs.com/mull/p/4477844.html
Copyright © 2020-2023  润新知