当前位置: 首页 > article >正文

VMEMMAP分析

VMEMMAP分析

  • 前言
  • 代码分析
    • memblocks_present
      • memory_present
      • sparse_index_init
    • first_present_section_nr
    • sparse_init_nid
      • __populate_section_memmap
      • pfn_to_page和page_to_pfn
      • vmemmap_populate
        • vmemmap_pgd_populate
        • vmemmap_pud_populate
        • vmemmap_alloc_block_zero
        • 问:什么arm64架构的vmemmap只映射到PMD
        • vmemmap_alloc_block_buf
      • sparse_init_one_section
  • 参考文档


前言

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如下图,第二种已经被sparsemem淘汰了
在这里插入图片描述
vmemmap是内核中page 数据的虚拟地址。针对sparse内存模型。内核申请page获取的page地址从此开始

在这里插入图片描述
综上所述,vmemmap就是针对sparse内存模型管理page的一种技术,使用虚拟映射的内存映射来优化pfn_to_page和page_to_pfn操作
在这里插入图片描述
内核中关于虚拟地址和物理地址相互转化的代码如下:
在这里插入图片描述

代码分析

在这里插入图片描述
这里我们直接从sparse_init开始分析,这个函数会按照子函数挨个进行详细分析

/*
 * Allocate the accumulated non-linear sections, allocate a mem_map
 * for each and record the physical to section mapping.
 */
void __init sparse_init(void)
{
    unsigned long pnum_end, pnum_begin, map_count = 1;
    int nid_begin;
	/* 下面详细分析 */
    memblocks_present();
	/* 下面详细分析 */
pnum_begin = first_present_section_nr();
// 在memblocks_present 中会设置section_mem_map=nid<<3
// 这里解析出来就是内存nid的值
    nid_begin = sparse_early_nid(__nr_to_section(pnum_begin));

    /* Setup pageblock_order for HUGETLB_PAGE_SIZE_VARIABLE */
set_pageblock_order();  //没定义HUGETLB就啥也不做
// 这里pnum_end = -1 (8字节的全f)
// 该函数最后会调用next_present_section_nr进行for循环
// 也就是从第一个在线的内存段一直for到最后一个在线的内存段
for_each_present_section_nr(pnum_begin + 1, pnum_end) {
	// 这里pnum_end=最后一个在线内存段,并获取该内存段nid
        int nid = sparse_early_nid(__nr_to_section(pnum_end));
		// 对于我手中的板子,都是一个内存,nid都是一样的
		// for到最后(从内存段34+1开始一直到56)
        if (nid == nid_begin) {
			// map_count从1开始自加,一直加到轮询完全部在线内存段
            map_count++; 
            continue;
        }
		// 多内存的情况会在这里执行,否则不会在这里执行
        /* Init node with sections in range [pnum_begin, pnum_end) */
        sparse_init_nid(nid_begin, pnum_begin, pnum_end, map_count);
        nid_begin = nid;
        pnum_begin = pnum_end;
        map_count = 1;
    }
/* cover the last node */
// 下面详解,进行vmemmap映射
    sparse_init_nid(nid_begin, pnum_begin, pnum_end, map_count);
    vmemmap_populate_print_last();
}

memblocks_present

memblocks_present函数主要是通过for_each_mem_pfn_range遍历memblock管理的全部内存区域,获取对应nid,起始页帧和结束页帧,然后传参给memory_present进行处理

/*
 * Mark all memblocks as present using memory_present().
 * This is a convenience function that is useful to mark all of the systems
 * memory as present during initialization.
 */
static void __init memblocks_present(void)
{
    unsigned long start, end;
    int i, nid;
	/* 定义见下 */
for_each_mem_pfn_range(i, MAX_NUMNODES, &start, &end, &nid)
	// 下面详细分析
        memory_present(nid, start, end);
}

#define MAX_NUMNODES    (1 << NODES_SHIFT) // NODES_SHIFT = 0
// 下面详细分析
#define for_each_mem_pfn_range(i, nid, p_start, p_end, p_nid)       \
    for (i = -1, __next_mem_pfn_range(&i, nid, p_start, p_end, p_nid); \
         i >= 0; __next_mem_pfn_range(&i, nid, p_start, p_end, p_nid))

在执行for_each_mem_pfn_range时,i被赋值为-1,nid被赋值为MAX_NUMNODES,然后要获取p_start, p_end, p_nid这3个参数。我们来具体分析一下输入以上两个值后的执行效果
在这里插入图片描述

