linux内存管理(十七)创建页表流程分析

本文围绕Linux内核创建页表的核心函数__create_pgd_mapping展开。先介绍其传入参数含义,接着分析early_pgtable_alloc函数,它用memblock分配器分配内存并处理固定映射。然后详细剖析__create_pgd_mapping及其子函数,如alloc_init_pud等,阐述页表初始化和映射的具体操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

开篇

Linux内核创建页表的核心函数是__create_pgd_mapping,位于arch/arm64/mm/mmu.c文件中。很多函数都是通过调用它实现页表的创建,比如linux内核vmlinux各段内存和memblock内存创建细粒度映射时候的调用流程如下:

start_kernel()
	-> setup_arch()
		->paging_init()
			->map_kernel()
				->map_kernel_segment() *5 映射内核lds文件的各个段内存
					->__create_pgd_mapping()
			->map_mem()
				->__map_memblock() 映射memblock内存
					->__create_pgd_mapping() 

当然,还有很多函数也是通过__create_pgd_mapping来创建页表的,比如:create_mapping_noalloc、create_pgd_mapping、update_mapping_prot、map_entry_trampoline和arch_add_memory。接下来,我们看看__create_pgd_mapping函数的传入参数含义吧。

__create_pgd_mapping传入参数含义分析:

  1. pgd_t *pgdir:

    • pgdir 参数是一个指向页全局目录(PGD)的指针,也就是PGD的页表基地址,页表转换的时候放在TTBR寄存器中使用。PGD是页表层次结构的顶层,包含对下一级页表(PUD)的指针。
  2. phys_addr_t phys:

    • phys 参数指定了要映射的物理地址。phys_addr_t 是一个类型,用于表示物理内存地址。
  3. unsigned long virt:

    • virt 参数指定了对应的虚拟地址。这个地址是内核希望在虚拟地址空间中使用的地址。
  4. phys_addr_t size:

    • size 参数定义了要映射的内存区域的大小。
  5. pgprot_t prot:

    • prot 参数是一个属性标志,用于设置页表项的低位属性和高位属性。也就是内存属性索引、非安全位、读写权限、用户访问权限、共享性、访问标志位、全局标志位、脏位连续集合位、内核执行权限位、用户执行权限位。
  6. phys_addr_t (*pgtable_alloc)(int):

    • pgtable_alloc 是一个函数指针,指向用于分配页表内存的函数。使用指定函数分配用于页表的内存,避免系统启动初期,内存管理系统还没起来,无法分配内存。
  7. int flags:

    • flags 参数可能包含一些标志位,用于控制映射的特定行为或属性,比如是否进行块映射或者是否使用连续映射标志位。

我们可以看到map_kernel_segment和__map_memblock函数的第六个传入参数都是early_pgtable_alloc,也就是他们都是通过early_pgtable_alloc来申请页表内存的,因为这个时候我们的伙伴系统还没有起来,无法直接从伙伴系统中申请物理内存,其他创建页表的函数要么是仅仅修改页表项属性,不需要申请页表内存;要么通过__get_free_page申请物理内存。下面我们看看early_pgtable_alloc和__create_pgd_mapping函数的实现吧。

一、 early_pgtable_alloc

static phys_addr_t __init early_pgtable_alloc(int shift)
{
	phys_addr_t phys;
	void *ptr;

	//使用的 memblock 分配器在物理内存中分配一块指定大小的内存区域
	phys = memblock_phys_alloc(PAGE_SIZE, PAGE_SIZE);
	if (!phys)
		panic("Failed to allocate page table page\n");

	/*
	 * The FIX_{PGD,PUD,PMD} slots may be in active use, but the FIX_PTE
	 * slot will be free, so we can (ab)use the FIX_PTE slot to initialise
	 * any level of table.
	 */
	ptr = pte_set_fixmap(phys);//建立phys的固定映射,返回虚拟地址

	memset(ptr, 0, PAGE_SIZE);//把刚刚申请的页表内存清零

	/*
	 * Implicit barriers also ensure the zeroed page is visible to the page
	 * table walker
	 */
	pte_clear_fixmap();//清除固定映射

	return phys;//返回物理地址
}

