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=最后一个在线内存段,并获取该内存段nidint 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=1struct memblock_type *type = &memblock.memory;struct memblock_region *r;int r_nid;// 首先或对*idx值自加 即此时i=0 type->cnt=1while (++*idx < type->cnt) {r = &type->regions[*idx];// r_nid = r->nid = 0r_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=524288size = 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);}
#endifstart &= 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可以是架构支持的任意值,我这里修改成了24for (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都是0set_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 / 256unsigned 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_EXTREMEif (!mem_section)return NULL;
#endifif (!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_sizenext = pmd_addr_end(addr, end);// 下面分析pgdp = vmemmap_pgd_populate(addr, node);if (!pgdp)return -ENOMEM;// 我的设备没有p4d这级页表 p4d = pgbp4dp = 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还没准备好,会调用下面的函数申请pageprintk("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都是NULLif (altmap)return altmap_alloc_block_buf(size, altmap);// size的大小是2Mptr = 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_mapms->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.rhkb.cn/news/417893.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软件测试永远的家——银行测试,YYDS

为什么做金融类软件测试举个栗子&#xff0c;银行里的软件测试工程师。横向跟互联网公司里的测试来说&#xff0c;薪资相对稳定&#xff0c;加班少甚至基本没有&#xff0c;业务稳定。实在是测试类岗位中的香饽饽&#xff01; 一、什么是金融行业 金融业是指经营金融商品的特…

网络安全售前入门09安全服务——安全加固服务

目录 1.服务概述 2.流程及工具 2.1服务流程 2.2服务工具 3.服务内容 ​​​​​​​4.服务方式 ​​​​​​​5.风险规避措施 ​​​​​​​6.服务输出 1.服务概述 安全加固服务是参照风险评估、等保测评、安全检查等工作的结果,基于科学的安全思维方式、长期的安全…

骨灵冷火!Solon Cloud Gateway 照面发布

骨灵冷火&#xff0c;是练药的好火哟。极冷&#xff0c;又极热。在冰冻中被烧死&#xff1a;&#xff09; 1、认识 Solon Cloud Gateway Solon Cloud Gateway 是基于 Solon Cloud、Vert.X 和 Solon Rx(reactive-streams) 接口实现。小特点&#xff1a; 纯响应式的接口体验流…

坐牢第三十六天(QT)

自定义QQ界面 wedget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QDebug> //qt中信息调试类 #include <QIcon> //图标类 #include <QPushButton>//按钮类 #include <QLabel> //标签类 #include <QMovie> //动图类…

树莓派外设驱动WiringPi库

树莓派外设驱动WiringPi库 文章目录 树莓派外设驱动WiringPi库一、树莓派安装WiringPi库二、WiringPi库的使用方法 一、树莓派安装WiringPi库 wiringPi库其实已经很熟悉了&#xff0c;在香橙派中大量使用过&#xff0c;这个库中集成了很多使用的功能性函数&#xff0c;树莓派安…

I2VGen-XL模型构建指南

一、介绍 VGen可以根据输入的文本、图像、指定的运动、指定的主体&#xff0c;甚至人类提供的反馈信号生成高质量的视频。它还提供了各类常用的视频生成模型工具&#xff0c;例如可视化、采样、训练、推理、使用图像和视频的联合训练&#xff0c;加速等各类工具和技术。 &quo…

docker 介绍以及常用命令

文章目录 Docker 概述docker 概念安装 Docker核心概念Docker 镜像Docker 容器Docker 仓库 docker 与虚拟机比较 Docker 命令docker 进程相关命令镜像相关命令查看本地镜像搜索镜像拉取/推送镜像删除镜像 容器相关命令创建容器查看容器启动/终止/删除容器新建并启动容器进入容器…

92. UE5 GAS RPG 使用C++创建GE实现灼烧的负面效果

在正常游戏里&#xff0c;有些伤害技能会携带一些负面效果&#xff0c;比如火焰伤害的技能会携带燃烧效果&#xff0c;敌人在受到伤害后&#xff0c;会接受一个燃烧的效果&#xff0c;燃烧效果会在敌人身上持续一段时间&#xff0c;并且持续受到火焰灼烧。 我们将在这一篇文章里…

地平线SuperDrive首秀:千人研发投入,出场即「比肩第一梯队」

作者 |德新 编辑 |王博 8月底&#xff0c;地平线在北京开放了第一批面向媒体的高阶智驾方案SuperDrive体验。 预计到明年第三季度&#xff0c;SuperDrive将伴随主机厂客户的第一款量产车交付。 目前在国内&#xff0c;仅有英伟达和华为两家的平台基础上&#xff0c;有车企向…