void __init_memblock __next_mem_pfn_range(int *idx, int nid,
                unsigned long *out_start_pfn,
                unsigned long *out_end_pfn, int *out_nid)
{
	// 我手中的设备只有一颗ddr,因此memblock管理的memory的cnt=1
    struct memblock_type *type = &memblock.memory;
    struct memblock_region *r;
    int r_nid;

    // 首先或对*idx值自加 即此时i=0 type->cnt=1
    while (++*idx < type->cnt) {
        r = &type->regions[*idx];
		// r_nid = r->nid = 0
        r_nid = memblock_get_region_node(r);

        printk("gytest __next_mem_pfn_range: idx:%d r_nid:%d\n", *idx, r_nid);
		// PFN_UP(r->base) = 139264  -> 0x2200_0000 >> 12
		// PFN_DOWN(r->base + r->size) = 233472 –> size= 0x1700_0000
		// #define PFN_UP(x)   (((x) + PAGE_SIZE-1) >> PAGE_SHIFT)
		// #define PFN_DOWN(x) ((x) >> PAGE_SHIFT)
        if (PFN_UP(r->base) >= PFN_DOWN(r->base + r->size))
            continue; //因此这里不满足条件
		// 
        if (nid == MAX_NUMNODES || nid == r_nid)
            break;
	}
	// 首次执行到这里后 i = 0,然后在
	// for_each_mem_pfn_range i >= 0 的条件中,还是满足的,
	// 因此至少还会在执行一次,直到i >= type->cnt
	// 也就是说对于有多个region的设备来说(多个内存的情况)
	// 会完全遍历所有内存区域,获取对硬内存区的nid,起始页帧和结束页帧
    if (*idx >= type->cnt) {
        *idx = -1;
        return;
    }

	// 最后将结果进行赋值,获取了实际的页帧起始和结束
    if (out_start_pfn)
        *out_start_pfn = PFN_UP(r->base);
    if (out_end_pfn)
        *out_end_pfn = PFN_DOWN(r->base + r->size);
    if (out_nid)
        *out_nid = r_nid;
    printk("gytest __next_mem_pfn_range: out_start_pfn:%ld 
    out_end_pfn:%ld out_nid:%d\n", *out_start_pfn, *out_end_pfn,
           *out_nid);
}

memory_present

然后我们在详细分析一下memory_present。对于我手中的设备来说,只有一个ddr因此nid=0,起始页帧start=139264,结束页帧end=233472,程序中几个关键的宏大小如下:

NR_SECTION_ROOTS:65536
SECTIONS_PER_ROOT:256
SECTIONS_SHIFT:24
MAX_PHYSMEM_BITS:48
SECTION_SIZE_BITS:24

/* Record a memory area against a node. */
static void __init memory_present(int nid, unsigned long start, unsigned long end)
{
    unsigned long pfn;

#ifdef CONFIG_SPARSEMEM_EXTREME
	// 如果定义了(也就是走二级指针的方案,动态分配mem_section)
    if (unlikely(!mem_section)) {
        unsigned long size, align;
        // 结构体指针的大小为8 最后size=524288
        size = sizeof(struct mem_section *) * NR_SECTION_ROOTS;
        align = 1 << (INTERNODE_CACHE_SHIFT); //对齐大小=64
		// 从memblock中预留512K(size大小)
        mem_section = memblock_alloc(size, align);
        if (!mem_section)
            panic("%s: Failed to allocate %lu bytes align=0x%lx\n",
                  __func__, size, align);
    }
#endif

	start &= PAGE_SECTION_MASK;
	// 校验起始页帧或结束页帧是否大于稀疏内存管理的最大页帧
	// 如果某项大于就等于最大页帧
	// 稀疏内存管理的最大页帧为:1<<( MAX_PHYSMEM_BITS - PAGE_SHIFT)
	// 即1<<(48-12)
	mminit_validate_memmodel_limits(&start, &end);

	// #define PAGES_PER_SECTION  (1UL << PFN_SECTION_SHIFT)
	// #define PFN_SECTION_SHIFT   (SECTION_SIZE_BITS - PAGE_SHIFT)
	// 即PAGES_PER_SECTION = (1<<(24-12)) = 4096
	// 也就是说:一个内存段可以管理4096个page
	// 注意:SECTION_SIZE_BITS可以是架构支持的任意值,我这里修改成了24
	for (pfn = start; pfn < end; pfn += PAGES_PER_SECTION) {
	// 从开始页帧,按内存段计算,一直for到结束页帧
	// 将页帧号转为断号,就是pfn >> PFN_SECTION_SHIFT;
        unsigned long section = pfn_to_section_nr(pfn);
        struct mem_section *ms;
		// 下面详细分析
		// 对于传入的起始和结束页对应的section分别为34和57
	// 也就是34~56,for到56就结束了
        sparse_index_init(section, nid); //这里申请root=0 的二级空间
		// 设置该内存段属于哪个内存节点,nid都是0
        set_section_nid(section, nid);

        ms = __nr_to_section(section);
		// 标记该内存段在线,正在使用
        if (!ms->section_mem_map) {
            ms->section_mem_map = sparse_encode_early_nid(nid) |
                            SECTION_IS_ONLINE;
            section_mark_present(ms);
        }
    }
}

