fixmap addresses原理
作者:smcdef 发布于:2018-4-29 20:35 分类:内存管理
Fixmap Addresses原理
引言
fixmap是一段固定地址映射。kernel预留一段虚拟地址空间。因此虚拟地址是在编译的时候确定。fixmap可以用来做什么?kernel启动初期,由于此时的kernel已经运行在虚拟地址上。因此我们访问具体的物理地址是不行的,必须建立虚拟地址和物理地址的映射,然后通过虚拟地址访问才可以。例如:dtb中包含bootloader传递过来的内存信息,我们需要解析dtb,但是我们得到的是dtb的物理地址。因此访问之前必须创建映射,创建映射又需要内存。但是由于所有的内存管理子系统还没有ready。因此我们不能使用ioremap接口创建映射。为此kernel提出fixmap的解决方案。
注:文章代码分析基于linux-4.15,架构基于aarch64(ARM64)。
fixmap空间分配
fixmap虚拟地址空间又被平均分成两个部分permanent fixed addresses和temporary fixed addresses。permanent fixed addresses是永久映射,temporary fixed addresses是临时映射。永久映射是指在建立的映射关系在kernel阶段不会改变,仅供特定模块一直使用。临时映射就是模块使用前创建映射,使用后解除映射。
fixmap区域又被继续细分,分配给不同模块使用。kernel中定义枚举类型作为index,根据index可以计算该模拟在fixmap区域的虚拟地址。
enum fixed_addresses { FIX_HOLE, #define FIX_FDT_SIZE (MAX_FDT_SIZE + SZ_2M) FIX_FDT_END, FIX_FDT = FIX_FDT_END + FIX_FDT_SIZE / PAGE_SIZE - 1, /* 1 */ FIX_EARLYCON_MEM_BASE, /* 2 */ FIX_TEXT_POKE0, __end_of_permanent_fixed_addresses, #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, /* 3 */ FIX_PTE, FIX_PMD, FIX_PUD, FIX_PGD, __end_of_fixed_addresses }; #define FIXADDR_SIZE (__end_of_permanent_fixed_addresses << PAGE_SHIFT) #define FIXADDR_START (FIXADDR_TOP - FIXADDR_SIZE)
- FIX_FDT映射设备树使用。在ARM64架构,大小是4M。
- early console使用,大小1页。1页虚拟地址空间完全够了,毕竟串口操作相关寄存器没有几个。
- early_ioremap()接口使用,这部分属于动态映射。大小是7 × 256KB。
fixmap区域是地址空间范围从FIXADDR_START到FIXADDR_TOP结束。FIXADDR_SIZE是permanent fixed addresses区域的大小。我对这个地方很奇怪,为什么不包括temporary fixed addresses区域的大小呢?如果你知道可以告诉我。fixmap区域可以想象成一块内存以页为单位被平均分成__end_of_permanent_fixed_addresses块。而这些枚举值就是这块内存的index。因此虚拟地址可以根据index进行计算。计算方法如下。
#define __fix_to_virt(x) (FIXADDR_TOP - ((x) << PAGE_SHIFT)) #define __virt_to_fix(x) ((FIXADDR_TOP - ((x) & PAGE_MASK)) >> PAGE_SHIFT)
FIX_FDT是给dtb创建映射的区域。例如需要得到FDT的虚拟地址,即可以利用__fix_to_virt(FIX_FDT)得到虚拟地址。之所以FIX_FDT放在枚举的最前面,是因为我们针对dtb映射采用section mapping要求虚拟地址2M对齐。FIXADDR_TOP地址本身是2M对齐的,因此FIXADDR_TOP - (FIX_FDT << PAGE_SHIFT)可以很容易2M对齐。
fixmap初始化
fixmap初始化操作在early_fixmap_init函数中完成。主要是建立PGD/PUD/PMD页表。early_fixmap_init实现如下(简化部分代码逻辑)。
static pte_t bm_pte[PTRS_PER_PTE] __page_aligned_bss; /* 1 */ static pmd_t bm_pmd[PTRS_PER_PMD] __page_aligned_bss __maybe_unused;/* 1 */ static pud_t bm_pud[PTRS_PER_PUD] __page_aligned_bss __maybe_unused;/* 1 */ void __init early_fixmap_init(void) { pgd_t *pgd; pud_t *pud; pmd_t *pmd; unsigned long addr = FIXADDR_START; /* 2 */ pgd = pgd_offset_k(addr); if (pgd_none(*pgd)) __pgd_populate(pgd, __pa_symbol(bm_pud), PUD_TYPE_TABLE); /* 2 */ pud = fixmap_pud(addr); if (pud_none(*pud)) __pud_populate(pud, __pa_symbol(bm_pmd), PMD_TYPE_TABLE); /* 2 */ pmd = fixmap_pmd(addr); __pmd_populate(pmd, __pa_symbol(bm_pte), PMD_TYPE_TABLE); /* 2 */ BUILD_BUG_ON((__fix_to_virt(FIX_BTMAP_BEGIN) >> PMD_SHIFT) != (__fix_to_virt(FIX_BTMAP_END) >> PMD_SHIFT)); /* 3 */ if ((pmd != fixmap_pmd(fix_to_virt(FIX_BTMAP_BEGIN))) || pmd != fixmap_pmd(fix_to_virt(FIX_BTMAP_END))) { /* 3 */ WARN_ON(1); } }
- 静态定义数组作为页表(PUD/PMD/PTE)使用。PGD页表使用swapper_pg_dir。
- 以FIXADDR_START为虚拟地址,建立页表映射关系。假设计算FDT的虚拟地址为addr = __fix_to_virt(FIX_FDT),必然addr是2M对齐的一个地址。由上面的分析可知,FIXADDR_START的值位于[addr - 2M, addr]之间。因此访问[addr - 2M, addr]之间的虚拟地址不需要再建立PUD/PMD页表项。只需要设置PTE页表对应的页表项即可。
- [FIX_BTMAP_BEGIN, FIX_BTMAP_END]区域给动态映射使用,保证该区域正好位于[addr - 2M, addr]之间,必须检查动态映射区域小于2M。定义的PTE页表数组其实是给动态映射使用的。当我们需要访问物理地址A,从[addr - 2M, addr]区域找到一个合适的虚拟地址B,填充B地址对应的PTE页表项即可访问。这也是early_ioremap的实现原理。
经过early_fixmap_init函数的探究,我们也可以得到一个结论:为了以page为单位进行映射,必须保证FIX_FDT和__end_of_fixed_addresses之间的虚拟地址空间必须小于2M。如果有超过2M的部分就要使用section mapping(因为只有一个PTE页表),以2M为单位映射。
early ioremap初始化
如果你希望kernel启动早期使用ioremap操作,其实是不行的。我们必须借助early ioremap接口。early ioremap是基于fixmap实现。初始化在early_ioremap_init完成。简化部分代码如下。
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_setup(void) { int i; for (i = 0; i < FIX_BTMAPS_SLOTS; i++) slot_virt[i] = __fix_to_virt(FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*i); } void __init early_ioremap_init(void) { early_ioremap_setup(); }
early ioremap利用slot管理映射,最多支持FIX_BTMAPS_SLOTS个映射,每个映射最大支持映射256KB。slot_virt数组存储每个slot的虚拟地址首地址。prev_map数组用来记录已经分配出去的虚拟地址,数组值为0代表没有分配。prev_size记录映射的size。
创建FDT映射
创建FDT映射的函数是__fixmap_remap_fdt,实现如下。
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); /* 1 */ int offset; void *dt_virt; BUILD_BUG_ON(dt_virt_base % SZ_2M); /* 2 */ BUILD_BUG_ON(__fix_to_virt(FIX_FDT_END) >> SWAPPER_TABLE_SHIFT != __fix_to_virt(FIX_BTMAP_BEGIN) >> SWAPPER_TABLE_SHIFT); /* 3 */ offset = dt_phys % SWAPPER_BLOCK_SIZE; dt_virt = (void *)dt_virt_base + offset; create_mapping_noalloc(round_down(dt_phys, SWAPPER_BLOCK_SIZE), /* 4 */ dt_virt_base, SWAPPER_BLOCK_SIZE, prot); *size = fdt_totalsize(dt_virt); if (offset + *size > SWAPPER_BLOCK_SIZE) create_mapping_noalloc(round_down(dt_phys, SWAPPER_BLOCK_SIZE), dt_virt_base, round_up(offset + *size, SWAPPER_BLOCK_SIZE), prot); /* 5 */ return dt_virt; }
- 根据__fix_to_virt计算fdt虚拟地址。
- 虚拟地址必须2M对齐。主要是为了section mapping。
- 在early_fixmap_init函数中,已经建立了PUD/PMD页表。为了让映射过程不用分配额外的PMD页表内存,这里必须保证FDT所在的虚拟地址范围落在early_fixmap_init函数建立的PMD页表范围内。
- 创建页表。这里映射2M空间。
- 如果dtb文件结尾地址超过上一个建立映射的地址范围,就必须紧接着再映射2M空间。

