linux内存管理3 fixmap

一 为什么需要fixmap

    第二篇中介绍了在start_kernel之前,内核就初始化了idmap和swapper页表,然后使能MMU,从而使系统工作在虚拟地址空间。此后,在内核中若要访问内存或io地址,就必须为其物理地址建立页表,以实现物理地址和虚拟地址之间的映射。
    在内核初始化的前期,内存管理系统还未初始化完成,除了kernel image以外,其余的物理内存也都没有建立页表。因此,此时内核内存操作相关的函数,如ioremap,kmalloc等都无法使用。fixmap就是内核用来实现在前期为某些特定的模块实现物理内存映射的机制。

二 fixmap的地址分配

    fixmap的虚拟地址是内核在编译时就已经固定的一段空间。在arm64架构下,若我们假设虚拟地址长度为48位,且内核未使能KASAN,也未配置CONFIG_UNMAP_KERNEL_AT_EL0选项,则其与整个内核虚拟地址之间的关系如下图:
linux内存管理3 fixmap_第1张图片
    从图中可以看出,整个内核的虚拟地址空间分为线性映射区和非线性映射区, 线性映射区的范围为0xffff 8000 0000 0000 - 0xffff ffff ffff ffff(256T),因此在arm64架构下线性映射的虚拟地址已经足够,也就不再需要高端内存,同时由于其第47位为1,而该位在非线性映射时为0,故可以通过判断bit47的值来判断某虚拟地址是否处于线性映射区。
    非线性映射区的分布如上图,其中内核的起始虚拟地址为0xffff 0000 0000 0000,fixmap位于vmalloc区域和pci io区域之间,size为0x40200。该段fixmap地址又按其功能被划分成了几个更小的部分,其中每一部分都有特定的用途。我们看下代码中对其的定义如下:

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_UNMAP_KERNEL_AT_EL0
	FIX_ENTRY_TRAMP_DATA,
	FIX_ENTRY_TRAMP_TEXT,
#define TRAMP_VALIAS		(__fix_to_virt(FIX_ENTRY_TRAMP_TEXT))
#endif /* CONFIG_UNMAP_KERNEL_AT_EL0 */
	__end_of_permanent_fixed_addresses,

	/*
	 * Temporary boot-time mappings, used by early_ioremap(),
	 * before ioremap() is functional.
	 */
/* 总size为256k * 7 ,其中每个slot为256k,一共7个slots*/
#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的地址可以分为几个部分:
    (1)dtb的虚拟地址空间,该值为4M。由于linux规定dtb的size需要小于2M,理论上用一个2M的虚拟地址空间即可完成映射。但是在后面代码实现中,我们会发现建立dtb页表需要使用section map,即其最后一级页表会直接指向2M block为边界的物理地址。此时若dtb的位置是如下图所示的横跨物理地址2M边界时,显然需要为上下两个2M block都创建页表才能访问完整的image,正是基于这个考虑,此处内核为dtb保留了4M的虚拟地址空间。
linux内存管理3 fixmap_第2张图片
    (2)earlycon的虚拟地址空间,它只占一个页。其实对于earlycon只需要映射一些串口相关的IO寄存器即可,故所占的空间很小,一个页的空间已经足够。
    (3)FIX_TEXT_POKE0也只占一个页,它可以用于修改代码段指令的操作。其实现位于arch/arm64/kernel/insn.c中,在通过fixmap映射该地址时会将代码段的flag映射为可读写的,然后通过构造所需的指令,并将其覆盖掉原先的内容,从而实现代码段的修改。在kgdb中就是用这一机制插入和移除断点的,如需要在某处插入断点,可以将该地址通过fixmap映射,然后用断点指令替换原始指令,当需要继续执行时,则把原始指令再替换回去即可。
    (4)BITMAP空间:从定义可见以上部分是属于permanent fixmap,而从bitmap映射开始是非permanent fixmap的。对于这一部分我的理解是前面的部分经过页表映射以后,该页表在此后不再会被解除映射而会永久存在。其后的bitmap空间会被用于early ioremap,而ioremap的映射在使用完以后即可通过iounmap操作解除映射,因此其映射关系不是永久的。该区域一共占7 * 256k(1792k)空间。
    (5)FIX_PTE等:该区域的空间用于存放页表entry,一共占16k。