early_pgtable_alloc函数主要做了以下几件事:

  1. 调用函数memblock_phys_alloc使用的 memblock 分配器在物理内存中分配一块指定大小的内存区域,返回物理地址phys,因为这个时候伙伴系统还没启动,暂时使用memblock 管理内存;
  2. 调用函数pte_set_fixmap建立phys的固定映射,返回虚拟地址ptr;
  3. 把刚刚申请到的页表内存清零;
  4. 调用函数pte_clear_fixmap清除固定映射;
  5. 返回清零了的物理内存的物理地址。

笔记:
固定映射是初始化在start_kernel()-> setup_arch()->early_fixmap_init()中,会初始化FIXMAP的起始地址,并建立一个映射框架,但不填充页表条目(PTE)。这是为了在MMU启动后,能够通过虚拟地址访问关键的物理内存区域。在需要的时候填充它就可以把想要的物理地址映射到任意我们想要的虚拟地址上。linux内核以数组的形式记录了这些固定映射的条目,并且固定映射是马上映射,马上使用,马上用完,马上删除的。

二、 __create_pgd_mapping

static void __create_pgd_mapping(pgd_t *pgdir, phys_addr_t phys,
				 unsigned long virt, phys_addr_t size,
				 pgprot_t prot,
				 phys_addr_t (*pgtable_alloc)(int),
				 int flags)
{
	unsigned long addr, end, next;
 
	//通过pgd基地址和虚拟地址计算出pgd项地址
	pgd_t *pgdp = pgd_offset_pgd(pgdir, virt);

	/*
	 * If the virtual and physical address don't have the same offset
	 * within a page, we cannot map the region as the caller expects.
	 */
	if (WARN_ON((phys ^ virt) & ~PAGE_MASK))//无法理解
		return;
	//页表以页为单位创建,起始地址和结束地址需要页对齐 
	phys &= PAGE_MASK;
	addr = virt & PAGE_MASK;
	end = PAGE_ALIGN(virt + size);

	//
	do {
		//计算下一个pgd项映射的虚拟地址值,当前pgd项映射的虚拟地址为[addr,next)
		next = pgd_addr_end(addr, end);
		alloc_init_pud(pgdp, addr, next, phys, prot, pgtable_alloc,
			       flags);//初始化当前pgd指向的pud页表
		phys += next - addr;//计算下一个pgd页表项映射的物理地址
	} while (pgdp++, addr = next, addr != end);//处理一个个pgd项,直到虚拟地址映射完 
}

我们分析完传入参数后看看这个函数做了什么:

  1. 调用函数pgd_offset_pgd通过pgd基地址和虚拟地址计算出pgd项地址;
  2. 把映射的物理起始地址、虚拟起始地址和虚拟结束地址进行页对齐;
  3. 在do while循环里面处理一个个pgd页目录项,直到虚拟地址映射完,在循环体里面做下面的操作,完成后返回:
  4. 调用函数pgd_addr_end计算下一个pgd项映射的虚拟地址值,得到当前pgd项映射的虚拟地址为[addr,next);
  5. 调用函数alloc_init_pud初始化当前pgd项指向的pud页表,当前pgd项映射的虚拟地址为[addr,next);
  6. 计算下一个pgd页表项映射的物理地址。

2.1 alloc_init_pud

