参考文章:https://www.cnblogs.com/sky-heaven/p/4846835.html
linux内核版本:4.15.0
PAGE_OFFSET 在 arch\arm\include\asm\memory中定义。
#define PAGE_OFFSET UL(CONFIG_PAGE_OFFSET)
CONFIG_PAGE_OFFSET 在内核根目录下的配置文件 .config 中定义
CONFIG_PAGE_OFFSET =0xC0000000
代码:
开头的一些宏定义。
@TEXT_OFFSET 是内核代码(解压后)相对于RAM起始的偏移
@PAGE_OFFSET 内核虚拟地址空间的起始地址(链接地址)
@(suniv 处理器)这里PAGE_OFFSET=0xC0000000
#define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET) @计算得到内核虚拟起始地址0xc0008000,必须8000结尾
#if (KERNEL_RAM_VADDR & 0xffff) != 0x8000 @最后16位必须是0x8000
#error KERNEL_RAM_VADDR must start at 0xXXXX8000
#endif
#ifdef CONFIG_ARM_LPAE @未定义
/* LPAE requires an additional page for the PGD */
#define PG_DIR_SIZE 0x5000
#define PMD_ORDER 3
#else
#define PG_DIR_SIZE 0x4000 @设置MMU页表大小为16k (4Byte * 4095)
#define PMD_ORDER 2
#endif
.globl swapper_pg_dir @页表虚拟起始地址
.equ swapper_pg_dir, KERNEL_RAM_VADDR - PG_DIR_SIZE @计算页表起始虚拟地址
@计算页表的起始物理地址,计算结果返回到rd
.macro pgtbl, rd, phys
add \rd, \phys, #TEXT_OFFSET @phys+TEXT_OFFSET ,内核起始物理地址
sub \rd, \rd, #PG_DIR_SIZE @phys+TEXT_OFFSET-PG_DIR_SIZE ,页表起始地址
.endm
。。。。。。。。。。。。部分代码省略。
创建特定映射,以满足__enable_mmu的需求。
此特定映射将被paging_init()删除。
/*
* Setup the initial page tables. We only setup the barest
* amount which are required to get the kernel running, which
* generally means mapping in the kernel code.
*
* r8 = phys_offset, r9 = cpuid, r10 = procinfo
*
* Returns:
* r0, r3, r5-r7 corrupted
* r4 = physical page table address
*/
__create_page_tables:
pgtbl r4, r8 @ 获取页表起始物理地址
/*
* Clear the swapper page table 清空16k页表
*/
/*
* 清零16K的一级初始页表区
* 这些页表在内核自解压时被设置过
* (此时MMU已关闭)
*/
mov r0, r4
mov r3, #0
add r6, r0, #PG_DIR_SIZE
1: str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
teq r0, r6
bne 1b
/*
* 获取节描述符的默认配置(除节基址外的其他配置)
* 这个数据依构架而不同,数据是用汇编文件配置的:
* arch/arm/mm/proc-xxx.S
* (此时MMU已关闭)
*/
ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
/*
* 创建特定映射,以满足__enable_mmu的需求。
* 此特定映射将被paging_init()删除。
*
* 其实这个特定的映射就是仅映射__enable_mmu功能函数区的页表
* 以保证在启用mmu时代码的正确执行--1:1映射(物理地址=虚拟地址)
*/
/*
* Create identity mapping to cater for __enable_mmu.
* This identity mapping will be removed by paging_init().
*/
adr r0, __turn_mmu_on_loc @获取当前虚拟地址到r0
ldmia r0, {r3, r5, r6} @r3中存放当前物理地址,通过“.”符号获取
sub r0, r0, r3 @ virt->phys offset @ 获取编译时确定的虚拟地址到当前物理地址的偏移(r0-r3)
add r5, r5, r0 @ phys __turn_mmu_on @ 计算__enable_mmu的当前物理地址
add r6, r6, r0 @ phys __turn_mmu_on_end @计算 __enable_mmu_end的当前物理地址
mov r5, r5, lsr #SECTION_SHIFT @右移20位,计算 __enable_mmu的节基址
mov r6, r6, lsr #SECTION_SHIFT @右移20位,计算 __enable_mmu_end的节基址
@对两物理地址右移20位,取高12位,作为存入页表的高12位
1: orr r3, r7, r5, lsl #SECTION_SHIFT @ flags + kernel base @ 生成节描述符:flags + 节基址
@r7存有mm_mmuflags,r5左移20位,或上mm_mmuflags,存入r3,即为应该要存入页表的值
str r3, [r4, r5, lsl #PMD_ORDER] @ identity mapping @ 设置节描述符,1:1映射(物理地址=虚拟地址)
@存入页表,这里链接地址=物理地址,指定页表(页表基址r4)偏移r5(此为物理地址的高12位)左移2位(页表每一页为4字节)
@可以表述为把r3存入 r4+(r5<<2)这个地址。偏移r5*4个地址,因为页表每页描述符占4字节。
cmp r5, r6 @ 完成映射?(理论上一次就够了,这个函数应该不会大于1M吧~)(比较r5与r6是否在同一页内)
addlo r5, r5, #1 @ next section r5和r6不在同一页,继续映射下一页
blo 1b
为.bbs 段做映射。
为内核代码段创建映射。
@下面为.bbs 段做映射
/*
* Map our RAM from the start to the end of the kernel .bss section.
*/
add r0, r4, #PAGE_OFFSET >> (SECTION_SHIFT - PMD_ORDER) @把链接地址映射到相同的物理地址空间0xc0000000
ldr r6, =(_end - 1)
orr r3, r8, r7
add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
1: str r3, [r0], #1 << PMD_ORDER
add r3, r3, #1 << SECTION_SHIFT
cmp r0, r6
bls 1b
#ifdef CONFIG_XIP_KERNEL
/*
* 如果是XIP技术的内核,上面的映射只能映射内核代码和只读数据部分
* 这里我们再映射一些RAM来作为 .data and .bss 空间.
*/
@下面为内核代码创建MMU映射
/*
* 现在创建内核的逻辑映射区页表(节映射)
* 创建范围:KERNEL_START---KERNEL_END
* KERNEL_START:内核最终运行的虚拟地址
* KERNEL_END:内核代码结束的虚拟地址(bss段之后,但XIP不是)
*/
/*
* Map the kernel image separately as it is not located in RAM.
*/
#define XIP_START XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)
mov r3, pc @ 获取当前物理地址
mov r3, r3, lsr #SECTION_SHIFT @ r3 = 当前物理地址的节基址
orr r3, r7, r3, lsl #SECTION_SHIFT @ r3 为当前物理地址的节描述符
/*
* 下面是为了确定页表项的入口地址
* 其实页表入口项的偏移就反应了对应的虚拟地址的高位
*
* 由于ARM指令集的8bit位图问题,只能分两次得到
* XIP_START:内核最终运行的虚拟地址
* 当前所在的页乘上4才是当前页表的节地址(每个节地址表述符占用4个字节)。
*/
add r0, r4, #(XIP_START & 0xff000000) >> (SECTION_SHIFT - PMD_ORDER)
str r3, [r0, #((XIP_START & 0x00f00000) >> SECTION_SHIFT) << PMD_ORDER]! @设置节描述符
ldr r6, =(_edata_loc - 1) @_edata_loc内核结束地址
add r0, r0, #1 << PMD_ORDER @r0指向页表的下一页
add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER) @ r6 = 内核逻辑映射结束的节基址
1: cmp r0, r6 @到最后一页?表示映射完成
add r3, r3, #1 << SECTION_SHIFT @ 生成节描述符(只需做基址递增)
strls r3, [r0], #1 << PMD_ORDER @设置节描述符,r0指向下一页
bls 1b
#endif
映射启动参数区
/*
* 然后映射启动参数区(现在r2中的atags物理地址)
* 或者
* 如果启动参数区的虚拟地址没有确定(或者无效),则会映射RAM的头1MB.
*/
/*
* Then map boot params address in r2 if specified.
* We map 2 sections in case the ATAGs/DTB crosses a section boundary.
*/
@映射r2中的引导参数地址(如果指定的话)
mov r0, r2, lsr #SECTION_SHIFT
@r0存放着启动参数区的物理地址页偏移(要把这个偏移存入页表,建立页表映射)
movs r0, r0, lsl #SECTION_SHIFT @r0不为0,则执行下面这条指令
@比较r0是否为0(为0表示未指定)
@当r0为0时,Z标志被置1
@如果r2没有指定则不进行映射,下面带ne的指令都不执行
subne r3, r0, r8
@Z标志为0(r2存在指定参数)才执行,计算与起始物理地址的偏移量
@r8是物理内存(SDRAM)的起始地址(在s3c2440中是 0x30000 0000)
addne r3, r3, #PAGE_OFFSET @ 转换为虚拟地址
@计算后r3中存放的是参数区起始虚拟地址
addne r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER) @ 确定页表项(节描述符)入口地址
@Z标志为0(r2存在指定参数)才执行,
@计算参数区的页表项地址(虚拟地址所在页),存放到r3
orrne r6, r7, r0 @ 生成节描述符 (物理偏移页生成节描述符,存入r6)
strne r6, [r3], #1 << PMD_ORDER @ 设置节描述符 (设置参数区虚拟地址页到物理地址页的映射)
@虚拟偏移页加1
addne r6, r6, #1 << SECTION_SHIFT @物理偏移页加1
strne r6, [r3] @再继续映射一页
@一共映射了2M空间,每次1M
将串口IO内存之上的所有地址都进行映射
@______________________________________________________________________________________
@ 将串口IO内存之上的所有地址都进行映射
#ifdef CONFIG_DEBUG_LL
/*
* 下面是调试信息的输出函数区
* 这里做了IO内存空间的节映射
* 为串口调试映射IO内存空间(将串口IO内存之上的所有地址都映射了)
* 这允许调试信息(在paging_init之前)从串口控制台输出
*
*/
@---------------------------------------------------------------------------------------
#if !defined(CONFIG_DEBUG_ICEDCC) && !defined(CONFIG_DEBUG_SEMIHOSTING)
/*
* Map in IO space for serial debugging.
* This allows debug messages to be output
* via a serial console before paging_init.
*/
addruart r7, r3, r0 @ 宏代码,位于arch/arm/mach-xxx/include/mach/debug-macro.S
@ 作用是将串口控制寄存器的基址放入r7(物理地址)和r3(虚拟地址)
mov r3, r3, lsr #SECTION_SHIFT
mov r3, r3, lsl #PMD_ORDER
@计算虚拟地址页偏移
add r0, r4, r3 @通过r3计算页表项入口地址
mov r3, r7, lsr #SECTION_SHIFT @获得物理地址页偏移,存入r3
ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @获取 io_mmuflags
orr r3, r7, r3, lsl #SECTION_SHIFT @ 用r3和r7相与,生成节描述符
@当前r3存放节描述符
@当前r0存放表项入口地址
@CONFIG_ARM_LPAE未定义 -----------
#ifdef CONFIG_ARM_LPAE
/* mov r7, #1 << (54 - 32) @ XN
#ifdef CONFIG_CPU_ENDIAN_BE8
str r7, [r0], #4
str r3, [r0], #4
#else
str r3, [r0], #4 @建立一页映射,uart虚拟地址映射到物理地址
str r7, [r0], #4
#endif
*/
#else @---------------------------
orr r3, r3, #PMD_SECT_XN @修改节描述符
str r3, [r0], #4 @设置节描述符 ,并且指向下一页
#endif
#else /* CONFIG_DEBUG_ICEDCC || CONFIG_DEBUG_SEMIHOSTING */
/* we don't need any serial debugging mappings */
ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
#endif
/*#if !defined(CONFIG_DEBUG_ICEDCC) && !defined(CONFIG_DEBUG_SEMIHOSTING)结束*/
@---------------------------------------------------------------------------------------
/*
#if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)
/*
* If we're using the NetWinder or CATS, we also need to map
* in the 16550-type serial port for the debug messages
*/
/*
add r0, r4, #0xff000000 >> (SECTION_SHIFT - PMD_ORDER)
orr r3, r7, #0x7c000000
str r3, [r0]
#endif
@---------------------------------------------------------------------------------------
#ifdef CONFIG_ARCH_RPC
/*
* Map in screen at 0x02000000 & SCREEN2_BASE
* Similar reasons here - for debug. This is
* only for Acorn RiscPC architectures.
*/
/*
add r0, r4, #0x02000000 >> (SECTION_SHIFT - PMD_ORDER)
orr r3, r7, #0x02000000
str r3, [r0]
add r0, r4, #0xd8000000 >> (SECTION_SHIFT - PMD_ORDER)
str r3, [r0]
#endif
@---------------------------------------------------------------------------------------
*/
#endif
@______________________________________________________________________________________