Linux内核源码分析(三)--启动汇编下篇

废话少说,接着上一篇继续分析我们的__create_page_tables函数。回顾一下前面的分析可以知道目前r8中存放的是machine_desc结构的地址,r9中存放的是cpu主标识,r10中存放的是proc_info_list结构的地址。


__create_page_tables:
 pgtbl r4
内核加载地址之前的16KB内存用来存放一级页表,由pgtbl r4可知我们现在把一级页表的地址保存在r4中。
 mov r0, r4
 mov r3, #0
 add r6, r0, #0x4000
1: str r3, [r0], #4
 str r3, [r0], #4
 str r3, [r0], #4
 str r3, [r0], #4
 teq r0, r6
 bne 1b
将r4到r4+16K之间的内存清零,即清空一级页表。
 ldr r7, [r10, #PROCINFO_MM_MMUFLAGS]
将proc_info_list结构中__cpu_mm_mmu_flags的值保存到r7中,对于我们来说即PMD_TYPE_SECT | PMD_SECT_BUFFERABLE | PMD_SECT_CACHEABLE | PMD_BIT4 | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ,由 PMD_TYPE_SECT可知开始会进行段映射。
 mov r6, pc, lsr #20
 orr r3, r7, r6, lsl #20
 str r3, [r4, r6, lsl #2]
cpu使用的虚拟地址,前12位是在一级页表内的偏移,再后面的偏移会根据具体的映射方式划分。这里将内核起始的1MB空间映射到相等的虚拟空间,注意一个页表项占了4个地址,所以一级偏移量要乘以4才能找到对应一级页表项的地址。
 add r0, r4,  #(KERNEL_START & 0xff000000) >> 18
 str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!
 ldr r6, =(KERNEL_END - 1)
 add r0, r0, #4
 add r6, r4, r6, lsr #18
1: cmp r0, r6
 add r3, r3, #1 << 20
 strls r3, [r0], #4
 bls 1b
除了一对一映射的1MB空间,还需要进行内核链接时的虚拟地址到物理地址的映射,上面的代码就是做这个的,开始的两条指令不能合二为一的原因是arm的立即数是8位的。可以看到,r0中开始存放的是内核虚拟起始地址在一级页表中对应表项的地址,r6中最终存放的是内核虚拟结束地址在一级页表中对应表项的结束地址,r6只右移了18位没有把低2位清零说明找的不是页表项的起始地址,记住一个页表 项32位占了4个地址。
 add r0, r4, #PAGE_OFFSET >> 18
 orr r6, r7, #(PHYS_OFFSET & 0xff000000)
 .if (PHYS_OFFSET & 0x00f00000)
 orr r6, r6, #(PHYS_OFFSET & 0x00f00000)
 .endif
 str r6, [r0]
 mov pc, lr
PAGE_OFFSET是内核高端内存的起始虚拟地址,PHYS_OFFSET是物理地址的起始地址,看样子只是映射了1MB的空间,因为这段空间里有bootloader传过来的参数。整个映射关系如下所示。注意图中标注的地址位段地址,页表和内核起始地址相差16KB在同一个段内。



函数返回后会寻找proc_info_list结构中的__cpu_flush并执行此处的指令,我们的proc_info_list的__cpu_flush中存的是一条跳转指令b __arm920_setup。而跳转之前我们需要知道lr寄存器中存的是__enable_mmu的物理地址,r13中存的是__switch_data的虚拟地址。下面看arch/arm/mm/proc-arm920.S中的__arm920_setup函数。
__arm920_setup:
        mov     r0, #0
        mcr     p15, 0, r0, c7, c7              @ invalidate I,D caches on v4
        mcr     p15, 0, r0, c7, c10, 4          @ drain write buffer on v4
#ifdef CONFIG_MMU
        mcr     p15, 0, r0, c8, c7              @ invalidate I,D TLBs on v4
#endif
        adr     r5, arm920_crval
        ldmia   r5, {r5, r6}
        mrc     p15, 0, r0, c1, c0              @ get control register v4
        bic     r0, r0, r5
        orr     r0, r0, r6
        mov     pc, lr
cp15的c7寄存器控制着高速缓存和写缓存,c8控制TLB。这里先是指令和数据cache内容无效,清除写缓存,使快表无效。接着从arm920_crval处取出两个字分别放入r5和r6中。arm920_crval的定义如下。
        .type   arm920_crval, #object
arm920_crval:
        crval   clear=0x00003f3f, mmuset=0x00003135, ucset=0x00001130
crval是一个宏,定义在arch/arm/mm/proc-macros.S文件中。
        .macro  crval, clear, mmuset, ucset
#ifdef CONFIG_MMU
        .word   \clear
        .word   \mmuset
#else
        .word   \clear
        .word   \ucset
#endif
        .endm
如此r5中放的是要清除cp15中c1的位,r6中是设置位,最终要设到cp15的c1寄存器的值计算好后保存在r0寄存器中,r0中使能mmu的位被设置了,所以不能在这里设置c1,要等到后面在__enable_mmu函数中进行设置。

cp15中c1寄存器的编码格式如下所示。

C1中的控制位

含义

M(bit[0])

0 :禁止 MMU 或者 PU 

1 :使能 MMU 或者 PU

如果系统中没有MMU及PU,读取时该位返回0,写入时忽略该位

A(bit[1])

0 :禁止地址对齐检查

1 :使能地址对齐检查

C(bit[2])

当数据cache和指令cache分开时,本控制位禁止/使能数据cache。当数据cache和指令cache统一时,该控制位禁止/使能整个cache。

0 :禁止数据 / 整个 cache 

1 :使能数据 / 整个 cache

如果系统中不含cache,读取时该位返回0.写入时忽略

当系统中不能禁止cache 时,读取时返回1.写入时忽略

W(bit[3])

0 :禁止写缓冲

1 :使能写缓冲

如果系统中不含写缓冲时,读取时该位返回0.写入时忽略

当系统中不能禁止写缓冲时,读取时返回1.写入时忽略

P(bit[4])

对于向前兼容26位地址的ARM处理器,本控制位控制PROG32控制信号

0 :异常中断处理程序进入 32 位地址模式

1 :异常中断处理程序进入26 位地址模式

如果本系统中不支持向前兼容26位地址,读取该位时返回1,写入时忽略

D(bit[5])

对于向前兼容26位地址的ARM处理器,本控制位控制DATA32控制信号

0 :禁止 26 位地址异常检查

1 :使能 26 位地址异常检查

如果本系统中不支持向前兼容26位地址,读取该位时返回1,写入时忽略

L(bit[6])

对于ARMv3及以前的版本,本控制位可以控制处理器的中止模型

0 :选择早期中止模型

1 :选择后期中止模型

B(bit[7])

对于存储系统同时支持big-endian和little-endian的ARM系统,本控制位配置系统的存储模式

0 : little endian  

1 : big endian

对于只支持little-endian的系统,读取时该位返回0,写入时忽略

对于只支持big-endian的系统,读取时该位返回1,写入时忽略

S(bit[8])

在基于 MMU 的存储系统中,本位用作系统保护

R(bit[9])

在基于 MMU 的存储系统中,本位用作 ROM 保护

F(bit[10])

由生产商定义

Z(bit[11])

对于支持跳转预测的ARM系统,本控制位禁止/使能跳转预测功能

0 :禁止跳转预测功能 

1 :使能跳转预测功能

对于不支持跳转预测的ARM系统,读取该位时返回0,写入时忽略

I(bit[12])

当数据cache和指令cache是分开的,本控制位禁止/使能指令cache

0 :禁止指令 cache  

1 :使能指令 cache

如果系统中使用统一的指令cache和数据cache或者系统中不含cache,读取该位时返回0,写入时忽略。当系统中的指令cache不能禁止时,读取时该位返回1,写入时忽略

V(bit[13])

对于支持高端异常向量表的系统,本控制位控制向量表的位置

0 :选择低端异常中断向量 0x0~0x1c 

1 :选择高端异常中断向量0xffff0000~ 0xffff001c

对于不支持高端异常向量表的系统,读取时该位返回0,写入时忽略

PR(bit[14])

如果系统中的cache的淘汰算法可以选择的话,本控制位选择淘汰算法

0 :常规的 cache 淘汰算法,如随机淘汰 

1 :预测性淘汰算法,如round-robin 淘汰算法

如果系统中cache的淘汰算法不可选择,写入该位时忽略。读取该位时,根据其淘汰算法是否可以比较简单地预测最坏情况返回0或者1

L4(bit[15])

对于ARM版本5及以上的版本,本控制位可以提供兼容以前的ARM版本的功能

0 :保持 ARMv5 以上版本的正常功能

1 :将 ARMv5 以上版本与以前版本处理器 兼容,不根据跳转地址的 bit[0] 进行 ARM 指令和 Thumb 状态切换: bit[0] 等于 0 表示 ARM 指令,等于 1 表示 Thumb 指令

Bits[31:16])

这些位保留将来使用,应为UNP/SBZP



接下来可以看__enable_mmu函数了。
 .type __enable_mmu, %function
__enable_mmu:
#ifdef CONFIG_ALIGNMENT_TRAP
 orr r0, r0, #CR_A
#else
 bic r0, r0, #CR_A
#endif
#ifdef CONFIG_CPU_DCACHE_DISABLE
 bic r0, r0, #CR_C
#endif
#ifdef CONFIG_CPU_BPREDICT_DISABLE
 bic r0, r0, #CR_Z
#endif
#ifdef CONFIG_CPU_ICACHE_DISABLE
 bic r0, r0, #CR_I
#endif
 mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \
        domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
        domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \
        domain_val(DOMAIN_IO, DOMAIN_CLIENT))
 mcr p15, 0, r5, c3, c0, 0  @ load domain access register
 mcr p15, 0, r4, c2, c0, 0  @ load page table pointer
 b __turn_mmu_on