static void alloc_init_pud(pgd_t *pgdp, unsigned long addr, unsigned long end,
			   phys_addr_t phys, pgprot_t prot,
			   phys_addr_t (*pgtable_alloc)(int),
			   int flags)
{
	unsigned long next;
	pud_t *pudp;
	//五级页表才有的p4dp,在这里p4dp就是pgdp,用作预留接口
	p4d_t *p4dp = p4d_offset(pgdp, addr);
	p4d_t p4d = READ_ONCE(*p4dp);//读取p4d(pgd)项的值

	if (p4d_none(p4d)) {//如果p4d项是无效项
		phys_addr_t pud_phys;
		BUG_ON(!pgtable_alloc);
		pud_phys = pgtable_alloc(PUD_SHIFT);//调用传入参数pgtable_alloc申请页表内存 
		//将pud页物理基地址结合属性PUD_TYPE_TABLE形成页目录项格式写到p4d项中
		__p4d_populate(p4dp, pud_phys, PUD_TYPE_TABLE);
		p4d = READ_ONCE(*p4dp);//读取p4d项的值
	}
	BUG_ON(p4d_bad(p4d));

	/*
	 * No need for locking during early boot. And it doesn't work as
	 * expected with KASLR enabled.
	 */
	if (system_state != SYSTEM_BOOTING)
		mutex_lock(&fixmap_lock);
	//读取g4d项的值得到pud基地址,然后结合addr计算pud项的地址
	//并且使用固定映射的方式映射pud基物理地址到pud基虚拟地址
	pudp = pud_set_fixmap_offset(p4dp, addr);
	do {
		pud_t old_pud = READ_ONCE(*pudp);//读取pud项的地址得到pud项的值
		
		//计算下一个pud项映射的虚拟地址值,当前pud项映射的虚拟地址为[addr,next)
		next = pud_addr_end(addr, end);

		/*
		 * For 4K granule only, attempt to put down a 1GB block
		 */
		//如果页面是4k粒度的,并且虚拟地址覆盖了整个pud项
		if (use_1G_block(addr, next, phys) &&
		    (flags & NO_BLOCK_MAPPINGS) == 0) {//标志没有禁止使用BLOCK
			pud_set_huge(pudp, phys, prot);//填写pud项的值,设置为块映射

			/*
			 * After the PUD entry has been populated once, we
			 * only allow updates to the permission attributes.
			 */
			//如果pud项本来是有值的,我们只能修改属性,不能修改物理地址,否则报BUG
			BUG_ON(!pgattr_change_is_safe(pud_val(old_pud),
						      READ_ONCE(pud_val(*pudp))));
		} else {//这个if分支说明我们不采用BLOCK映射
			//初始化当前pud指向的pmd页表
			alloc_init_cont_pmd(pudp, addr, next, phys, prot,
					    pgtable_alloc, flags);

			BUG_ON(pud_val(old_pud) != 0 &&
			       pud_val(old_pud) != READ_ONCE(pud_val(*pudp)));
		}
		phys += next - addr;//计算下一个pud页表项映射的物理地址
	} while (pudp++, addr = next, addr != end);//处理一个个pud项,直到虚拟地址映射完

	pud_clear_fixmap();//清除固定映射
	if (system_state != SYSTEM_BOOTING)
		mutex_unlock(&fixmap_lock);
}

alloc_init_pud函数主要做了以下几件事:

  1. 调用函数p4d_offset计算出p4d项地址的值。
  2. 读取p4d项的值得到p4d
  3. 调用函数p4d_none判断p4d是否为一个无效项,如果是无效项,执行下面的操作,否则跳到6;
  4. 调用传入参数pgtable_alloc申请页表内存;
  5. 调用函数__p4d_populate将pud页物理基地址结合属性PUD_TYPE_TABLE形成页目录项格式写到p4d项中;
  6. 调用函数pud_set_fixmap_offset读取p4d项的值得到pud页表基地址,然后结合addr计算pud项的物理地址,并且使用固定映射的方式映射pud物理基地址到pud虚拟基地址;
  7. 使用do while循环处理一个个pud项,直到虚拟地址映射完。在循环体中执行8-12的操作;
  8. 读取pud项的地址得到pud项的值;
  9. 调用函数pud_addr_end计算下一个pud项映射的虚拟地址值,当前pud项映射的虚拟地址为[addr,next);
  10. 使用函数use_1G_block判断如果页面是4k粒度的,并且虚拟地址覆盖了整个pud项,同时没有使用NO_BLOCK_MAPPINGS标志(表示没有禁止使用BLOCK),则调用函数pud_set_huge填写pud项的值,设置为块映射;
  11. 否则说明我们不采用BLOCK映射,调用函数alloc_init_cont_pmd初始化当前pud指向的pmd页表;
  12. 计算下一个pud页表项映射的物理地址,判断是否继续循环。
  13. 调用函数pud_clear_fixmap清除固定映射。

2.1.1 __p4d_populate

static inline void __p4d_populate(p4d_t *p4dp, phys_addr_t pudp, p4dval_t prot)
{
        set_p4d(p4dp, __p4d(__phys_to_p4d_val(pudp) | prot));
}