三 代码实现

1. fixmap的初始化

    fixmap初始化的主要功能是为其虚拟地址创建页表结构,其页表结构如下图。
    (1)fixmap映射与swapper映射共享PGD页表,其基地址为swapper_pg_dir。由于swapper_pg_dir的size占一个页,且每个PGD的entry占8字节,因此PGD页表最多可以含有512个entry(4k页大小,48位虚拟地址配置),而PGD entry的index为虚拟地址的bit39 - bit47计算结果,因此index值的范围为0 - 511。故一个PGD页可以表示整个48位虚拟地址空间,这也使得fixmap映射与swapper映射共享PGD页表称为了可能,只要将相应虚拟地址在PGD中所占的entry指向不同的PUD即可。
    (2)fixmap的pud页表有两种情况,一种是fixmap的PGD entry正好与swapper映射内核image的PGD entry相同,此时它就与kernel image共享pud页表,其PGD页表也指向swapper_pud。另一种是fixmap的PGD entry与内核image的PGD entry不相同,此时只要将fixmap地址对应的PGD entry指向其自身的pud页表bm_pud即可。bm_pud定义在内核的bss段,故作为内核的一部分,其地址已经被映射过,因此内核代码可以直接通过该符号获取其虚拟地址。
    (3)pud的每个entry指向一个范围在bit0 - bit29位的PMD页表,即每个PMD页表包含一个1GB的地址范围,而从上一节fixmap地址分配所示图中,我们可以看到fixmap的虚拟地址不可能与kernel image地址处于同一个1G范围中,即它们在pud页表中不可能位于同一个entry。故指向pud中fixmap的entry指向其特定的pmd页表bm_pmd,与bm_pud一样,它也是定义在内核image的bss段中。
    (4)fixmap起始地址对应的pmd entry指向bm_pte,显然该PTE的所有entry一共可以映射2^21(2M)地址范围的虚拟地址。根据fixmap的虚拟地址分配我们可知其最高4M用于dtb的映射,它的地址范围为0xffff 7dff fea0 0000 - 0xffff 7dff fec0 0000,故它的起止地址都是2M对齐的。而fixmap的起始地址位于__end_of_permanent_fixed_addresses,即其位于dtb以下的2M空间中,故该PTE可以映射dtb之下2M的虚拟地址,由以上fixmap各部分的size可知,除了dtb之外,其余所有部分包括earlycon,text_poke0,bitmap以及pte等的地址总长度小于2M,故它们都可以通过该PTE映射完成。而dtb的映射则会使用section map机制,通过block映射方式直接填充PMD entry完成。
linux内存管理3 fixmap_第3张图片
    fixmap的初始代码如下(位于arch/arm64/mm/mmu.c):

void __init early_fixmap_init(void)
{
	pgd_t *pgdp, pgd;
	pud_t *pudp;
	pmd_t *pmdp;
	unsigned long addr = FIXADDR_START;

	pgdp = pgd_offset_k(addr);                                                    (1)
	pgd = READ_ONCE(*pgdp);
	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));
		pudp = pud_offset_kimg(pgdp, addr);                                        (2)
	} else {
		if (pgd_none(pgd))
			__pgd_populate(pgdp, __pa_symbol(bm_pud), PUD_TYPE_TABLE);             (3)
		pudp = fixmap_pud(addr);                                                   (4)
	}
	if (pud_none(READ_ONCE(*pudp)))
		__pud_populate(pudp, __pa_symbol(bm_pmd), PMD_TYPE_TABLE);                  (5)
	pmdp = fixmap_pmd(addr);                                                        (6)
	__pmd_populate(pmdp, __pa_symbol(bm_pte), PMD_TYPE_TABLE);                      (7)

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

	if ((pmdp != fixmap_pmd(fix_to_virt(FIX_BTMAP_BEGIN)))
	     || pmdp != fixmap_pmd(fix_to_virt(FIX_BTMAP_END))) {
		WARN_ON(1);
		pr_warn("pmdp %p != %p, %p\n",
			pmdp, 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);
	}
}

    上面已经详细说明了初始化页表建立的方式,下面结合代码简要介绍下各步执行的操作:
    (1)获取fixmap起始地址的pgd entry指针
    (2)由于swapper页表在此之前只有映射kernle image时填充了一个entry,因此若该entry为非空,说明其与kernle image位于同一个entry,此时基于kernel image的pud页表计算相应的pud entry指针
    (3)fixmap的pgd entry与kernel image不同,此时,将bm_pud的物理地址写入该pgd entry,以使其指向pud页表基地址
    (4)计算该地址的pud entry指针
    (5)将将pmd页表bm_pmd的物理地址基地址写入pud entry中
    (6)计算该地址的pmd entry指针
    (7)将pte页表bm_pte的物理地址基地址写入pmd entry中
    (8)其后的代码都是一些参数校验

