Linux内核固定虚拟地址映射

前面我们说到,为kernel image设置了虚实地址转换表,并且开启了mmu。但是现在从虚拟地址空间CPU只能看到kernel image,如果此时想访问其他物理地址空间怎么办?用ioremap吗?要知道,此时内存子系统还没有初始化,ioremap无法工作。为了解决这一问题,Linux内核定义了一段固定的虚拟地址空间,所谓固定就是说在编译时就确定的,内核启动早期会将某些物理地址映射到这段固定虚拟地址空间。

谁要使用虚拟地址空间?

我们先来想一个问题,谁会使用这段固定虚拟地址空间?乍一看这个问题可不好回答,那么可以换一种问法,CPU要使用哪些物理地址空间?这个问题就比较容易了吧!哈哈,想到点什么没有?至少有两点是比较容易想到的:
DTB:目前DTB在linux内核中可谓深入人心,DTB用来描述硬件拓扑及资源信息,linux启动早期需要解析DTB文件,获取硬件拓扑及资源信息。
Console:调试过Linux内核启动的朋友应该都是知道串口控制台的重要性,基于调试的考虑,我们往往希望尽可能早的通过串口控制台打印我们需要的信息,那么就需要访问UART的控制器。
下面是linux所定义的固定虚拟地址空间的layout:

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,
    __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
};

- FIX_FDT—FIX_FDT_END(4M)
用于DTB.
- FIX_EARLYCON_MEM_BASE
用于早期串口控制台。
- FIX_TEXT_POKE0
用法不明。
- FIX_BTMAP_BEGIN—FIX_BTMAP_END
用于早期ioremap。
- FIX_PTE
pte table固定虚拟地址,用于临时映射。
- FIX_PMD
pmd table固定虚拟地址,用于临时映射。
- FIX_PUD
pud table固定虚拟地址,用于临时映射。
- FIX_PGD
pgd table固定虚拟地址,用于临时映射。

下面的图能更清晰的反应固定虚拟地址空间的layout:
这里写图片描述

重要函数解释

  • pgd_offset_k(addr)
    根据传入的虚拟地址addr,计算得到pgd table中的某个entry,并返回其虚拟地址。
  • pgd_populate(struct mm_struct *mm, pgd_t *pgd, pud_t *pud)
    将pud table的物理基地址填充到pgd entry中。
  • fixmap_pud(unsigned long addr)
    根据传入的虚拟地址,计算得到pud table中的某个entry,并返回其虚拟地址。
  • pud_populate(struct mm_struct *mm, pud_t *pud, pmd_t *pmd)
    将pmd table的物理基地址填充到pud entry中。
  • fixmap_pmd(unsigned long addr)
    根据传入的虚拟地址,计算得到pmd table中的某个entry,并返回其虚拟地址。
  • pmd_populate_kernel(struct mm_struct *mm, pmd_t *pmdp, pte_t *ptep)
    将pte table的物理地址填充到pmd entry中。
    以上六个函数为一个虚拟地址设置了三级转换,但是并没有设置第四级转换,第四级转换映射到物理地址。

代码逻辑分析

1. 固定映射初始化:early_fixmap_init

为虚拟地址FIXADDR_START(即固定虚拟地址空间的起始地址)设置前三级转换。
单看这个函数的逻辑,其实特别简单,就是根据虚拟地址找到pgd table中的某个entry,并填充pud table的物理基地址;再根据虚拟地址找到pud table中的某个entry,并填充pmd table的物理地址;在根据虚拟地址找到pmd table中的某个entry,并填充pte table的物理地址,并没有将虚拟地址映射到物理地址。
代码逻辑看似简单,实际上暗藏玄机,如果不把此间玄机窥探清除,读后面的代码会非常迷惑,下面我们就把其中玄机一点一点呈现出来。

- 玄机1:FIXADDR_START神秘之处。

回头看下固定虚拟地址layout,FIXADDR_TOP是2M对齐的,这个从FIXADDR_TOP定义可以知道,而FIXADDR_START并非2M对齐,这一点其实非常重要,后面的两个玄机都与此相关。

- 玄机2:为DTB固定虚拟地址设置了前两级转换表,即PDG,PUD转换表。

DTB是按section来作映射的,section映射需要三级转换,一条section映射覆盖2M空间,由于FIXADDR_TOP是2M对齐的,因而两个连续的pmd entry即可覆盖4M的DTB虚拟地址空间,后面只需要填充最多两个PMD entry即可。

- 玄机3:为其他固定虚拟地址设置了前三级转换表,及PGD,PUD,PMD转换表。

由于FIXADDR_START不是2M对齐的,此时设置的pmd entry覆盖所有的非DTB虚拟地址空间,后面映射到实际物理地址的时候,只需要填充该PMD entry指向的PTE table中的某个entry即可。

2. 早期ioremap初始化:early_ioremap_init

为了在内存子系统起来之前使用ioremap,linux实现了早期ioremap机制。该函数实现相关的初始化,实际上该初始化函数仅仅是将用于早期ioremap的固定虚拟地址空间FIX_BTMAP_BEGIN_FIX_BITMAP_END的虚拟地址存放到数组slot_virt中。

3. 完成DTB虚实地址映射:__fixmap_remap_fdt

前面early_fixmap_init已经为DTB固定虚拟地址设置了前两级转换表,此时再设置一个或者两个PMD entry即可。那么一个或者两个是怎么定的呢?主要是看DTB在物理地址空间是否跨2M的边界,因为DTB是按section(2M)来做映射的,如果DTB跨2M边界,则需要连续两个pmd entry。由于DTB最大2M,如果DTB物理基地址不是2M对齐的,则有可能出现DTB跨2M边界的情况。

3.1. dt_virt_base = __fix_to_virt(FIX_FDT);

计算得出分配给dtb的虚拟基地址。

3.2. create_mapping_noalloc:first

设置第一个section mapping,主要就是设置PMD entry。注意,这里noalloc的意思是,不会分配新的地址转换table,只会填充某个table的entry。该函数的具体代码我们不去一一分析了,我这里把其中比较重要的点列出来,供有兴趣的朋友分析代码是参考:

  • 该函数中设置DTB虚实地址映射只需要设置一个pmd entry,因为early_fixmap_init函数已经设置了前两级映射。
  • 固定虚拟地址空间定义了四个page,用于访问PGD,PUD,PMD,PTE四级页表,在当前上下文,这四级页表用于其自身的虚实地址映射及DTB虚实地址映射。也就是说首先要为页表做虚实地址映射,以便CPU可以使用虚拟地址访问页表,之后设置页表,实现DTB虚实地址映射。

3.3. create_mapping_noalloc:second

如果第一个section mapping无法覆盖完成的DTB,则需要设置第二个section mapping。

第二阶段主要是分析固定虚拟地址的映射,当然主要讲的是DTB,还有一些其他固定虚拟地址,我们后面遇到还会分析。

猜你喜欢

转载自blog.csdn.net/liuhangtiant/article/details/80386894