#define __p4d(x)        ((p4d_t) { __pgd(x) })
#define __pgd(x)        ((pgd_t) { (x) } )

#define __phys_to_p4d_val(phys) __phys_to_pte_val(phys)
#define __phys_to_pte_val(phys) (phys)


static inline void set_p4d(p4d_t *p4dp, p4d_t p4d)
{
        if (in_swapper_pgdir(p4dp)) {
                set_swapper_pgd((pgd_t *)p4dp, __pgd(p4d_val(p4d)));
                return;
        }

        WRITE_ONCE(*p4dp, p4d);//把页表项的值写入页表项所在的地址
        dsb(ishst);
        isb();
}
  1. __p4d的作用仅仅是作为类型装换而已,把一个u64类型转化为p4d_t而已。__phys_to_p4d_val宏的作用是整理物理地址的位置。所以, __p4d(__phys_to_p4d_val(pudp) | prot)就是页表项的格式了。
  2. 然后调用函数set_p4d把页表项的值写入页表项所在的地址,这里dsb的作用是等待内存写入完毕,然后通知其他Inner Shareable的domain。isb的作用是刷cache。

2.1.2 pud_set_fixmap_offset

//读取g4d项的值得到pud页表基地址,然后结合addr计算pud项的地址
#define pud_offset_phys(dir, addr)      (p4d_page_paddr(READ_ONCE(*(dir))) + pud_index(addr) * sizeof(pud_t))
//使用固定映射的方式映射pud物理基地址到pud虚拟基地址
#define pud_set_fixmap_offset(p4d, addr)        pud_set_fixmap(pud_offset_phys(p4d, addr))
#define pud_set_fixmap(addr)            ((pud_t *)set_fixmap_offset(FIX_PUD, addr))

#define set_fixmap_offset(idx, phys) \
        __set_fixmap_offset(idx, phys, FIXMAP_PAGE_NORMAL)
        
#define __set_fixmap_offset(idx, phys, flags)                           \
({                                                                      \
        unsigned long ________addr;                                     \
        __set_fixmap(idx, phys, flags);                                 \
        ________addr = fix_to_virt(idx) + ((phys) & (PAGE_SIZE - 1));   \
        ________addr;                                                   \
})



pud_set_fixmap_offset这个宏主要做了:

  1. 使用pud_offset_phys宏读取g4d项的值得到pud页表基地址,然后结合addr计算pud项的地址;
  2. 使用pud_set_fixmap宏使用固定映射的方式映射pud物理基地址到pud虚拟基地址;
  3. 宏层层调用,来到__set_fixmap_offset中,__set_fixmap_offset会调用__set_fixmap进行固定映射,填写固定映射的PTE,最后返回虚拟地址。
2.1.2.1 __set_fixmap
void __set_fixmap(enum fixed_addresses idx,
                               phys_addr_t phys, pgprot_t flags)
{
        unsigned long addr = __fix_to_virt(idx);//根据枚举值idx计算对应的虚拟地址
        pte_t *ptep;

        BUG_ON(idx <= FIX_HOLE || idx >= __end_of_fixed_addresses);

        ptep = fixmap_pte(addr);/根据固定映射的虚拟地址获取页表条目pte页表项地址

        if (pgprot_val(flags)) {//如果 flags 包含有效的页属性
        		//设置页表条目,将物理页帧号和页属性结合起来
                set_pte(ptep, pfn_pte(phys >> PAGE_SHIFT, flags));
        } else {//不包含有效的页属性
                pte_clear(&init_mm, addr, ptep);//清除页表条目
                //刷新地址为 addr 和 addr+PAGE_SIZE 范围内的TLB
                flush_tlb_kernel_range(addr, addr+PAGE_SIZE);
        }
}


static inline pte_t * fixmap_pte(unsigned long addr)
{
        return &bm_pte[pte_index(addr)];
}

__set_fixmap函数做了几件事:

  1. 调用函数__fix_to_virt根据枚举值idx计算对应的虚拟地址;
  2. 调用函数fixmap_pte根据固定映射的虚拟地址获取页表条目pte页表项地址;
  3. 如果 flags 包含有效的页属性,调用函数set_pte设置页表条目,将物理页帧号和页属性结合起来;否则清除页表条目和刷TLB。
