linux 内核虚拟地址空间 --- fixmap 区域

在系统启动的汇编阶段,为kernel image、dtb 建立了临时页表,开启了MMU,进入了虚拟空间的世界,进入到start_kernel,内核要访问内存,要访问IO地址空间,那么就必须要为物理地址建立页表,以实现物理地址和虚拟地址之间的映射。

在内核初始化前期,内存管理系统还未初始化,现在除了临时页表外,主要的还是kernel image空间,其余的物理内存都没有建立页表,那么对于内存管理相关的API接口都无法使用,内核提出了fix mapped address的概念用来解决这些问题,本文主要针对ARM(IMX6ull)体系结构进行分析。

1. fixmap概念

当我们通过创建页表后,开启MMU,进入到start_kernel的世界中,那么当内存管理子系统没有完全初始化成功时候,我们所面对的困难为:

我们无法访问所有的内存,只能访问到临时页表创建的kernel image 和 dtb的地址空间

我们无法访问任何的硬件,这些硬件对应的地址空间还没有完成映射关系

 

当内核完全启动后,内存管理提供了各种各样的API来使各个模块完成物理地址到虚拟地址的映射功能,但是在内核启动的初期,有些模块就需要使用虚拟地址并mapping到指定的物理地址上,这些模块也没有办法等到内核的内存管理模块完全初始化之后再进行映射功能,因此,内核就分配了fixmap这段地址空间,对于ARM32,为0xFFC00000 - 0xFFF00000这段虚拟地址空间,这段地址空间就用来实现前期某些特定的模块实现物理内存映射。

上图为 fixmap 虚拟地址空间,根据下面枚举分为以下几个部分


#define FIXADDR_START       0xffc80000UL
#define FIXADDR_END     0xfff00000UL
#define FIXADDR_TOP     (FIXADDR_END - PAGE_SIZE)

enum
fixed_addresses { FIX_EARLYCON_MEM_BASE, __end_of_permanent_fixed_addresses, FIX_KMAP_BEGIN = __end_of_permanent_fixed_addresses, FIX_KMAP_END = FIX_KMAP_BEGIN + (KM_MAX_IDX * NR_CPUS) - 1, /* Support writing RO kernel text via kprobes, jump labels, etc. */ FIX_TEXT_POKE0, FIX_TEXT_POKE1, __end_of_fixmap_region, /* * Share the kmap() region with early_ioremap(): this is guaranteed * not to clash since early_ioremap() is only available before * paging_init(), and kmap() only after. */ #define NR_FIX_BTMAPS 32 #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, __end_of_early_ioremap_region };

上面枚举项都是以 page 为单位

FIX_EARLYCON_MEM_BASE:和 console 串口有关

FIX_KMAP_BEGIN ~ FIX_KMAP_END:从低地址向高地址使用,kmap()使用,只在 paging_init() 后使用。kmap 内核永久映射空间。

FIX_BTMAP_BEGIN ~ FIX_BTMAP_END:从高地址向低地址使用,early_ioremap()使用,只在 paging_init() 前使用

 

FIXADDR_TOP ~ FIXADDR_END:用途未知

2. fixmap的初始化

在执行setup_arch中,会最先调用 early_fixmap_init() 来映射 fixmap 的一段内存,其代码实现如下

void __init early_fixmap_init(void)
{
    pmd_t *pmd;

    /*
     * The early fixmap range spans multiple pmds, for which
     * we are not prepared:
     */
    BUILD_BUG_ON((__fix_to_virt(__end_of_early_ioremap_region) >> PMD_SHIFT)
             != FIXADDR_TOP >> PMD_SHIFT);

    pmd = fixmap_pmd(FIXADDR_TOP); // 获取 FIXADDR_TOP 在页表 swapper_pg_dir 对应的页表项地址 pmd
    pmd_populate_kernel(&init_mm, pmd, bm_pte); // 把虚拟地址 FIXADDR_TOP 映射到 bm_pte[PTE_HWTABLE_OFF],即访问 FIXADDR_TOP 就是访问 bm_pte[PTE_HWTABLE_OFF]

    pte_offset_fixmap = pte_offset_early_fixmap;
}
  static pte_t * __init pte_offset_early_fixmap(pmd_t *dir, unsigned long addr)
  {
      return &bm_pte[pte_index(addr)];
  }
 
static pte_t bm_pte[PTRS_PER_PTE + PTE_HWTABLE_PTRS]
    __aligned(PTE_HWTABLE_OFF + PTE_HWTABLE_SIZE) __initdata;

然后初始化 early_ioremap 


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;
 

void
__init early_ioremap_init(void) { early_ioremap_setup(); } void __init early_ioremap_setup(void) { int i; for (i = 0; i < FIX_BTMAPS_SLOTS; i++) if (WARN_ON(prev_map[i])) break; for (i = 0; i < FIX_BTMAPS_SLOTS; i++) slot_virt[i] = __fix_to_virt(FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*i); }

 

 初始化 slot_virt[],记录fixmap区域的7个虚拟地址。

之后就可以用以下API

early_ioremap
early_iounmap

用于将IO物理地址映射/解除映射到虚拟地址

 

posted @ 2023-06-22 23:09  流水灯  阅读(147)  评论(0编辑  收藏  举报