2.fixmap在dtb映射中的应用

    fixmap映射dtb内存的函数调用关系为(位于arch/arm64/mm/mmu.c):
fixmap_remap_fdt --> __fixmap_remap_fdt --> create_mapping_noalloc -->__create_pgd_mapping --> alloc_init_pud --> alloc_init_pmd
    特的映射过程映射没有什么特别,主要工作也是根据虚拟地址的值建立各级页表,并将物理地址和prot指示的flag信息组合以后的内容存放到末级页表中,具体的映射过程可以参考以上代码流程,此处不再一 一分析了。
    当然,dyb映射也有一些自己的特点:
    第一,其使能了2M对齐的block映射,因此其末级页表为pmd,且其相应的entry会设置为2M对齐的物理地址与prot组合的值,其过程可参考下面代码的(1) - (7)步。
    (1)判断是否使能了block mapping,以及物理地址和虚拟地址是否2M对齐
    (2)将pmd entry设置为该物理地址和prot的值,即其指向该物理地址为基地址的2M block
    (3)-(4)根据pmd entry的内容决定是否刷新tlb。若pmd entry中原先就存在内容,则此处会修改其值,因此它可能会导致tlb中的内容与实际页表内容不一致(若老的pmd entry已经加载到tlb中了),故需要刷新tlb内容
    (5)-(7)根据pmd entry中原先是否存在内容,决定是否需要将相应内存从memblock的的reserve区移除

static void alloc_init_pmd(pud_t *pud, unsigned long addr, unsigned long end,
				  phys_addr_t phys, pgprot_t prot,
				  phys_addr_t (*pgtable_alloc)(void),
				  bool allow_block_mappings)
{
	pmd_t *pmd;
	unsigned long next;

	/*
	 * Check for initial section mappings in the pgd/pud and remove them.
	 */
	BUG_ON(pud_sect(*pud));
	if (pud_none(*pud)) {
		phys_addr_t pmd_phys;
		BUG_ON(!pgtable_alloc);
		pmd_phys = pgtable_alloc();
		pmd = pmd_set_fixmap(pmd_phys);
		__pud_populate(pud, pmd_phys, PUD_TYPE_TABLE);
		pmd_clear_fixmap();
	}
	BUG_ON(pud_bad(*pud));

	pmd = pmd_set_fixmap_offset(pud, addr);
	do {
		next = pmd_addr_end(addr, end);
		/* try section mapping first */
		if (((addr | next | phys) & ~SECTION_MASK) == 0 &&
		      allow_block_mappings) {                                        (1)
			pmd_t old_pmd =*pmd;
			pmd_set_huge(pmd, phys, prot);                                   (2)
			/*
			 * Check for previous table entries created during
			 * boot (__create_page_tables) and flush them.
			 */
			if (!pmd_none(old_pmd)) {                                        (3)
				flush_tlb_all();                                             (4)
				if (pmd_table(old_pmd)) {                                    (5)
					phys_addr_t table = pmd_page_paddr(old_pmd);             (6)
					if (!WARN_ON_ONCE(slab_is_available()))
						memblock_free(table, PAGE_SIZE);                     (7)
				}
			}
		} else {
			alloc_init_pte(pmd, addr, next, __phys_to_pfn(phys),
				       prot, pgtable_alloc);
		}
		phys += next - addr;
	} while (pmd++, addr = next, addr != end);

	pmd_clear_fixmap();
}

    第二,dtb映射会先映射2M的空间,若dtb的全部地址范围都在这2M之间,则映射过程结束。否则,它会再映射其后的2M空间,其代码实现如下,其中(1)为对起始2M空间的映射,(2)为对后2M空间的映射。