2.1.2.1 fix_to_virt
static __always_inline unsigned long fix_to_virt(const unsigned int idx)
{
        BUILD_BUG_ON(idx >= __end_of_fixed_addresses);
        return __fix_to_virt(idx);
}
#define __fix_to_virt(x)        (FIXADDR_TOP - ((x) << PAGE_SHIFT))

fix_to_virt的作用就是固定映射(fixmap)的索引转换为对应的虚拟地址。

2.1.3 pud_set_huge

int pud_set_huge(pud_t *pudp, phys_addr_t phys, pgprot_t prot)
{
	//根据物理地址phys和属性prot生成新的pud的值
	pud_t new_pud = pfn_pud(__phys_to_pfn(phys), mk_pud_sect_prot(prot));

	//如果pud项本来是有值的,我们只能修改属性,不能修改物理地址,否则报BUG
	if (!pgattr_change_is_safe(READ_ONCE(pud_val(*pudp)),
				   pud_val(new_pud)))
		return 0;

	VM_BUG_ON(phys & ~PUD_MASK);
	set_pud(pudp, new_pud);//把新的pud的值写入pud项中
	return 1;
}

pud_set_huge函数主要做了以下几件事:

  1. 调用宏pfn_pud根据物理地址phys和属性prot生成新的pud的值;
  2. 使用函数pgattr_change_is_safe判断pud项中的值是否有效,如果有效,pud项中的值和new_pud 的物理地址需要相同,否则直接返回0。因为我们不能修改pud项中的物理地址。
  3. 调用函数set_pud把新的pud的值写入pud项中。set_pud和上面的set_p4d函数差不多,不再展开分析了。

2.1.4 alloc_init_cont_pmd

static void alloc_init_cont_pmd(pud_t *pudp, unsigned long addr,
				unsigned long end, phys_addr_t phys,
				pgprot_t prot,
				phys_addr_t (*pgtable_alloc)(int), int flags)
{
	unsigned long next;
	pud_t pud = READ_ONCE(*pudp);//读取pud指针的值到pud

	/*
	 * Check for initial section mappings in the pgd/pud.
	 */
	BUG_ON(pud_sect(pud));//如果pud项的值表示这是个块内存则报bug
	if (pud_none(pud)) {//如果pud项是无效项
		phys_addr_t pmd_phys;
		BUG_ON(!pgtable_alloc);
		pmd_phys = pgtable_alloc(PMD_SHIFT);//调用传入参数pgtable_alloc申请页表内存
		//将pmd页物理基地址结合属性PUD_TYPE_TABLE形成页目录项格式写到pud项中
		__pud_populate(pudp, pmd_phys, PUD_TYPE_TABLE);
		pud = READ_ONCE(*pudp);//读取pud项的值
	}
	BUG_ON(pud_bad(pud));

	do {
		pgprot_t __prot = prot;
		
		//计算下一个pmd项映射的虚拟地址值,当前pmd项映射的虚拟地址为[addr,next)
		next = pmd_cont_addr_end(addr, end);

		/* use a contiguous mapping if the range is suitably aligned */
		//如果范围适当对齐,没有使用NO_CONT_MAPPINGS标志禁用连续映射
		if ((((addr | next | phys) & ~CONT_PMD_MASK) == 0) &&
		    (flags & NO_CONT_MAPPINGS) == 0)
			//更新内存属性,添加PTE_CONT标志位
			__prot = __pgprot(pgprot_val(prot) | PTE_CONT);

		//真正初始化当前pud指向的pmd页表
		init_pmd(pudp, addr, next, phys, __prot, pgtable_alloc, flags);

		phys += next - addr;//计算下一个pmd页表项映射的物理地址
	} while (addr = next, addr != end);//遍历虚拟内存,对每一段内存初始化其pmd页表
}