sparse_index_init

这里面涉及到一个root的概念,实际上就是vmemmap是如何管理页帧的,我们先看一张图。如下图,页帧被分成了3段概念:

  • 第一个概念是root_num:用于标明该页帧属于二级指针第一级的那个位置(即mem_section[root_num][x])。
  • 第二个概念是sections_per_root:用于标明该页帧属于二级指针第二级的位置(即mem_section[root_num][ sections_per_root])。
  • 第三个概念当然就是pfn_section_shift了,标明一段内存的大小,也就是内存段大小。这个值决定了具体page在内存段的那个位置,至于page_shift决定了具体page的具体地址

在这里插入图片描述

static int __meminit sparse_index_init(unsigned long section_nr, int nid)
{
	// #define SECTION_NR_TO_ROOT(sec) ((sec) / SECTIONS_PER_ROOT)
	// #define SECTIONS_PER_ROOT (PAGE_SIZE/sizeof(struct mem_section))
	// 其中结构体mem_section的大小为16字节
	// 即:sections_per_root的大小是固定的,256
	// 也就是说一页可以管理256个内存段
	// root = section_nr / 256
    unsigned long root = SECTION_NR_TO_ROOT(section_nr);
	struct mem_section *section;

	// 该root的内存段的第二级如果已经申请的话,直接返回
    if (mem_section[root])
        return 0;
	// 否则申请空间,这里是申请了一个page出来
    section = sparse_index_alloc(nid);
    if (!section)
        return -ENOMEM;
	// 然后申请的空间给到第一级(实际就是申请了二维数据的第二维的空间)
    mem_section[root] = section;

    return 0;
}

first_present_section_nr

该函数实际上就是找到第一个在线的内存段,因为在前面的memblocks_present中已经对memblock管理的全部内存段置成在线了

static inline unsigned long first_present_section_nr(void)
{
    return next_present_section_nr(-1);
}
static inline unsigned long next_present_section_nr(unsigned long section_nr)
{
	// 一直while到最大内存段
	while (++section_nr <= __highest_present_section_nr) {
	// 如果找到在线的内存段,返回内存段段号
        if (present_section_nr(section_nr))
            return section_nr;
    }

    return -1;
}

sparse_init_nid

对于我手中的设备,入参nid=0,pnum_begin=34,pnum_end=最大,map_count=23

/*
 * Initialize sparse on a specific node. The node spans [pnum_begin, pnum_end)
 * And number of present sections in this node is map_count.
 */
