Linux内存 -- Fixmap映射固定虚拟地址

目录

Fixmap固定虚拟地址

Fixmap空间特性        

Fixmap虚拟空间的映射过程


      

       继上文介绍内核在启动初期为内核段建立了临时映射页表,实现了内核物理地址到虚拟地址的映射;该映射完成后可以通过虚拟地址访问内核空间,但此段仅限于内核段的访问,内核之外的内存空间还不能使用虚拟地址进行访问,需要继续完成内存空间外的内存映射。要实现其他内存空间的映射就需要从fdt中解析系统的内存情况,要访问fdt就要先实现fdt的映射,即要引出本章的介绍的主角fixmap。

       fdt物理内存空间映射到虚拟地址空间是通过fixmap实现的,所谓fixmap是一段固定的虚拟内存空间,在系统编译前划分好虚拟地址。Fixmap的机制实现了系统资源物理地址向虚拟地址的映射,例如fdt、earlycon等内存数据的映射过程。

Fixmap固定虚拟地址

arm64/include/asm/memory.h
#define FIXADDR_TOP        (PCI_IO_START - SZ_2M)

arm64/include/asm/fixmap.h
#define FIXADDR_SIZE    (__end_of_permanent_fixed_addresses << PAGE_SHIFT)
#define FIXADDR_START    (FIXADDR_TOP - FIXADDR_SIZE)

        如上Fixmap固定虚拟地址为FIXADDR_START,其为Fixmap的固定虚拟地址的尾部地址FIXADDR_TOP 减去 Fixmap虚拟地址空间的大小 FIXADDR_SIZE。

        由FIXADDR_SIZE定义可知Fixmap虚拟空间大小永久fixmap空间,不包含临时fixmap空间。

        FIXADDR_TOP 地址为 PCI_IO起始虚拟地址 减 2M。

Fixmap空间特性        

  • Fixmap枚举结构
arch/arm64/include/asm/fixmap.h
/*
 * Here we define all the compile-time 'special' virtual
 * addresses. The point is to have a constant address at
 * compile time, but to set the physical address only
 * in the boot process.
 *
 * These 'compile-time allocated' memory buffers are
 * page-sized. Use set_fixmap(idx,phys) to associate
 * physical memory with fixmap indices.
 *
 */
enum fixed_addresses {
	FIX_HOLE,

	/*
	 * Reserve a virtual window for the FDT that is 2 MB larger than the
	 * maximum supported size, and put it at the top of the fixmap region.
	 * The additional space ensures that any FDT that does not exceed
	 * MAX_FDT_SIZE can be mapped regardless of whether it crosses any
	 * 2 MB alignment boundaries.
	 *
	 * Keep this at the top so it remains 2 MB aligned.
	 */
#define FIX_FDT_SIZE		(MAX_FDT_SIZE + SZ_2M)
	FIX_FDT_END,
	FIX_FDT = FIX_FDT_END + FIX_FDT_SIZE / PAGE_SIZE - 1,

	FIX_EARLYCON_MEM_BASE,
	FIX_TEXT_POKE0,

#ifdef CONFIG_ACPI_APEI_GHES
	/* Used for GHES mapping from assorted contexts */
	FIX_APEI_GHES_IRQ,
	FIX_APEI_GHES_NMI,
#endif /* CONFIG_ACPI_APEI_GHES */

#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
	FIX_ENTRY_TRAMP_TEXT3,
	FIX_ENTRY_TRAMP_TEXT2,
	FIX_ENTRY_TRAMP_TEXT1,
	FIX_ENTRY_TRAMP_DATA,
#define TRAMP_VALIAS		(__fix_to_virt(FIX_ENTRY_TRAMP_TEXT1))
#endif /* CONFIG_UNMAP_KERNEL_AT_EL0 */
	__end_of_permanent_fixed_addresses,

	/*
	 * Temporary boot-time mappings, used by early_ioremap(),
	 * before ioremap() is functional.
	 */
#define NR_FIX_BTMAPS		(SZ_256K / PAGE_SIZE)
#define FIX_BTMAPS_SLOTS	7
#define TOTAL_FIX_BTMAPS	(NR_FIX_BTMAPS * FIX_BTMAPS_SLOTS)