alloc_init_cont_pmd函数主要做了以下几件事:

  1. 调用函数pud_none判断pud项是否为无效项,如果是则调用传入参数pgtable_alloc申请页表内存,然后调用函数__pud_populate将pmd页物理基地址结合属性PUD_TYPE_TABLE形成页目录项格式写到pud项中,最后读取pud项的值回来;
  2. 使用 do while循环遍历虚拟内存,对每一段内存初始化其pmd页表:
  3. 调用函数pmd_cont_addr_end计算下一个pmd项映射的虚拟地址值,当前pmd项映射的虚拟地址为[addr,next);
  4. 如果范围适当对齐并且没有使用NO_CONT_MAPPINGS标志禁用连续映射,则更新内存属性,添加PTE_CONT标志位;
  5. 调用函数init_pmd真正初始化当前pud指向的pmd页表;
  6. 计算下一个pmd页表项映射的物理地址。
2.1.4.1 init_pmd
static void init_pmd(pud_t *pudp, unsigned long addr, unsigned long end,
		     phys_addr_t phys, pgprot_t prot,
		     phys_addr_t (*pgtable_alloc)(int), int flags)
{
	unsigned long next;
	pmd_t *pmdp;
	//读取pud项的值得到pmd基地址,然后结合addr计算pmd项的地址
	//并且使用固定映射的方式映射pmd基物理地址到pmd基虚拟地址
	pmdp = pmd_set_fixmap_offset(pudp, addr);
	do {
		pmd_t old_pmd = READ_ONCE(*pmdp);//读取pmd项的地址得到pud项的值

		//计算下一个pmd项映射的虚拟地址值,当前pmd项映射的虚拟地址为[addr,next)
		next = pmd_addr_end(addr, end);

		/* try section mapping first */
		//虚拟地址覆盖了整个pmd项并且没有禁止块映射
		if (((addr | next | phys) & ~SECTION_MASK) == 0 &&
		    (flags & NO_BLOCK_MAPPINGS) == 0) {
			pmd_set_huge(pmdp, phys, prot);//填写pmd项的值,设置为块映射

			/*
			 * After the PMD entry has been populated once, we
			 * only allow updates to the permission attributes.
			 */
			BUG_ON(!pgattr_change_is_safe(pmd_val(old_pmd),
						      READ_ONCE(pmd_val(*pmdp))));
		} else {//这个if分支说明我们不采用BLOCK映射
			//初始化当前pmd指向的pte页表
			alloc_init_cont_pte(pmdp, addr, next, phys, prot,
					    pgtable_alloc, flags);

			BUG_ON(pmd_val(old_pmd) != 0 &&
			       pmd_val(old_pmd) != READ_ONCE(pmd_val(*pmdp)));
		}
		phys += next - addr;//计算下一个pud页表项映射的物理地址
	} while (pmdp++, addr = next, addr != end);//处理一个个pmd项,直到虚拟地址映射完

	pmd_clear_fixmap();//清除固定映射
}

init_pmd函数做要做了以下几件事:

  1. 调用函数pmd_set_fixmap_offset读取pud项的值得到pmd基地址,然后结合addr计算pmd项的地址,并且使用固定映射的方式映射pmd基物理地址到pmd基虚拟地址。
  2. 使用do while循环处理一个个pmd项,直到虚拟地址映射完:
  3. 读取pmd项的地址得到pud项的值,然后计算下一个pmd项映射的虚拟地址值,当前pmd项映射的虚拟地址为[addr,next);
  4. 如果虚拟地址覆盖了整个pmd项并且没有禁止块映射,调用函数pmd_set_huge填写pmd项的值,设置为块映射;否则调用函数alloc_init_cont_pte初始化当前pmd指向的pte页表;
  5. 计算下一个pud页表项映射的物理地址,直到虚拟地址映射完退出循环。
  6. 清除固定映射。
