在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的垃圾信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。
7 本节新增汇编指令汇总(接上篇)
指令 | 功能 | 说明 |
---|---|---|
bic | Bit clear | 清除指定位 |
.align | align | 指定对齐字节数 |
8 __cpu_flush剖析
在《内核启动流程分析(一)》中我们已经分析过,对于arm9来说,__cpu_flush其实被定义为了 b __arm920_setup,所以,接下来我们分析 __arm920_stup函数。
该函数主要做的工作是在开启MMU之前清除ICache、DCache、Writebuffer和TLB等。这些工作一般是通过cp15协处理器来实现的,并且是平台相关的(因为是直接操作寄存器啊)。
代码分析如下:
/* /arch/arm/mm/proc-asm920.S */
__INIT @ 声明该函数编译时放入.init.text段中
.type __arm920_setup, #function
__arm920_setup:
mov r0, #0
mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4 关闭I&D cashes
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer on v4 清除drain write buffer
#ifdef CONFIG_MMU
mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4 清除(invalidate)Instruction TLB 和Data TLB
#endif
adr r5, arm920_crval @ 取arm920_crval的运行地址到r5中
ldmia r5, {r5, r6} @ r5 = clear=0x00003f3f, r6 = mmuset=0x00003135(对于配置了MMU的情况)
mrc p15, 0, r0, c1, c0 @ get control register v4,取c1内容到r0中
bic r0, r0, r5 @ 先根据掩码0x3f3f清除指定位
orr r0, r0, r6 @ 再将0x3135配置到r0(这里猜测后续会继续配置r0,然后将r0写回c1)
mov pc, lr @ 函数返回,而lr中存放的正是__enable_mmu函数的地址
.size __arm920_setup, . - __arm920_setup
/*
* R
* .RVI ZFRS BLDP WCAM
* ..11 0001 ..11 0101
*
*/
.type arm920_crval, #object
arm920_crval:
crval clear=0x00003f3f, mmuset=0x00003135, ucset=0x00001130
9 __enable_mmu剖析
__arm920_stup 函数只是做了 配置 工作,但是最终 使能 工作还未完成。这就需要 __enable_mmu 函数来完成了。
在开始分析代码之前,先来梳理几个很重要的概念,不然只看代码就会云里雾里,知其然但不知其所以然。
重点说一下空间这个概念,因为把这个搞懂了,所有概念就都迎刃而解。空间就是你的认知范围,超出这个范围你就触控不到了。空间就是一个围猎场(物理地址空间),就这么大,中间的猎物(存储芯片)可以很少,也可以很多,但绝对不会超出这个场子,一开始,你(CPU)就在这个真实的猎场内打猎,实打实地干。后来有人研究了一套虚拟现实技术,告诉你说:“老大,以后不用去真实猎场打了,我们做了一个虚拟猎场(虚拟地址空间),你想在哪里打就在哪里打,你打死了哪一只,我们有对应地人(MMU)把对应的(空间映射)真实的猎物打死给你抓过来。”你一想,嘻嘻,这很方便啊,于是就这么拍板儿定下了~~
概念 | 说明 |
---|---|
地址空间 | 地址空间其实是一个逻辑上的概念,比如32bit对应的地址空间是4G,但是在硬件连接上可能只有256MB,那就只有256MB地址空间是有效的(在没有引入虚拟地址的情况下)。地址空间可以理解为CPU能够看到的地址范围。所以,各类硬件(SFR,存储芯片,I/O等硬件)为了让CPU看到自己,必须将自己映射到地址空间中去(依靠地址总线在硬件上的连接)。 |
链接地址 | 链接过程中由链接脚本指定的内存地址(这个阶段的地址必须是虚拟的)。链接地址必须在地址空间内,链接地址所在地址空间是虚拟地址空间。 |
绝对地址/相对地址 | 绝对地址就是内存的绝对地址(可以认为是相对于0地址的相对地址),相对地址一般是由相对于PC指向地址的偏移量计算得出。 |
位置无关码(PIC) | 位置无关码就是这段代码无论被加载到哪个内存地址都可以正确运行。这就要求变量的地址全部都使用相对地址。 |
虚拟地址 | 虚拟地址是相对于物理地址来说的,一般虚拟地址就是链接地址,使用虚拟地址的一个好处就是可以扩充有效的地址空间。虚拟地址也必须在地址空间内,虚拟地址所在地址空间也是虚拟地址空间。 |
物理地址/实地址 | 物理地址就是实实在在的内存地址,和存储芯片以及连接方式相关。物理地址也必须在地址空间内,物理地址所在地址空间是物理地址空间。 |
运行地址 | 运行地址就是程序加载到内存中开始运行之后,PC(有些处理器架构还有段寄存器)指向的地址。在开启MMU之前,PC指向的是物理地址,在开启MMU之后PC指向的是虚拟地址。 |
在物理地址空间运行 | 运行地址是物理地址的,我们就说程序在物理地址空间运行。 |
在虚拟地址空间运行 | 运行地址是虚拟地址的,我们就说程序在虚拟地址空间运行。(程序说:我们厉害吧,还可以穿梭时空~~) |
有了概念的铺垫,分析代码就好比摧枯拉朽。
/*
* Setup common bits before finally enabling the MMU. Essentially
* this is just loading the page table pointer and domain access
* registers.
*
* r0 = c1 parameters (用来配置控制寄存器的参数)
* r4 = pgtbl (page table 的物理基地址)
* r8 = machine info (struct machine_desc的基地址)
* r9 = cpu id (通过cp15获得的cpu id)
* r10 = procinfo (struct proc_info_list的基地址)
*/
.type __enable_mmu, %function
__enable_mmu:
@ 下述对r0的操作是根据内核编译选项继续配置r0(猜测r0最终一定会被写入c1)
#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,配置domain(domain是arm中用来控制访问权限的)
mcr p15, 0, r4, c2, c0, 0 @ load page table pointer,
@ 将页物理基址告诉协处理器(这一步在创建页表时就可以预料到了)
b __turn_mmu_on @ 跳转到__turn_mmu_on继续运行
/*
* Enable the MMU. This completely changes the structure of the visible
* memory space(这句话就是我们要穿越了).
* You will not be able to trace execution through this(穿越时是无法跟踪的).
* If you have an enquiry about this, *please* check the linux-arm-kernel
* mailing list archives BEFORE sending another post to the list.
*
* r0 = cp#15 control register
* r13 = *virtual* address to jump to upon completion(MMU 打开后意味着程序就运行在虚拟地址下了)
*
*
* other registers depend on the function called upon completion
*/
.align 5 @ 为什么对齐?以便把打开mmu的操作放到一个单独的cache line上.
@ 因为ICache是可以打开也可以关闭的,
@ 这样做可以保证在ICache打开的情况下,打开mmu的操作也能快速生效。
.type __turn_mmu_on, %function
__turn_mmu_on:
mov r0, r0 @ 相当于一个nop指令,这里增加nop以便保证下一条指令在开启MMU时,
@ “mcr p15, 0, r4, c2, c0, 0”指令已经执行完成,当然多加几条nop也没有问题。
mcr p15, 0, r0, c1, c0, 0 @ write control reg,这一步终于将r0写回c1,打开MMU。
mrc p15, 0, r3, c0, c0, 0 @ read id reg
mov r3, r3 @ 相当于一个nop指令
mov r3, r3 @ 相当于一个nop指令
@ 这里为啥要加两个nop呢?
@ 因为arm的流水线的特性是:打开mmu操作之后,要延迟3个cycle才会生效,
@ 此时pc绝对不能轻举妄动。
mov pc, r13 @ 长跳转,因为r13中存的是虚拟地址,因此程序马上要进入虚拟
@ 地址空间运行(注意啦,注意啦,马上就要穿越时空~)
@ r13中存储的是__switch_data地址处的值,即__mmap_switched,
@ 所以接下来会运行__mmap_switched函数
Tips:对于arm流水线不了解的可以查找相关资料,也可以先跳过这个知识点,学会抓大放小。
<完>