CR_A、CR_C、CR_Z、CR_I定义在include/asm-arm/system.h中
#define CR_A    (1 << 1)        /* Alignment abort enable               */
#define CR_C    (1 << 2)        /* Dcache enable                        */
#define CR_Z    (1 << 11)       /* Implementation defined               */
#define CR_I    (1 << 12)       /* Icache enable                        */
在__turn_mmu_on中r0的值会被设置到cp15的c1寄存器中。
另外cp15的c2寄存器存放一级页目录地址,c3寄存器定义了arm处理器16域的访问权限。
c3中每两位表示一个域,描述分别如下。
00:当前级别下,该内存区域不允许被访问,任何的访问都会引起一个domain fault,这是AP位无效
01:当前级别下,该内存区域的访问必须配合该内存区域的段描述符中AP位进行权限检查
10:保留状态
11:当前级别下,对该内存区域的访问都不进行权限检查,此时AP位无效
再看include/asm-arm/domain.h文件中domain相关的宏定义。
#define DOMAIN_KERNEL   2
#define DOMAIN_TABLE    2
#define DOMAIN_USER     1
#define DOMAIN_IO       0
可见开始除了IO地址,其他都不需要权限检查。
#define DOMAIN_NOACCESS 0
#define DOMAIN_CLIENT   1
#define DOMAIN_MANAGER  3
#define domain_val(dom,type)    ((type) << (2*(dom)))

