• 内存管理初始化源码3:bootmem


      start_kernel ——> setup_arch ——> arch_mem_init ——> bootmem_init ——> init_bootmem_node:

      此时,不得不说的就是 bootmem 。

    1. 什么是bootmem:

      我们都知道,所有的物理内存是交给内核管理的,或者说是交给内存管理子系统管理的。那么,从内核启动到内核管理子系统启动之间,是否需要内存呢?答案是肯定的,该时间段内是需要物理内存的。

      那么bootmem就是负责该时间段的物理内存的分配。

    2. 特性:

      简单

      该分配器的需求集中于简单性方面,而不是性能和通用性。因此内核开发者决定实现一个最先适配(first-fit)分配器用于在启动阶段管理内存。

    3. 基本原理

      用一个位图来管理页,位图比特位的数目与系统中物理内存页的数据相同。比特位为1,表示已用页;比特位为0,表示空闲页。

      在需要分配内存时,分配器逐位扫描位图,直至找到一个能够提供足够连续页的位置,即所谓的最先最佳(first-best)或最先适配的位置。

    4. 初始化

    /**
     * init_bootmem_node - register a node as boot memory
     * @pgdat: node to register                        【属于某个内存结点的bootmem】
     * @freepfn: pfn where the bitmap for this node is to be placed   【该内存结点的物理内存页的位图所存内存的pfn】
     * @startpfn: first pfn on the node                   【该内存的结点的 first pfn】
     * @endpfn: first pfn after the node                     【该内存将诶点的 end pfn】
     *
     * Returns the number of bytes needed to hold the bitmap for this node. 【返回管理该结点所有内存页的位图所需的总字节数】
     */
    unsigned long __init init_bootmem_node(pg_data_t *pgdat, unsigned long freepfn,
                    unsigned long startpfn, unsigned long endpfn)
    {
        return init_bootmem_core(pgdat->bdata, freepfn, startpfn, endpfn);
    }
    /**
    * 我们传递的参数是: bootmap_size = init_bootmem_node(NODE_DATA(0), mapstart, min_low_pfn, max_low_pfn);
    * mapstart = 2358, min_low_pfn = 0, max_low_pfn = 131072
    * 注意:1. mapstart之前的页是存储了initrd相关数据,上文已经解释过
    * 2. max_low_pfn 为131072是由于此时是将 0 ~ 512M 都认为是低端内存,将 0 ~ 512M 之间的所有物理页都建立了bitmap,其实,我们的低端物理内存页只有 0 ~ 57344
    */
    /*
     * Called once to set up the allocator itself.
     */
    static unsigned long __init init_bootmem_core(bootmem_data_t *bdata,
        unsigned long mapstart, unsigned long start, unsigned long end)
    {
        unsigned long mapsize;
    
        mminit_validate_memmodel_limits(&start, &end);         // start 和 end 的合法性检测
        bdata->node_bootmem_map = phys_to_virt(PFN_PHYS(mapstart)); // mapstart是存储位图的pfn,转换为相应的虚拟地址
        bdata->node_min_pfn = start;                    // 记录 node_min_pfn
        bdata->node_low_pfn = end;                     // 记录 node_low_pfn
        link_bootmem(bdata);                        // bdata和什么做关联?
    
        /*
         * Initially all pages are reserved - setup_arch() has to
         * register free RAM areas explicitly.
    */
      // 初始化所有保留的页——setup_arch()必须精确的注册所有的RAM区域
    mapsize = bootmap_bytes(end - start);             // 计算 bitmap 所需的 bytes memset(bdata->node_bootmem_map, 0xff, mapsize);     // 将 bitmap 区域设置 0xff bdebug("nid=%td start=%lx map=%lx end=%lx mapsize=%lx ", bdata - bootmem_node_data, start, mapstart, end, mapsize); return mapsize; }
    ...
    static struct list_head bdata_list __initdata = LIST_HEAD_INIT(bdata_list);
    ...

    /*
    * link bdata in order  【将bdata按顺序连接到临时链表 bdata_list中】 */ static void __init link_bootmem(bootmem_data_t *bdata) { struct list_head *iter; list_for_each(iter, &bdata_list) { bootmem_data_t *ent; ent = list_entry(iter, bootmem_data_t, list); if (bdata->node_min_pfn < ent->node_min_pfn) break; } list_add_tail(&bdata->list, iter);

      /* 等价于 */   for (iter = (&bdata_list)->next; iter != (&bdata_list); iter = iter->next) {
        bootmem_data_t *ent;
        ent = container_of(iter, bootmem_data_t, list);
        if (bdata->node_min_pfn < ent->node_min_pfn)
          break;
      }
      list_add_tail(&bdata->list, iter); // 将该结点的 bdata 连接到临时链表 bdata_list中, bdata_list的定义在 bootmem.c中
    }
    static unsigned long __init bootmap_bytes(unsigned long pages)
    {
        unsigned long bytes = (pages + 7) / 8;
    
        return ALIGN(bytes, sizeof(long));
    }

    5. 将低端内存交给 bootmem allocator 管理

    /**
     * free_bootmem - mark a page range as usable
     * @addr: starting address of the range
     * @size: size of the range in bytes
     *
     * Partial pages will be considered reserved and left as they are.
     *
     * The range must be contiguous but may span node boundaries.
     */
    /**
    * 功能:标志一个 page 为可用状态
    * @addr : 标记范围的开始地址
    * @size : 标记范围的大小(bytes)
    * 范围必须是连续的,但可能跨节点的边界。
    */
    void __init free_bootmem(unsigned long addr, unsigned long size) { unsigned long start, end; kmemleak_free_part(__va(addr), size); // 内核内存泄露检测 start = PFN_UP(addr); end = PFN_DOWN(addr + size); mark_bootmem(start, end, 0, 0); }
    /**
    * free_bootmem(PFN(start), size << PAGE_SHIFT);   // start = 2358, size = end - start = 53744 - 2358
    * 虽然我们在建立位图时建立的是整个低端内存的位图,但其实我们真正可用的物理内存只有这些。
    */
    static int __init mark_bootmem(unsigned long start, unsigned long end,
                    int reserve, int flags)
    {
        unsigned long pos;
        bootmem_data_t *bdata;
    
        pos = start;
        list_for_each_entry(bdata, &bdata_list, list) {
            int err;
            unsigned long max;
    
            if (pos < bdata->node_min_pfn ||
                pos >= bdata->node_low_pfn) {
                BUG_ON(pos != start);
                continue;
            }
    
            max = min(bdata->node_low_pfn, end);  // 此时:bdata->node_low_pfn = 131072(对应512M), end = 57344(对应256M)
    
            err = mark_bootmem_node(bdata, pos, max, reserve, flags);
            if (reserve && err) {
                mark_bootmem(start, pos, 0, 0);
                return err;
            }
    
            if (max == end)
                return 0;
            pos = bdata->node_low_pfn;
        }
        BUG();
    }
    static int __init mark_bootmem_node(bootmem_data_t *bdata,
                    unsigned long start, unsigned long end,
                    int reserve, int flags)
    {
        unsigned long sidx, eidx;
    
        bdebug("nid=%td start=%lx end=%lx reserve=%d flags=%x
    ",
            bdata - bootmem_node_data, start, end, reserve, flags);
    
        BUG_ON(start < bdata->node_min_pfn);
        BUG_ON(end > bdata->node_low_pfn);
    
        printk("%d : start = %lu, end = %lu
    ", __LINE__, start, end);
    
        sidx = start - bdata->node_min_pfn;  // start = 2350, bdata->node_min_pfn = 0
        eidx = end - bdata->node_min_pfn;    // end = 53744, bdata->node_min_pfn = 0
    
        printk("%d : sidx = %lu, eidx = %lu
    ", __LINE__, sidx, eidx);
    
        if (reserve)
            return __reserve(bdata, sidx, eidx, flags);   // 此时的 _reserve 和 _free 猜测就可知, _reserve 是 set bit, _free 是 clear bit.
        else
            __free(bdata, sidx, eidx);
        return 0;
    }

      上述代码从细节上分析了 bootmem 的初始化及如何将低端内存交给 bootmem 管理; 那么如何分配内存及释放内存稍后解释,我们从宏观看看 bootmem 的这些函数。

    1. 初始化:

      init_bootmem_node:初始化某个内存结点的 bootmem,具体是哪个内存结点由使用者指定。用于初始化具有多个内存结点的系统(NUMA)。

      init_bootmem          :初始化内存结点为0的 bootmem。用于初始化只有一个内存结点的系统。只能用于初始化(UMA),而 init_bootmem_node也可以初始化UMA。

    2. 释放:

      free_all_bootmem_node:释放某个内存结点的内存给 buddy allocator(NUMA)

      free_all_bootmem          :释放空闲页给 buddy allocator(UMA)

      首先扫描 bootmem 分配器的页位图,释放每个未用的页,到伙伴系统的接口是 __free_pages_bootmem函数,该函数对每个空闲页调用。该函数内部依赖于标准函数 __free_page。它使得这些页并入伙伴系统的数据结构,在其中作为空闲页管理,可用于分配。

      在位图已经完全扫描之后,它占据的内存空间也必须释放。此后,只有伙伴系统可以分配内存。

  • 相关阅读:
    摒弃FORM表单上传图片,异步批量上传照片
    小功能——简单代码实现邮箱发送邮件
    小工具 ——快速生成验证码
    [转]C++11 多线程
    [转]线性插值&双线性插值&三线性插值
    [转]第四章 使用OpenCV探测来至运动的结构——Chapter 4:Exploring Structure from Motion Using OpenCV
    windows的Timer和写文件方式串口注意!
    OPENCV3.1+VS 坑我笔记!
    最简单的PC机串口通信程序
    用MFC时,如果程序崩溃,检查内存,然后注意GDI数量,在任务管理器里选项-查看列-GDI数量
  • 原文地址:https://www.cnblogs.com/ronnydm/p/5917442.html
Copyright © 2020-2023  润新知