评论:
2018-07-09 11:28
2018-07-06 16:44
(1) 在kernel啟動初期,我們藉著使用ioremap接口創建虛擬地址和物理地址的映射
(2) fixmap區域是地址空間範圍從FIXADDR_START到FIXADDR_TOP結束, FIXADDR_SIZE是temporary fixed addresses區域的大小
(3) kernel正常運作時,運作temporary fixed addresses的資料必須是特權指令的
(4) FIX_FDT是給dtb創建映射的區域。例如需要得到FDT的虛擬地址,即可以利用__virt_to_fix(x)得到虛擬地址,也就是運作(FIXADDR_TOP - ((x) << PAGE_SHIFT))
(5) Device Tree Blob中包含bootloader傳遞過來的內存信息,而我們得到的是Device Tree Blob的物理地址
2018-05-06 16:51
2018-05-06 17:57
我是这么想的,临时映射仅仅是系统启动初期,ioremap等子系统还没有ready的时候使用,当内存管理等各个子系统ready之后,临时映射在ARM64中实际上就没有用武之地了,当然此时也不会有相关的模块还是用临时映射区域。所以临时映射区域的虚拟地址实际上并没有占用(内存管理系统没有ready的时候才占用),因此不需要显示的宏定义告诉你这部分地址被分配了。
这位兄弟,你觉得呢?
功能
最新评论
文章分类
随机文章
文章存档
- 2024年2月(1)
- 2023年5月(1)
- 2022年10月(1)
- 2022年8月(1)
- 2022年6月(1)
- 2022年5月(1)
- 2022年4月(2)
- 2022年2月(2)
- 2021年12月(1)
- 2021年11月(5)
- 2021年7月(1)
- 2021年6月(1)
- 2021年5月(3)
- 2020年3月(3)
- 2020年2月(2)
- 2020年1月(3)
- 2019年12月(3)
- 2019年5月(4)
- 2019年3月(1)
- 2019年1月(3)
- 2018年12月(2)
- 2018年11月(1)
- 2018年10月(2)
- 2018年8月(1)
- 2018年6月(1)
- 2018年5月(1)
- 2018年4月(7)
- 2018年2月(4)
- 2018年1月(5)
- 2017年12月(2)
- 2017年11月(2)
- 2017年10月(1)
- 2017年9月(5)
- 2017年8月(4)
- 2017年7月(4)
- 2017年6月(3)
- 2017年5月(3)
- 2017年4月(1)
- 2017年3月(8)
- 2017年2月(6)
- 2017年1月(5)
- 2016年12月(6)
- 2016年11月(11)
- 2016年10月(9)
- 2016年9月(6)
- 2016年8月(9)
- 2016年7月(5)
- 2016年6月(8)
- 2016年5月(8)
- 2016年4月(7)
- 2016年3月(5)
- 2016年2月(5)
- 2016年1月(6)
- 2015年12月(6)
- 2015年11月(9)
- 2015年10月(9)
- 2015年9月(4)
- 2015年8月(3)
- 2015年7月(7)
- 2015年6月(3)
- 2015年5月(6)
- 2015年4月(9)
- 2015年3月(9)
- 2015年2月(6)
- 2015年1月(6)
- 2014年12月(17)
- 2014年11月(8)
- 2014年10月(9)
- 2014年9月(7)
- 2014年8月(12)
- 2014年7月(6)
- 2014年6月(6)
- 2014年5月(9)
- 2014年4月(9)
- 2014年3月(7)
- 2014年2月(3)
- 2014年1月(4)
2018-12-12 15:33
by fix_to_virt() will be located here.
PKMAP_BASE PAGE_OFFSET-1 Permanent kernel mappings
One way of mapping HIGHMEM pages into kernel
space.
我看arm的内存分布,fixmap仅仅代表的是临时映射,永久映射区的定义在PKMAP那里。
PKMAP也就是Permanent kernel mappings 永久映射。
我的理解看来和楼主有点不同