void *__init __fixmap_remap_fdt(phys_addr_t dt_phys, int *size, pgprot_t prot)
{
	const u64 dt_virt_base = __fix_to_virt(FIX_FDT);
	int offset;
	void *dt_virt;

	/*
	 * Check whether the physical FDT address is set and meets the minimum
	 * alignment requirement. Since we are relying on MIN_FDT_ALIGN to be
	 * at least 8 bytes so that we can always access the magic and size
	 * fields of the FDT header after mapping the first chunk, double check
	 * here if that is indeed the case.
	 */
	BUILD_BUG_ON(MIN_FDT_ALIGN < 8);
	if (!dt_phys || dt_phys % MIN_FDT_ALIGN)
		return NULL;

	/*
	 * Make sure that the FDT region can be mapped without the need to
	 * allocate additional translation table pages, so that it is safe
	 * to call create_mapping_noalloc() this early.
	 *
	 * On 64k pages, the FDT will be mapped using PTEs, so we need to
	 * be in the same PMD as the rest of the fixmap.
	 * On 4k pages, we'll use section mappings for the FDT so we only
	 * have to be in the same PUD.
	 */
	BUILD_BUG_ON(dt_virt_base % SZ_2M);

	BUILD_BUG_ON(__fix_to_virt(FIX_FDT_END) >> SWAPPER_TABLE_SHIFT !=
		     __fix_to_virt(FIX_BTMAP_BEGIN) >> SWAPPER_TABLE_SHIFT);

	offset = dt_phys % SWAPPER_BLOCK_SIZE;
	dt_virt = (void *)dt_virt_base + offset;

	/* map the first chunk so we can read the size from the header */
	create_mapping_noalloc(round_down(dt_phys, SWAPPER_BLOCK_SIZE),                        (1)
			dt_virt_base, SWAPPER_BLOCK_SIZE, prot);

	if (fdt_magic(dt_virt) != FDT_MAGIC)
		return NULL;

	*size = fdt_totalsize(dt_virt);
	if (*size > MAX_FDT_SIZE)
		return NULL;

	if (offset + *size > SWAPPER_BLOCK_SIZE)
		create_mapping_noalloc(round_down(dt_phys, SWAPPER_BLOCK_SIZE), dt_virt_base,     (2)
			       round_up(offset + *size, SWAPPER_BLOCK_SIZE), prot);

	return dt_virt;
}

3.fixmap在early ioremap中的应用

early ioremap映射的映射机制
    early ioremap的虚拟地址使用btmap的虚拟内存为io内存建立页表,根据上面的分析我们知道这段虚拟地址位于dtb地址之下2M范围内,而这2M内存已经在fixmap初始化时创建好了pgd,pud,pmd和pte页表。在ioremap时只需根据虚拟地址的bit12 - bit20获取其pte entry指针,并将经过页对齐操作的相应物理地址和prot组合的值填入该pte entry中,即可建立完整映射。最后,再将页对齐的虚拟地址与物理地址低12的offset相加就是给定物理地址的虚拟地址。

early ioremap的初始化
    early ioremap使用slot来管理每一次的ioremap操作,它用以下三个数据结构来管理每个slot的映射情况。slot_virt表示每个slot的起始虚拟地址,prev_map表示已经分配出去slot的地址,prev_size表示已经分配出去slot的size。

static void __iomem *prev_map[FIX_BTMAPS_SLOTS] __initdata;
static unsigned long prev_size[FIX_BTMAPS_SLOTS] __initdata;
static unsigned long slot_virt[FIX_BTMAPS_SLOTS] __initdata;

    slot_virt的内容在ioremap初始化函数中被设置,其代码如下,即每个slot的虚拟地址会被设置为以BTMAP起始地址开始,以256k为单位递增的值。