2.1.4.1.1 alloc_init_cont_pte
static void alloc_init_cont_pte(pmd_t *pmdp, unsigned long addr,
				unsigned long end, phys_addr_t phys,
				pgprot_t prot,
				phys_addr_t (*pgtable_alloc)(int),
				int flags)
{
	unsigned long next;
	pmd_t pmd = READ_ONCE(*pmdp);//读取pmd项的值

	BUG_ON(pmd_sect(pmd));
	if (pmd_none(pmd)) {//如果pmd项是无效项
		phys_addr_t pte_phys;
		BUG_ON(!pgtable_alloc);
		pte_phys = pgtable_alloc(PAGE_SHIFT);//调用传入参数pgtable_alloc申请页表内存
		//将pte页物理基地址结合属性PMD_TYPE_TABLE形成页目录项格式写到pmd项中
		__pmd_populate(pmdp, pte_phys, PMD_TYPE_TABLE);
		pmd = READ_ONCE(*pmdp);//读取pmd项的值
	}
	BUG_ON(pmd_bad(pmd));

	do {
		pgprot_t __prot = prot;

		//计算下一个pte项映射的虚拟地址值,当前pte项映射的虚拟地址为[addr,next)
		next = pte_cont_addr_end(addr, end);

		//如果范围适当对齐,没有使用NO_CONT_MAPPINGS标志禁用连续映射
		if ((((addr | next | phys) & ~CONT_PTE_MASK) == 0) &&
		    (flags & NO_CONT_MAPPINGS) == 0)
			//更新内存属性,添加PTE_CONT标志位
			__prot = __pgprot(pgprot_val(prot) | PTE_CONT);

		//真正初始化当前pud指向的pmd页表
		init_pte(pmdp, addr, next, phys, __prot);

		phys += next - addr;//计算下一个pud页表项映射的物理地址
	} while (addr = next, addr != end);//遍历虚拟内存,对每一段内存初始化其pte页表
}

alloc_init_cont_pte函数主要做了以下几件事:

  1. 读取pmd项的值到pmd;
  2. 调用函数pmd_none判断pmd项是否为无效项,如果pmd项是无效项,调用传入参数pgtable_alloc申请页表内存,然后调用函数__pmd_populate将pte页物理基地址结合属性PMD_TYPE_TABLE形成页目录项格式写到pmd项中,最后再次读取pmd项的值更新到pmd;
  3. 使用do while循环遍历虚拟内存,对每一段内存初始化其pte页表:
  4. 调用函数pte_cont_addr_end计算下一个pte项映射的虚拟地址值,当前pte项映射的虚拟地址为[addr,next);
  5. 如果范围适当对齐并且没有使用NO_CONT_MAPPINGS标志禁用连续映射,则更新内存属性,添加PTE_CONT标志位;
  6. 调用函数init_pte真正初始化当前pud指向的pmd页表;
  7. 计算下一个pud页表项映射的物理地址,直到虚拟地址映射完退出循环。
2.1.4.1.1 init_pte
static void init_pte(pmd_t *pmdp, unsigned long addr, unsigned long end,
		     phys_addr_t phys, pgprot_t prot)
{
	pte_t *ptep;
	//读取gmd项的值得到pte基地址,然后结合addr计算pte项的地址
	//并且使用固定映射的方式映射pte基物理地址到pte基虚拟地址
	ptep = pte_set_fixmap_offset(pmdp, addr);
	do {
		pte_t old_pte = READ_ONCE(*ptep);
		//组合物理地址和属性形成pte的值,然后写入pte项中
		set_pte(ptep, pfn_pte(__phys_to_pfn(phys), prot));

		/*
		 * After the PTE entry has been populated once, we
		 * only allow updates to the permission attributes.
		 */
		BUG_ON(!pgattr_change_is_safe(pte_val(old_pte),
					      READ_ONCE(pte_val(*ptep))));

		phys += PAGE_SIZE;//计算下一个pte页表项映射的物理地址
	} while (ptep++, addr += PAGE_SIZE, addr != end);//处理一个个pte项,直到虚拟地址映射完

	pte_clear_fixmap();//清除固定映射
}

init_pte函数主要做了以下几件事:

  1. 调用函数pte_set_fixmap_offset读取gmd项的值得到pte基地址,然后结合addr计算pte项的地址并且使用固定映射的方式映射pte基物理地址到pte基虚拟地址;
  2. 使用do while循环遍历虚拟内存,处理一个个pte项,直到虚拟地址映射完:
  3. 调用函数pfn_pte组合物理地址和属性形成pte的值,然后调用函数set_pte写入pte项中;
  4. 计算下一个pte页表项映射的物理地址,直到虚拟地址映射完。
  5. 清除固定映射。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小坚学Linux

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值