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
参考文档
- https://www.kernel.org/doc/html/latest/translations/zh_CN/mm/memory-model.html
- https://www.cnblogs.com/LoyenWang/p/11523678.html#:~:text=sparse_ini
- https://blog.csdn.net/gjioui123/article/details/128284701