static void __init sparse_init_nid(int nid, unsigned long pnum_begin,
                   unsigned long pnum_end,
                   unsigned long map_count)
{
    struct mem_section_usage *usage;
    unsigned long pnum;
    struct page *map;
	// 申请了一个bitmap,usage_size大小16字节,共申请368字节
    usage = sparse_early_usemaps_alloc_pgdat_section(NODE_DATA(nid),
            mem_section_usage_size() * map_count);
    if (!usage) {
        pr_err("%s: node[%d] usemap allocation failed", __func__, nid);
        goto failed;
	}
	// sizeof(struct page):64 PAGES_PER_SECTION:4096 乘积为256K
	// PMD_SIZE:2097152 PMD_SHIFT:21 section_map_size:2097152 
	// 这里显然按照更大的pmd_size对齐的
	// 这里申请了23*2M的内存
    sparse_buffer_init(map_count * section_map_size(), nid);
    
	// 依旧是遍历所有在线的内存段
    for_each_present_section_nr(pnum_begin, pnum) {
        unsigned long pfn = section_nr_to_pfn(pnum);

        if (pnum >= pnum_end)
            break;
		// 下面重点分析
        map = __populate_section_memmap(pfn, PAGES_PER_SECTION, nid, NULL);
        if (!map) {
			// 失败处理
            pr_err("%s: node[%d] memory map backing failed. Some memory will not be available.",
                   __func__, nid);
            pnum_begin = pnum;
            sparse_buffer_fini();
            goto failed;
        }
		// 不使能内存热拔什么都不做
        check_usemap_section_nr(nid, usage);
		// 下面分析
        sparse_init_one_section(__nr_to_section(pnum), pnum, map, usage,
                SECTION_IS_EARLY);
        usage = (void *) usage + mem_section_usage_size();
	}
	// 将没使用的预留内存都释放掉,前面申请了23*2M的内存
	// 实际只使用了4*2M内存 因此剩下的都释放
    sparse_buffer_fini();
    return;
failed:
    /* We failed to allocate, mark all the following pnums as not present */
    for_each_present_section_nr(pnum_begin, pnum) {
        struct mem_section *ms;

        if (pnum >= pnum_end)
            break;
        ms = __nr_to_section(pnum);
        ms->section_mem_map = 0;
    }
}

__populate_section_memmap

入参pfn从34一直遍历到56,nr_pages=4096,nid一直为0,最后一个参数一直为NULL,注意该函数为vmemmap的实现方式,如果不启用vmemmap则实现方式完全不同

struct page * __meminit __populate_section_memmap(unsigned long pfn,
        unsigned long nr_pages, int nid, struct vmem_altmap *altmap)
{
	// 这里直接返回了vmemmap映射的该页帧的虚拟地址
	unsigned long start = (unsigned long) pfn_to_page(pfn);
	// 计算结束地址
    unsigned long end = start + nr_pages * sizeof(struct page);
	// 确保是对齐的,其实不对齐也没什么关系(笑,因此我这么用过)
    if (WARN_ON_ONCE(!IS_ALIGNED(pfn, PAGES_PER_SUBSECTION) ||
        !IS_ALIGNED(nr_pages, PAGES_PER_SUBSECTION)))
        return NULL;
    // 做实际物理映射,下面详细分析
    if (vmemmap_populate(start, end, nid, altmap))
        return NULL;
	// 最后返回该页帧对应的page的地址(虚拟)
    return pfn_to_page(pfn);
}

其中vmemmap是按照struct page* 对齐的而 139264 = 0x22000,也就是说经过页帧转化后的start实际地址是 vmemmap + pfn*64(注:我手里的设备的vmemmap:0xfffffffeffe00000,memstart_addr:0x0)

在这里插入图片描述
在这里插入图片描述

pfn_to_page和page_to_pfn

这里涉及到了页帧到page的转化,分析一下定义

#define page_to_pfn __page_to_pfn
#define pfn_to_page __pfn_to_page

#if defined(CONFIG_SPARSEMEM_VMEMMAP)
// 对比非vmemmap映射少了很多函数转化的过程
#define __pfn_to_page(pfn)  (vmemmap + (pfn))
#define __page_to_pfn(page) (unsigned long)((page) - vmemmap)
#elif defined(CONFIG_SPARSEMEM)
// 这里我们先简单分析一下不带vmemmap的转化,因为比较清晰
#define __page_to_pfn(pg)                   \
({  const struct page *__pg = (pg);             \
    int __sec = page_to_section(__pg);          \
    (unsigned long)(__pg - __section_mem_map_addr(__nr_to_section(__sec))); \
})

#define __pfn_to_page(pfn)              \
({  unsigned long __pfn = (pfn);            \
    struct mem_section *__sec = __pfn_to_section(__pfn);    \
    __section_mem_map_addr(__sec) + __pfn;      \
})
#endif

/* 注意:以下都是非vmemmap才会这样用 */
static inline struct mem_section *__pfn_to_section(unsigned long pfn)
{
    return __nr_to_section(pfn_to_section_nr(pfn));
}

static inline unsigned long pfn_to_section_nr(unsigned long pfn)
{
	// 返回nr: 页帧 >> 12,获取了mem_section的二级指针的第二级
	// 即mem_section[root][nr]
    return pfn >> PFN_SECTION_SHIFT;
}

