ARM64架构下Linux内核Fixmap机制解析
1. Fixmap概述
在Linux内核启动的早期阶段,完整的页表机制尚未建立,动态内存分配器也未初始化。此时,内核需要通过**Fixmap(固定映射)**机制访问特定的物理内存或硬件寄存器。Fixmap是一块预定义的虚拟地址区域,允许内核将物理地址静态映射到固定虚拟地址,解决了早期内存管理受限的问题。
2. Fixmap的核心作用
- 静态映射保证:在启动早期,固定虚拟地址确保关键硬件(如串口、设备树)的可访问性。
- 绕过动态分配:避免依赖
vmalloc()
或kmalloc()
等动态机制,直接通过编译时确定的虚拟地址操作硬件。 - 类型多样性:支持映射物理内存(
FIX_PMD
)、设备寄存器(FIX_EARLYCON_MEM_BASE
)等多种资源。
3. ARM64架构下的Fixmap实现
3.1 虚拟地址空间布局
在ARM64中,内核虚拟地址空间划分如下(以48位地址为例):
0xffff000000000000 - 0xffff7fffffffffff : vmalloc区域
0xffff800000000000 - 0xffffffffffffffff : 内核空间
|- 0xffff800000000000 - 0xffff800007ffffff : 线性映射区(直接映射物理内存)
|- 0xffffb7ffffee0000 - 0xffffb7ffffef0000 : Fixmap区域
Fixmap的起始地址通常由FIXADDR_START
定义,大小由__end_of_permanent_fixed_addresses
决定。
3.2 Fixmap索引与映射类型
Fixmap区域按功能划分为多个子区域,每个区域通过枚举索引标识。例如:
// arch/arm64/include/asm/fixmap.h
enum fixed_addresses {
FIX_HOLE,
FIX_FDT_END,
FIX_FDT,
FIX_EARLYCON_MEM_BASE,
FIX_TEXT_POKE0,
__end_of_permanent_fixed_addresses,
// 临时映射区域
NR_FIX_BTMAPS = 32,
FIX_BTMAP_END = __end_of_permanent_fixed_addresses + NR_FIX_BTMAPS,
__end_of_fixed_addresses
};
- 永久映射(如
FIX_FDT
):用于设备树等长期存在的资源。 - 临时映射(如
FIX_BTMAP
):短时映射,用后立即释放。
3.3 关键操作函数
- 设置映射:通过
set_fixmap(idx, phys_addr)
将物理地址映射到Fixmap的指定索引位置。void __set_fixmap(enum fixed_addresses idx, phys_addr_t phys, pgprot_t prot) { unsigned long vaddr = __fix_to_virt(idx); create_pgd_mapping(swapper_pg_dir, vaddr, phys, FIXMAP_PAGE_SIZE, prot); }
- 清除映射:使用
clear_fixmap(idx)
解除映射。
4. Fixmap使用场景分析
4.1 设备树(FDT)解析
启动早期,设备树物理地址需映射到Fixmap区域以便解析:
// drivers/of/fdt.c
void *initial_boot_params;
void __init early_init_dt_setup(void *params)
{
initial_boot_params = fixmap_remap_fdt(params, &size, PAGE_KERNEL);
}
4.2 早期控制台输出
初始化串口控制台前,需映射UART寄存器:
// drivers/tty/serial/earlycon.c
static int __init earlycon_init(struct earlycon_device *device)
{
device->membase = earlycon_map(addr, size);
// 写入UART寄存器进行初始化
}
4.3 内核代码热补丁
使用FIX_TEXT_POKE0
映射临时可写页,修改运行中的内核代码:
// arch/arm64/kernel/insn.c
void *__kprobes text_poke(void *addr, const void *opcode, size_t len)
{
set_fixmap(FIX_TEXT_POKE0, __pa_symbol(addr));
memcpy((void *)fix_to_virt(FIX_TEXT_POKE0), opcode, len);
clear_fixmap(FIX_TEXT_POKE0);
}
5. 使用Fixmap的示例代码
映射物理内存并访问:
#include <asm/fixmap.h>
void access_phys_mem(phys_addr_t phys)
{
void __iomem *vaddr;
// 设置映射
set_fixmap(FIX_EARLYCON_MEM_BASE, phys);
vaddr = (void __iomem *)fix_to_virt(FIX_EARLYCON_MEM_BASE);
// 读写操作
iowrite32(0x1234, vaddr);
u32 val = ioread32(vaddr);
// 清除映射
clear_fixmap(FIX_EARLYCON_MEM_BASE);
}
6. 注意事项
- 索引冲突:Fixmap区域有限(通常约64项),需确保索引使用不越界。
- 并发安全:Fixmap非原子操作,需在单线程环境(如启动初期)使用。
- 调试支持:启用
CONFIG_DEBUG_VIRTUAL
时,非法地址访问会触发内核警告。
7. 总结
Fixmap机制是ARM64内核早期初始化的基石,通过预编译的虚拟地址映射,解决了硬件访问的关键问题。理解其实现细节和适用场景,有助于在内核开发中高效处理内存映射需求,规避潜在的启动错误。随着内核启动流程的推进,Fixmap的临时映射逐渐被动态机制取代,但其在系统启动阶段的核心地位不可替代。