	FIX_BTMAP_END = __end_of_permanent_fixed_addresses,
	FIX_BTMAP_BEGIN = FIX_BTMAP_END + TOTAL_FIX_BTMAPS - 1,

	/*
	 * Used for kernel page table creation, so unmapped memory may be used
	 * for tables.
	 */
	FIX_PTE,
	FIX_PMD,
	FIX_PUD,
	FIX_PGD,

	__end_of_fixed_addresses
};

        编译时决定了Fixmap结构中每个元素的虚拟地址在,Fixmap结构中包含了永久虚拟地址空间和临时虚拟地址空间。永久虚拟空间即该空间决定后不会再被释放作为他用,临时虚拟空间则相反。

        Fixmap虚拟空间地址是以page size对齐,即相邻两个元素的地址的差为page size的整数倍。每个虚拟空间对应的物理地址空间地址在系统启动过程中建立。

  • Fixmap ID和虚拟地址转化
#define __fix_to_virt(x)	(FIXADDR_TOP - ((x) << PAGE_SHIFT))
#define __virt_to_fix(x)	((FIXADDR_TOP - ((x)&PAGE_MASK)) >> PAGE_SHIFT)

        如上虚拟地址与Fixmap ID之间的转换逻辑,因为Fixmap ID对应的虚拟地址以page size对齐,所以Fixmap ID左移page_shift位后即为该Fixmap ID对应的虚拟地址基于FIXADDR_TOP的偏移。

Fixmap虚拟空间的映射过程

        

static pte_t bm_pte[PTRS_PER_PTE] __page_aligned_bss;
static pmd_t bm_pmd[PTRS_PER_PMD] __page_aligned_bss __maybe_unused;
static pud_t bm_pud[PTRS_PER_PUD] __page_aligned_bss __maybe_unused;


/*
 * The p*d_populate functions call virt_to_phys implicitly so they can't be used
 * directly on kernel symbols (bm_p*d). This function is called too early to use
 * lm_alias so __p*d_populate functions must be used to populate with the
 * physical address from __pa_symbol.
 */