static inline struct mem_section *__nr_to_section(unsigned long nr)
{
#ifdef CONFIG_SPARSEMEM_EXTREME
    if (!mem_section)
        return NULL;
#endif
    if (!mem_section[SECTION_NR_TO_ROOT(nr)])
        return NULL;
	// 通过nr获取最后的指针,可以看前面sparse_index_init解析的映射关系
    return &mem_section[SECTION_NR_TO_ROOT(nr)][nr & SECTION_ROOT_MASK];
}

static inline struct page *__section_mem_map_addr(struct mem_section *section)
{
	// 返回段对应的mem_map这个地址
unsigned long map = section->section_mem_map;
// 低4位用于其他标记了,需要屏蔽低4位
map &= SECTION_MAP_MASK;
// page先直接指向这个地址
    return (struct page *)map;
}

/* 注意:以下都是vmemmap才会这样用 */
// vmemmap地址 = VMEMMAP虚拟地址 – 物理内存起始地址 >> 12 (按页偏移)
// 注意:这里反直觉的在于vmemmap按页帧加一次的地址偏移是64字节
#define vmemmap         ((struct page *)VMEMMAP_START - (memstart_addr >> PAGE_SHIFT))


vmemmap_populate

直接来到页表映射环节,注:该代码由架构进行支持(前言的官方文档截图已经给出说明),位置于:arch\arm64\mm\mmu.c

int __meminit vmemmap_populate(unsigned long start, unsigned long end, 
int node, struct vmem_altmap *altmap)
{
    unsigned long addr = start;
    unsigned long next;
    pgd_t *pgdp;
    p4d_t *p4dp;
    pud_t *pudp;
    pmd_t *pmdp;
    
	do {
		// next按照pmd_size(2M)进行递增,如果addr+pmd_size > end
		// next = end,否则next = addr+pmd_size
        next = pmd_addr_end(addr, end);
		// 下面分析
        pgdp = vmemmap_pgd_populate(addr, node);
        if (!pgdp)
            return -ENOMEM;
		// 我的设备没有p4d这级页表 p4d = pgb
        p4dp = vmemmap_p4d_populate(pgdp, addr, node);
        if (!p4dp)
            return -ENOMEM;
		// 但是pud这一级的pud_none不在无条件返回0了
		// 需要简单分析该函数
        pudp = vmemmap_pud_populate(p4dp, addr, node);
        if (!pudp)
            return -ENOMEM;
		// pmd是有的,页表偏移查找不分析
        pmdp = pmd_offset(pudp, addr);
        if (pmd_none(READ_ONCE(*pmdp))) {
            void *p = NULL;
			// 如果这级页表没有被映射的话,开始做映射
			// 问:为什么只映射到了PMD这一层?下面解释
			// 下面详细分析该函数
            p = vmemmap_alloc_block_buf(PMD_SIZE, node, altmap);
            if (!p)
                return -ENOMEM;
			// 给pmd页表设置实际指向的物理地址
			// 实际上就是从0x3600_0000->0x3680_0000(对于我手中的设备)
            pmd_set_huge(pmdp, __pa(p), __pgprot(PROT_SECT_NORMAL));
        } else
			// 校验该表项对应的内容(指向的实际物理地址)在不在
			// 不在的话在执行vmemmap_alloc_block_buf做映射
			// 就不在具体分析了,原理一样的
            vmemmap_verify((pte_t *)pmdp, node, addr, next);
    } while (addr = next, addr != end);

    return 0;
}

在这里插入图片描述
在这里插入图片描述
也就是说如果内存不是按照128M对齐的话,使用vmemmap是有内存上的一定的损失的。注:PMD_SIZE=2M,而一个page的大小是64字节,那么一个pmd对应能保存的页帧数量是32768个。对应的实际物理地址为:32769 << PAGE_SHIFT (12) = 128M

vmemmap_pgd_populate
pgd_t * __meminit vmemmap_pgd_populate(unsigned long addr, int node)
{
	// 获取地址对应的pgd,对于我手中的设备,pgd在内核启动(汇编阶段)
	// 就创建好了,pgd本身都是存在的,但是pgd的子项即指向的下级页表未必存在 
    pgd_t *pgd = pgd_offset_k(addr);
	if (pgd_none(*pgd)) { //该函数无条件返回0
		// 这里一定走不进来,走进来就见鬼了
        void *p = vmemmap_alloc_block_zero(PAGE_SIZE, node);
        if (!p)
            return NULL;
        pgd_populate(&init_mm, pgd, p);
	}
	// 返回pgd,一定不可能是null的
    return pgd;
}
vmemmap_pud_populate

