1 固定映射
1.1 数据结构
Linux 高端内存中的临时内存区为固定内存区的一部分, 对于固定内存在 Linux 内核中有下面描述
x86 | arm | arm64 |
---|---|---|
arch/x86/include/asm/fixmap.h?v=4.7, line 67 | arch/arm/include/asm/fixmap.h?v=4.7, line 11 | arch/arm64/include/asm/fixmap.h?v=4.7, line 36 |
- /*
- * 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 Windows 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
- };
1.2 固定映射
ioremap 的作用是将 IO 和 BIOS 以及物理地址空间映射到在 896M 至 1G 的 128M 的地址空间内, 使得 kernel 能够访问该空间并进行相应的读写操作.
start_kernel()->setup_arch()->early_ioremap_init()
然后 ARM 和 arm64 上 early_ioremap_init 又是 early_ioremap_setup 的前端
函数 | x86 | arm | arm64 |
---|---|---|---|
early_ioremap_init | arch/x86/mm/ioremap.c?v=4.7, line 445 | arch/arm/mm/ioremap.c?v=4.7, line 489 | arch/arm64/mm/ioremap.c?v=4.7, line 110 |
early_ioremap_setup | mm/early_ioremap.c?v=4.7, line 67 https://note.youdao.com/ | 体系结构无关 | 体系结构无关 |
- /*
- * Must be called after early_fixmap_init
- */
- void __init early_ioremap_init(void)
- {
- early_ioremap_setup();
- }
但是 ARM 和 arm64 下的 setup_arch 函数则会先调用 early_fixmap_init 函数来填充 fixmap. 参见 arch/ARM/kernel/setup.c?v=4.7, line 1058 和 arch/arm64/kernel/setup.c?v=4.7, line 229.
- void __init setup_arch(char **cmdline_p)
- {
- early_fixmap_init();
- early_ioremap_init();
- }
early_fixmap_init 函数的定义在
arm | arm64 |
---|---|
arch/arm/mm/mmu.c?v=4.7, line 385 | arch/arm64/mm/mmu.c?v=4.7, line 676 |
其中 ARM 架构的定义如下所示, 在 arch/ARM/mm/mmu.c?v=4.7, line 385
- 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
- , 此 pmd 为虚拟地址转换为物理地址的 pmd*/
- pmd = fixmap_pmd(FIXADDR_TOP);
- /* 将 bm_pte 页表设置为固定映射区开始地址的 pmd 的第一个页表;*/
- pmd_populate_kernel(&init_mm, pmd, bm_pte);
- pte_offset_fixmap = pte_offset_early_fixmap;
- }
1.3 ioremap 函数
对于 ioremap 的使用需要通过 early_memremap 和 early_iounmap 进行.
由于对应于 ioremap 的内存空间是有限的, 所以对于 ioremap 空间的使用遵照使用结束马上释放的原则. 这就是说 early_memremap 和 early_iounmap 必须配对使用并且访问结束必须马上执行 unmap
2 临时内核映射
刚才描述的 kmap 函数不能用于中断处理程序, 因为它可能进入睡眠状态. 如果 pkmap 数组中没有空闲位置, 该函数会进入睡眠状态, 直至情形有所改善.
因此内核提供了一个备选的映射函数, 其执行是原子的, 逻辑上称为 kmap_atomic. 该函数的一个主要优点是它比普通的 kmap 快速. 但它不能用于可能进入睡眠的代码. 因此, 它对于很快就需要一个临时页的简短代码, 是非常理想的.
kmap_atomic 的定义在 IA-32, PPC, Sparc32 上是特定于体系结构的, 但这 3 种实现只有非常细微的差别. 其原型是相同的.
2.1 kmap_atomic 函数
- // http://lxr.free-electrons.com/source/arch/arm/mm/highmem.c?v=4.7#L55
- void *kmap_atomic(struct page *page)
page 是一个指向高端内存页的管理结构的指针, 而早期的内核中, 增加了一个类型为 enum km_type 的 type 参数, 用于指定所需的映射类型
- // http://lxr.free-electrons.com/source/arch/arm/mm/highmem.c?v=2.6.32#L39
- void *kmap_atomic(struct page *page, enum km_type type)
而在新的内核中, 删除了这个标识, 但是保留了 km_type 的最大值 KM_TYPE_NR
- void *kmap_atomic(struct page *page)
- {
- unsigned int idx;
- unsigned long vaddr;
- void *kmap;
- int type;
- preempt_disable();
- pagefault_disable();
- if (!PageHighMem(page))
- return page_address(page);
- #ifdef CONFIG_DEBUG_HIGHMEM
- /*
- * There is no cache coherency issue when non VIVT, so force the
- * dedicated kmap usage for better debugging purposes in that case.
- */
- if (!cache_is_vivt())
- kmap = NULL;
- else
- #endif
- kmap = kmap_high_get(page);
- if (kmap)
- return kmap;
- type = kmap_atomic_idx_push();
- idx = FIX_KMAP_BEGIN + type + KM_TYPE_NR * smp_processor_id();
- vaddr = __fix_to_virt(idx);
- #ifdef CONFIG_DEBUG_HIGHMEM
- /*
- * With debugging enabled, kunmap_atomic forces that entry to 0.
- * Make sure it was indeed properly unmapped.
- */
- BUG_ON(!pte_none(get_fixmap_pte(vaddr)));
- #endif
- /*
- * When debugging is off, kunmap_atomic leaves the previous mapping
- * in place, so the contained TLB flush ensures the TLB is updated
- * with the new mapping.
- */
- set_fixmap_pte(idx, mk_pte(page, kmap_prot));
- return (void *)vaddr;
- }
- EXPORT_SYMBOL(kmap_atomic);
这个函数不会被阻塞, 因此可以用在中断上下文和起亚不能重新调度的地方. 它也禁止内核抢占, 这是有必要的, 因此映射对每个处理器都是唯一的 (调度可能对哪个处理器执行哪个进程做变动).
2.2 kunmap_atomic 函数
可以通过函数 kunmap_atomic 取消映射
- /*
- * Prevent people trying to call kunmap_atomic() as if it were kunmap()
- * kunmap_atomic() should get the return value of kmap_atomic, not the page.
- */
- #define kunmap_atomic(addr) do { BUILD_BUG_ON(__same_type((addr), struct page *)); __kunmap_atomic(addr); } while (0)
这个函数也不会阻塞. 在很多体系结构中, 除非激活了内核抢占, 否则 kunmap_atomic 根本无事可做, 因为只有在下一个临时映射到来前上一个临时映射才有效. 因此, 内核完全可以 "忘掉"kmap_atomic 映射, kunmap_atomic 也无需做什么实际的事情. 下一个原子映射将自动覆盖前一个映射.
来源: http://www.bubuko.com/infodetail-2905467.html