webm转换mp4怎么转?分享6种简单好用的转换方法

在日常的视频处理中&#xff0c;将WebM视频转换为MP4格式是一个常见的需求。无论是为了兼容性、分享还是编辑&#xff0c;MP4格式都因其广泛的支持和良好的性能而备受欢迎。本文将为大家介绍6种高效方法&#xff0c;有需要的小伙伴快来学习下吧。 方法一&#xff1a;口袋视频转…

暴力数据结构之优先级队列的解析及其模拟实现(C++)

1.认识优先级队列 如果我们给每个元素都分配一个数字来标记其优先级&#xff0c;不妨设较小的数字具有较高的优先级&#xff0c;这样我们就可以在一个集合中访问优先级最高的元素并对其进行查找和删除操作了。 优先级队列&#xff08;priority queue&#xff09; 是0个或多个元…

Spring-容器:IOC-基于注解管理Bean

目录 一、基于注解管理Bean&#xff08;重点&#xff09;1.1、概述1.2、开启组件扫描1.2.1、指定要排除的组件1.2.2、仅扫描指定组件 1.3、使用注解定义Bean1.4、使用Autowired注入1.4.1、属性注入1.4.2、set注入1.4.3、构造方法注入1.4.4、形参注入1.4.5、无注解注入1.4.6、联…

第十周:机器学习笔记

第十周机器学习周报 摘要Abstract机器学习——self-attention&#xff08;注意力机制&#xff09;1. 为什么要用self-attention2. self-attention 工作原理2.1 求α的两种方式2.2 attention-score&#xff08;关联程度&#xff09; Pytorch学习1. 损失函数代码实战1.1 L1loss&a…

传统CV算法——边缘算子与图像金字塔算法介绍

边缘算子 图像梯度算子 - Sobel Sobel算子是一种用于边缘检测的图像梯度算子&#xff0c;它通过计算图像亮度的空间梯度来突出显示图像中的边缘。Sobel算子主要识别图像中亮度变化快的区域&#xff0c;这些区域通常对应于边缘。它是通过对图像进行水平和垂直方向的差分运算来…

Robotics: computational motion planning 部分笔记—— week 1 graph-based

grassfire algorithm 四周扩散性&#xff1b;从终点开始按照相邻最小距离格子移动 Dijkstra’s Algorithm 标明从起点开始的所有点的最短距离&#xff08;从上一节点继承&#xff09;&#xff0c;直到终点 A* Algorithm 带有启发性的&#xff0c;给出距离估计&#xff0c…

小杨的H字矩阵小杨的日字矩阵 c++

小杨的H字矩阵 题目描述 小杨想要构造一个NxN的H字矩阵(N为奇数)&#xff0c;具体来说&#xff0c;这个矩阵共有N行&#xff0c;每行N个字符&#xff0c;其中最左列、最右列都是 | &#xff08;键盘右侧删除键下回车键上&#xff0c;shift\&#xff09;&#xff0c;而中间一行…

国内领先线上运动平台:如何借助AI技术实现业务腾飞与用户体验升级

“ 从智能训练到身体分析&#xff0c;再到辅助判决&#xff0c;AI技术正以惊人的速度渗透进体育和健身领域&#xff0c;为运动员和健身爱好者提供了前所未有的个性化体验。 ” AI&#xff0c;运动的智能伴侣 在巴黎奥运会上&#xff0c;AI技术的运用成为了焦点。它不仅为运动…

Java并发编程实战 03 | Java线程状态

在本文中&#xff0c;我们将深入探讨 Java 线程的六种状态以及它们之间的转换过程。其实线程状态之间的转换就如同生物生命从诞生、成长到最终死亡的过程一样。也是一个完整的生命周期。 首先我们来看看操作系统中线程的生命周期是如何转换的。 操作系统中的线程状态转换 线…

STM32F4按键状态机--单击、双击、长按

STM32F4按键状态机--单击、双击、长按 一、状态机的三要素二、使用状态机原因2.1资源占用方面2.2 执行效率方面&#xff1a;2.3 按键抖动方面&#xff1a; 三、状态机实现3.1 状态机分析3.1 程序实现 百度解析的状态机概念如下 状态机由状态寄存器和组合逻辑电路构成&#xff0…

深度学习 --- VGG16能让某个指定的feature map激活值最大化图片的可视化(JupyterNotebook实战)

VGG16能让某个指定的feature map激活值最大化图片的可视化 在前面的文章中&#xff0c;我用jupyter notebook分别实现了&#xff0c;预训练好的VGG16模型各层filter权重的可视化和给VGG16输入了一张图像&#xff0c;可视化VGG16各层的feature map。深度学习 --- VGG16卷积核的可…