首次进入该函数会触发缺页,给pgd申请下一级的页表项(pmd)

pud_t * __meminit vmemmap_pud_populate(p4d_t *p4d, unsigned long addr, int node)
{
    pud_t *pud = pud_offset(p4d, addr);
    if (pud_none(*pud)) {
        printk("gytest vmemmap_pud_populate into pud:0x%llx pa:0x%llx *pud = 0x%llx\n", pud_offset(p4d, addr),
                    __pa(pud_offset(p4d, addr)), *pud);
		// 下面分析
        void *p = vmemmap_alloc_block_zero(PAGE_SIZE, node);
        if (!p)
            return NULL;
        pud_populate(&init_mm, pud, p);
        printk("gytest vmemmap_pud_populate pud_populate *pud = 0x%llx\n", *pud);
    }
    return pud;
}

在这里插入图片描述
设备启动后通过工具查看0x38eaa000开始的1page内存的页表映射关系,可以看到从0x3600_0000一直到0x3680_0000映射了一共8M(一共映射了4个2M[PMD_SIZE])。注:实际表项为:8字节的0x0060_0000_3600_0701,因为arm64在内核那部分都是线性映射了,所以虚拟地址可以看做直接就是对应的物理地址
在这里插入图片描述

vmemmap_alloc_block_zero

入参size=PAGE_SIZE=4K,node=nid=0(内存节点)

static void * __meminit vmemmap_alloc_block_zero(unsigned long size, int node)
{
    void *p = vmemmap_alloc_block(size, node);

    if (!p)
        return NULL;
    memset(p, 0, size);

    return p;
}
void * __meminit vmemmap_alloc_block(unsigned long size, int node)
{
    /* If the main allocator is up use that, fallback to bootmem. */
    if (slab_is_available()) {
        gfp_t gfp_mask = GFP_KERNEL|__GFP_RETRY_MAYFAIL|__GFP_NOWARN;
        int order = get_order(size);
        static bool warned;
        struct page *page;

        page = alloc_pages_node(node, gfp_mask, order);
        if (page)
            return page_address(page);

        if (!warned) {
            warn_alloc(gfp_mask & ~__GFP_NOWARN, NULL,
                   "vmemmap alloc failure: order:%u", order);
            warned = true;
        }
        return NULL;
	} else {
		// 在这个时间节点,slab还没准备好,会调用下面的函数申请page
        printk("gytest slab is not available\n");
		// 实际就是调用membloc进行内存申请,不在深入分析
        return __earlyonly_bootmem_alloc(node, size, size, __pa(MAX_DMA_ADDRESS));
    }
}

问:什么arm64架构的vmemmap只映射到PMD

如下图为arm64页表映射情况。我们既可以用最终的【20:12】对应的PTE映射项,以4K为单位,进行虚拟地址到物理地址的映射;又可以以【29:21】对应的PMD映射项,以2M为单位,进行虚拟地址到物理地址的映射。

对于用户空间的虚拟地址而言,当我们进行的是PMD映射的时候,我们得到的是Huge Page,ARM64的2MB的huge page,在虚拟和物理上都连续,它在实践工程中的好处是,可以减小TLB miss,因为,如果进行了2MB的映射,整个2MB不再需要PTE,映射关系大为减小。
详见:https://blog.csdn.net/21cnbao/article/details/112057498

如下图为开启PTDUMP(debugfs)下的vmemmap区域的页表映射情况,关于PTDUMP这个工具以后有时间在分析

在这里插入图片描述
在这里插入图片描述

vmemmap_alloc_block_buf

这里实际上就是给vmemmap申请页帧空间了,注意:如果后面发生内存热插的话,插入超过128M的内存还会继续申请vmemmap用来map新内存插入的页帧。即每热插128M内存,要消耗2M页帧空间

void * __meminit vmemmap_alloc_block_buf(unsigned long size, int node,
                     struct vmem_altmap *altmap)
{
    void *ptr;
	// 传入的altmap都是NULL
    if (altmap)
        return altmap_alloc_block_buf(size, altmap);
    // size的大小是2M
    ptr = sparse_buffer_alloc(size);
    if (!ptr)
        ptr = vmemmap_alloc_block(size, node);
    return ptr;
}
/* 
该函数实际上就是使用sparse_buffer_init预留的内存,用多少取多少
最后没用上的全部free掉
 */