void __init early_ioremap_setup(void)
{
	int i;

	for (i = 0; i < FIX_BTMAPS_SLOTS; i++)                               
		if (WARN_ON(prev_map[i]))                                             (1)判断early ioremap是否已经被调用过
			break;
	for (i = 0; i < FIX_BTMAPS_SLOTS; i++)
		slot_virt[i] = __fix_to_virt(FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*i);      (2)根据BTMAP的起始虚拟地址和当前slot值设置其地址
}

early ioremap映射函数
    在early ioremap初始化完成后,early_ioremap ()接口即可被其它的模块调用以用于io地址的映射。该函数会从虚拟地址slots中查找一个空闲的slot,然后直接计算该虚拟地址对应的pte entry,并将物理地址与prot组合成的entry值填入该entry,以实现io地址的页表创建。它的代码实现位于linux/mm/early_ioremap.c, 相应的调用关系为early_ioremap --> __early_ioremap --> __early_set_fixmap / __late_set_fixmap -->__set_fixmap。其主要的实现函数为__early_ioremap和__set_fixmap,具体实现可参考如下代码及注释:

static void __init __iomem *
__early_ioremap(resource_size_t phys_addr, unsigned long size, pgprot_t prot)
{
	unsigned long offset;
	resource_size_t last_addr;
	unsigned int nrpages;
	enum fixed_addresses idx;
	int i, slot;

	WARN_ON(system_state >= SYSTEM_RUNNING);

	slot = -1;
	for (i = 0; i < FIX_BTMAPS_SLOTS; i++) {                             (1)从prev_map[i]中找到一个空闲的entry
		if (!prev_map[i]) {
			slot = i;
			break;
		}
	}

	if (WARN(slot < 0, "%s(%08llx, %08lx) not found slot\n",
		 __func__, (u64)phys_addr, size))
		return NULL;

	/* Don't allow wraparound or zero size */
	last_addr = phys_addr + size - 1;                             (2)计算需要映射的物理地址结尾 
	if (WARN_ON(!size || last_addr < phys_addr))
		return NULL;
		
	prev_size[slot] = size;                                       (3)	将映射的size值填入相应slot的prev_size结构中
	/*
	 * Mappings have to be page-aligned
	 */
	offset = offset_in_page(phys_addr);                            (4)根据起始物理地址的值计算其偏移量
	phys_addr &= PAGE_MASK;                                        (5)将起始物理地址做页对齐操作
	
	size = PAGE_ALIGN(last_addr + 1) - phys_addr;                  (6)计算页对齐后的size值

	/*
	 * Mappings have to fit in the FIX_BTMAP area.
	 */
	nrpages = size >> PAGE_SHIFT;                                   (7)计算需要映射的页的数量
	if (WARN_ON(nrpages > NR_FIX_BTMAPS))
		return NULL;

	/*
	 * Ok, go for it..
	 */
	idx = FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*slot;
	while (nrpages > 0) {                                            (8)逐页操作,在bm_pte页表中设置该地址对应entry的值
		if (after_paging_init)
			__late_set_fixmap(idx, phys_addr, prot);
		else
			__early_set_fixmap(idx, phys_addr, prot);
		phys_addr += PAGE_SIZE;
		--idx;
		--nrpages;
	}
	WARN(early_ioremap_debug, "%s(%08llx, %08lx) [%d] => %08lx + %08lx\n",
	     __func__, (u64)phys_addr, size, slot, offset, slot_virt[slot]);

	prev_map[slot] = (void __iomem *)(offset + slot_virt[slot]);        (9)将其起始地址的虚拟地址保存到prev_map相应的slot中
	return prev_map[slot];                                              (10)返回该物理地址起始值对应虚拟地址的值
}

    __set_fixmap可以用于设置fixmap的pte,实现early ioremap,也可以用于清除fixmap的pte,以实现early iounmap。其操作方式通过传入的flags参数判断,当该值为FIXMAP_PAGE_CLEAR时,会执行步骤(5)的清除pte操作 ,否则执行步骤(4)的设置pte操作。其代码及其注释如下:

void __set_fixmap(enum fixed_addresses idx,
			       phys_addr_t phys, pgprot_t flags)
{
	unsigned long addr = __fix_to_virt(idx);                       (1)计算该idx对应的虚拟地址
	pte_t *pte;

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

	pte = fixmap_pte(addr);                                         (2)计算该地址的pte entry,其中pte为bm_pte

	if (pgprot_val(flags)) {								        (3)根据flag的值确定pte entry的操作方式,在early_iounmap中,传入的值为FIXMAP_PAGE_CLEAR,
	                                                                     否则,为在early ioremap中执行该操作,用于执行设置pte的操作。
		set_pte(pte, pfn_pte(phys >> PAGE_SHIFT, flags));           (4)flags为非FIXMAP_PAGE_CLEAR,将物理地址及flag组合成的值设置到idx对应pte的entry中
	} else {
		pte_clear(&init_mm, addr, pte);                             (5)flags的值为FIXMAP_PAGE_CLEAR,执行清除pte中相应entry的old值,并刷新tlb操作
		flush_tlb_kernel_range(addr, addr+PAGE_SIZE);
	}
}

early ioremap解除映射函数
   由于early ioremap的虚拟地址池只有7个slot,因此若对使用完成的映射不解除映射,则可能会造成后续的early ioremap操作找不到空闲的slot。故与动态内存分配的malloc函数类似,early ioremap函数映射的地址在使用完成后,需要使用early_iounmap()函数解除映射,以释放虚拟地址资源。
   early_iounmap()函数的实现及其注释如下:

void __init early_iounmap(void __iomem *addr, unsigned long size)
{
	unsigned long virt_addr;
	unsigned long offset;
	unsigned int nrpages;
	enum fixed_addresses idx;
	int i, slot;

	slot = -1;
	for (i = 0; i < FIX_BTMAPS_SLOTS; i++) {                              (1)从已分配slot数组prev_map中,查找其虚拟地址与给定地址相等的slot
		if (prev_map[i] == addr) {
			slot = i;
			break;
		}
	}

	if (WARN(slot < 0, "early_iounmap(%p, %08lx) not found slot\n",
		 addr, size))                                                      (2)若未找到,直接返回
		return;

	if (WARN(prev_size[slot] != size,
		 "early_iounmap(%p, %08lx) [%d] size not consistent %08lx\n",
		 addr, size, slot, prev_size[slot]))                                (3)校验给定size是否与保存在prev_size中映射该地址时的size是否相等,并在不相等时返回
		return;

	WARN(early_ioremap_debug, "early_iounmap(%p, %08lx) [%d]\n",
	     addr, size, slot);                                                  (4)打印信息

	virt_addr = (unsigned long)addr;
	if (WARN_ON(virt_addr < fix_to_virt(FIX_BTMAP_BEGIN)))                   (5)校验给定虚拟地址是否处于BTMAP的地址空间
		return;

	offset = offset_in_page(virt_addr);                                       (6)计算该虚拟地址的低12位offset
	nrpages = PAGE_ALIGN(offset + size) >> PAGE_SHIFT;                        (7)计算需要解除映射地址的页数

	idx = FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*slot;
	while (nrpages > 0) {                                                     (8)逐页执行解除映射的操作
		if (after_paging_init)
			__late_clear_fixmap(idx);                                          (9)该操作与步骤(10)一样,也会调用__set_fixmap()函数
		else
			__early_set_fixmap(idx, 0, FIXMAP_PAGE_CLEAR);                     (10)该函数会调用__set_fixmap()函数,其已在ioremap中介绍过,当其flag为FIXMAP_PAGE_CLEAR
			                                                                         时,会执行清除fixmap的pte entry,并刷新tlb操作
		--idx;
		--nrpages;
	}
	/* 将prev map设置为null */
	prev_map[slot] = NULL;                                                      (11)清除prev_map的内容,以将该slot的虚拟地址空间
	                                                                                  释放给新的early ioremap使用
}

你可能感兴趣的:(内存管理)