void __init early_fixmap_init(void)
{
	pgd_t *pgd;
	pud_t *pud;
	pmd_t *pmd;
	/*
	 * 内核启动到此函数之前只完成了kernel内存空间的映射,FIXADDR_START虚拟地址不在内核空间,
	 * 故FIXADDR_START对应的页表目录为空,此函数就是要实现Fixmap空间的映射,完成Fixmap页表目录
	 * 创建
	 */
	unsigned long addr = FIXADDR_START;
	
	pgd = pgd_offset_k(addr);
	/*
	 * pgt_level大于3时,pud代表的上级页表项存在,需要完成上级页表项的填充;
	 */
	if (CONFIG_PGTABLE_LEVELS > 3 &&
	    !(pgd_none(*pgd) || pgd_page_paddr(*pgd) == __pa_symbol(bm_pud))) {
		/*
		 * We only end up here if the kernel mapping and the fixmap
		 * share the top level pgd entry, which should only happen on
		 * 16k/4 levels configurations.
		 */
		BUG_ON(!IS_ENABLED(CONFIG_ARM64_16K_PAGES));
		pud = pud_offset_kimg(pgd, addr);
	} else {
	/* 
	 * 3级页表时,pud等同于pgd;故pgd页表项填充bm_pmd对应物理地址
	 */
		if (pgd_none(*pgd))
			__pgd_populate(pgd, __pa_symbol(bm_pud), PUD_TYPE_TABLE);
		pud = fixmap_pud(addr);
	}
	if (pud_none(*pud))
		__pud_populate(pud, __pa_symbol(bm_pmd), PMD_TYPE_TABLE);
	/* 
	 * 获取FIXADDR_START虚拟地址对应的pmd页表项,并使用bm_pte物理地址对其进行填充
	 */
	pmd = fixmap_pmd(addr);
	__pmd_populate(pmd, __pa_symbol(bm_pte), PMD_TYPE_TABLE);

	/*
	 * The boot-ioremap range spans multiple pmds, for which
	 * we are not prepared:
	 */
	BUILD_BUG_ON((__fix_to_virt(FIX_BTMAP_BEGIN) >> PMD_SHIFT)
		     != (__fix_to_virt(FIX_BTMAP_END) >> PMD_SHIFT));

	if ((pmd != fixmap_pmd(fix_to_virt(FIX_BTMAP_BEGIN)))
	     || pmd != fixmap_pmd(fix_to_virt(FIX_BTMAP_END))) {
		WARN_ON(1);
		pr_warn("pmd %p != %p, %p\n",
			pmd, fixmap_pmd(fix_to_virt(FIX_BTMAP_BEGIN)),
			fixmap_pmd(fix_to_virt(FIX_BTMAP_END)));
		pr_warn("fix_to_virt(FIX_BTMAP_BEGIN): %08lx\n",
			fix_to_virt(FIX_BTMAP_BEGIN));
		pr_warn("fix_to_virt(FIX_BTMAP_END):   %08lx\n",
			fix_to_virt(FIX_BTMAP_END));

		pr_warn("FIX_BTMAP_END:       %d\n", FIX_BTMAP_END);
		pr_warn("FIX_BTMAP_BEGIN:     %d\n", FIX_BTMAP_BEGIN);
	}
}

        该函数在内核启动过程中通过调用__p*d_populate、__pa_symbol函数实现FIXMAP固定虚拟空间的页表创建(除pte页表)。__pa_symbol该函数获取内核符号对应的物理空间,具体实现逻辑可参考:  Linux内存 -- 内核空间物理与虚拟地址转换_hello_yj的博客-CSDN博客__p*d_populate函数将bm_pxx的物理地址及PXX_TYPE_TABLE赋值给对应的页表项。

  •  early_fixmap_init函数创建Fixmap页表项过程:

        1.通过 pgd_offset_k 函数获取FIXADDR_START对应的页表目录项pgd;

        2.三级页表系统中pud页表项 同 pgd页表目录项,故内核数组变量bm_pud不会被使用,而是将内核数组变量bm_pmd的物理地址填充到pud页表项,将bm_pmd数组做为Fixmap虚拟空间pud页表项的物理空间;

        3.获取FIXADDR_START虚拟地址对应的pmd页表项,并使用内核数组变量bm_pte物理地址对其进行填充;

        early_fixmap_init函数只是创建了Fixmap虚拟空间对应的pgd 、pmd两级页表,并没有实现pte页表的创建。Fixmap虚拟空间对应的pte页表项创建,需要知道Fixmap ID对应的物理空间地址;在内核随后的启动过程中会逐步完成该pte页表的创建。

  •         fixmap_pmd 函数

        fixmap_pmd函数目的在于获取fixmap空间特定虚拟地址对应的pmd页表项的虚拟地址

pgd_level = 3 ,3级页表结构

#define pmd_offset_kimg(dir,addr)	((pmd_t *)__phys_to_kimg(pmd_offset_phys((dir), (addr))))

#define pmd_offset_phys(dir, addr)	(pud_page_paddr(*(dir)) + pmd_index(addr) * sizeof(pmd_t))

static inline pmd_t * fixmap_pmd(unsigned long addr)
{
	/*
	 * 获取虚拟地址addr对应的pud页表项
	 */
	pud_t *pud = fixmap_pud(addr);

	BUG_ON(pud_none(*pud) || pud_bad(*pud));
	/*
	 * 获取虚拟地址addr对应的pmd页表项
	 */
	return pmd_offset_kimg(pud, addr);
}

 
         如上是fixmap_pmd相关代码实现(默认3级别页表结构),其中pmd_offset_kimg宏定义是功能实现(获取指定虚拟地址addr对应的pmd页表项虚拟地址)的核心函数。

        pmd_offset_kimg具体实现:
         1.dir为pud页表项,pud保存了addr虚拟地址对应的pmd页表项基地址,通过early_fixmap_init函数可知该地址即为 bm_pmd[]数组的物理地址;
         2.通过pmd_index(addr)可知addr虚拟地址对应的pmd_index , pmd_index(addr) * sizeof(pmd_t)即为 addr虚拟地址的pmd页表基于bm_pmd的偏移。
          3.知道addr对应的pmd页表物理地址后即可知道该页表对应的虚拟地址 (内核段虚拟地址 =内核段物理地址 + kimage_voffset );
 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值