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的时候才占用),因此不需要显示的宏定义告诉你这部分地址被分配了。
这位兄弟,你觉得呢?
功能
最新评论
- 牛逼666
牛逼 - hurricane618
@wangxingxing:如果是arm32 arch/a... - ppzzDD
wowo大佬,能否讲一讲关于蓝牙OTA那部。感谢感谢。 - ppzzDD
感谢wowo,2019年开始看wowo大佬的文章,2025年... - test
这个问题有意思,我感觉不是由dsb引起的, 因为这个时候 c... - magina
这里描述的不对,当该值从memory中加载到cache 0中...
文章分类
随机文章
文章存档
- 2025年4月(5)
- 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 永久映射。
我的理解看来和楼主有点不同