void * __meminit sparse_buffer_alloc(unsigned long size)
{
    void *ptr = NULL;

    if (sparsemap_buf) {
        ptr = (void *) roundup((unsigned long)sparsemap_buf, size);
        if (ptr + size > sparsemap_buf_end) {
            printk("gytest sparse_buffer_alloc ptr + size > sparsemap_buf_end\n");
            ptr = NULL;
        }   
        else {
            /* Free redundant aligned space */
            if ((unsigned long)(ptr - sparsemap_buf) > 0)
                sparse_buffer_free((unsigned long)(ptr - sparsemap_buf));
            sparsemap_buf = ptr + size;
            printk("gytest sparse_buffer_alloc sparsemap_buf:0x%llx\n",sparsemap_buf);
        }
    }
    return ptr;
}

在这里插入图片描述

sparse_init_one_section

入参分别为:pnum对应的内存段结构体指针,页帧号,页帧对应的内存段(2M对齐)的page的首地址,使用情况的bitmap,flag = SECTION_IS_EARLY

static void __meminit sparse_init_one_section(struct mem_section *ms,
        unsigned long pnum, struct page *mem_map,
        struct mem_section_usage *usage, unsigned long flags)
{
	// 忽略低4bit(用于flag标记)
	ms->section_mem_map &= ~SECTION_MAP_MASK;
	// 将地址编码给mem_map
    ms->section_mem_map |= sparse_encode_mem_map(mem_map, pnum)
        | SECTION_HAS_MEM_MAP | flags;
    ms->usage = usage;
}

static unsigned long sparse_encode_mem_map(struct page *mem_map, unsigned long pnum)
{
    unsigned long coded_mem_map =
        (unsigned long)(mem_map - (section_nr_to_pfn(pnum)));
    BUILD_BUG_ON(SECTION_MAP_LAST_BIT > (1UL<<PFN_SECTION_SHIFT));
    BUG_ON(coded_mem_map & ~SECTION_MAP_MASK);
    return coded_mem_map;
}

使用了vmemmap的section_mem_map就都是vmemmap的虚拟地址在这里插入图片描述
对于不使用vmemmap的映射是这样的:可以看出来每次都是按照0x40000(256K-> sizeof(struct page):64 PAGES_PER_SECTION:4096 乘积为256K)进行递增的,一共需要256K*23 = 5888K
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

参考文档

  1. https://www.kernel.org/doc/html/latest/translations/zh_CN/mm/memory-model.html
  2. https://www.cnblogs.com/LoyenWang/p/11523678.html#:~:text=sparse_ini
  3. https://blog.csdn.net/gjioui123/article/details/128284701

http://www.kler.cn/a/293336.html

相关文章:

  • zabbix搭建钉钉告警流程
  • MoneyPrinterTurbo – 开源的AI短视频生成工具
  • 边缘计算在智能交通系统中的应用
  • 普通电脑上安装属于自己的Llama 3 大模型和对话客户端
  • ️️一篇快速上手 AJAX 异步前后端交互
  • 轻松上手:使用Docker部署Java服务
  • Oracle RAC关于多节点访问同一个数据的过程
  • C 语言指针与数组的深度解析
  • 鸿蒙轻内核M核源码分析系列四 中断Hwi
  • 无人机纪录片航拍认知
  • LLM指令微调实践与分析
  • 用RPC Performance Inspector 优化你的区块链
  • 技术周刊 | Rspack 1.0、v0 支持 Vue、2024 年度编程语言排行榜、Ideogram 2.0、从 0 实现一个 React
  • 深度学习(九)-图像形态操作
  • 《C++进阶之路:探寻预处理宏的替代方案》
  • Spring Boot实现大文件分片下载
  • 谈一谈MVCC
  • 人工智能、机器学习和深度学习有什么区别?应用领域有哪些?
  • Linux 简介
  • HNU-2023电路与电子学-实验1
  • 如何看待AI技术对人们生活的影响?
  • 【网络安全】Sping Boot 未授权访问敏感数据
  • 时下改变AI的6大NLP语言模型
  • 关于 export HF_ENDPOINT=https://hf-mirror.com
  • DP(Display Port)
  • 缓存对象反序列化失败