最后跳到__turn_mmu_on函数真正的打开mmu,接着跳到r13所指向的地址处执行。
__turn_mmu_on:
 mov r0, r0
 mcr p15, 0, r0, c1, c0, 0  @ write control reg
 mrc p15, 0, r3, c0, c0, 0  @ read id reg
 mov r3, r3
 mov r3, r3
 mov pc, r13
r13中存放的是__switch_data的链接地址,mmu已经打开程序无须再做地址转换。
__switch_data:
 .long __mmap_switched
 .long __data_loc   @ r4 存放数据的位置
 .long __data_start   @ r5 数据段起始的位置
 .long __bss_start   @ r6 bss段起始地址
 .long _end    @ r7 bss段结束地址
以上定义在链接脚本中。
 .long processor_id   @ r4
 .long __machine_arch_type  @ r5
 .long cr_alignment   @ r6
 .long init_thread_union + THREAD_START_SP @ sp
__switch_data可以看成一个数据结构,首成员是一个函数指针(指向__mmap_switched函数)。
__mmap_switched:
 adr r3, __switch_data + 4
 ldmia r3!, {r4, r5, r6, r7}
 cmp r4, r5    @ 比较数据存放位置和数据起始位置
1: cmpne r5, r6 @不相等的话,比较数据段和bss段的起始地址是否相同
 ldrne fp, [r4], #4 @如果数据存放位置和数据起始位置不等且数据段起始位置
 strne fp, [r5], #4 @和bss段起始位置也不等则搬运数据
 bne 1b
 mov fp, #0         @ 清
1: cmp r6, r7       @ 空
 strcc fp, [r6],#4  @ bss
 bcc 1b               @ 段
 ldmia r3, {r4, r5, r6, sp} @r3现在指向__switch_data结构的processor_id成员
 str r9, [r4]   @ r9中是processor_id,先存放到__switch_data结构的processor_id成员指向的地址
 str r1, [r5]   @ r1是bootloader传过来的machine type,现存放到__switch_data结构的__machine_arch_type成员指向的地址
                   @ processor_id和__machine_arch_type是arch/arm/kernel/setup.c中定义的全局变量
 bic r4, r0, #CR_A   @ r0中存放的cp15中c1寄存器的值,现在打开地址对齐并保存到r4中
 stmia r6, {r0, r4}   @ cr_aligment和cr_no_alignment是定义在arch/arm/kernel/entry-armv.S中地址连续的两个全局变量
 b start_kernel
此后跳入start_kernel开启了真正的万里长征。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值