(亚嵌)ARM920T的MMU与Cache之操作MMU和Cache的内核启动代码
bootloader加载linux内核到内存并解压之后,Linux内核首先在汇编代码中读取CPU的基本信息,对CPU做一些基本设置,创建最简单的临时页表,然后开启MMU和Cache,启用虚拟内存管理(此后CPU核发出的地址都是虚拟地址),然后跳到C代码中完成其它初始化工作,比如创建完整的页表、初始化各种内核子系统、初始化硬件设备等。本节以Linux 2.4内核的启动代码为例,了解一下操作MMU和Cache的具体指令是怎么写的,通过实例来加深对前面内容的理解。本节的内容改编自[ARM Linux演义]。
假设目标板的RAM物理地址是从0x0800 0000开始的(也就是说,RAM芯片连接到CPU芯片上从0x0800 0000开始的bank)。经过内核的若干初始化代码之后,寄存器的内容如下:
表 3. 寄存器的初始值
接下来的步骤是:
1 创建简单的临时页表和临时映射
2 配置与MMU和Cache相关的CP15寄存器
3 启用MMU和Cache
临时页表存放在物理内存地址0x0800 4000开始的16K(回想一下,第一级页表是16K,有4096个页描述符)。后面将会把页描述符填写成Section格式,也就是直接映射到1M的大页面,这些都是内核初始化阶段临时用的,为了是写尽可能少的汇编代码,尽快启用MMU并跳到C代码中做剩下的初始化工作,在完整的两级页表建立之后临时页表就没有用了。首先将16K的临时页表清零:
mov r0, r4
mov r3, #0
add r2, r0, #0x4000 @ 16k of page table
1: str r3, [r0], #4 @ Clear page table
str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
teq r0, r2
bne 1b
下面我们将使用Section格式的页描述符来填充表项,由于是内核初始化阶段,还没有用户进程,我们只映射4M的地址空间,覆盖内核本身的代码和数据就可以了。思考一下,为什么首先要把这16K临时页表清零,即使没用到的表项也要清零?由于Linux内核在编译时确定的代码加载地址是0xc000 8000(虚拟地址),而bootloader将内核代码加载到物理地址0x0800 8000,我们需要把物理地址从0x0800 0000开始的4M映射到虚拟地址从0xc000 0000开始的4M。
但是这里有一个问题:设置好页表之后,最终有一条指令是启用MMU的,假设该指令的PA是0x0800 810c,根据我们要做的映射关系,它的VA应该是0xc000 810c,没有启用MMU之前CPU核发出的都是物理地址,从0x0800 810c地址取这条指令来执行,然而该指令执行之后,CPU核发出的地址都要被MMU拦截,CPU核就必须用虚拟地址来取指令了,因此下一条指令应该从0xc000 8110处取得,然而这时pc寄存器(也就是r15寄存器)的值并没有变,CPU核取下一条指令仍然要从0x0800 8110处取得,此时0x0800 8110已经成了非法地址了。如下图所示。
图 21. 启用MMU的那条指令导致的问题
为了解决这个问题,要求启用MMU的那条指令及其附近的指令虚拟地址跟物理地址相同,这样在启用MMU前后,附近指令的地址不会发生变化,从而实现平稳过渡。因此需要将物理地址从0x0800 0000开始的1M再映射到虚拟地址从0x0800 0000开始的1M,也就是做一个等价映射(identity map)[5]。现在把需要建立的映射项总结如下:
表 4. 需要建立的映射项
以下代码建立上面所说的等价映射。
回头看一下表 3 “寄存器的初始值”,r8的值是页描述符标志位,r5的值是RAM起始物理地址0x0800 0000,由于要做的是等价映射,这里的r5既是PA同时也是VA,第一条指令将r5当作PA,r3=r8+r5=0x0800 0c1e得到完整的页描述符,比对一下看看各bit的含义。
图 22. 等价映射的页描述符
add r3, r8, r5 @ mmuflags + start of RAM
add r0, r4, r5, lsr #18
str r3, [r0] @ identity mapping
该描述符所描述的Section属于第0个Domain,AP位是11,可读可写,C、B位都是1,允许Cache,并且Cache是Write Back方式的。第二条指令,将虚拟地址r5右移18位(对照图 14 “Translation Table Walk的详细过程”看一下为什么是右移18位),加到页表基地址上,得到该描述符在页表中的地址,结果保存在r0中。第三条指令,将第一条指令计算出的页描述符的值r3保存在第二条指令计算出的r0地址处,这样就填写好了页表项。
下面映射物理地址从0x8000 0000开始的4M到虚拟地址0xc000 0000,其中TEXTADDR是Linux内核在编译时确定的代码加载地址0xc000 8000,PAGE_OFFSET定义为0xc000 0000。请读者自己分析以下代码。
add r0, r4, #(TEXTADDR & 0xfff00000) >> 18 @ start of kernel 注:r0 = r4+ 0x3000 = 0800 4000 + 3000 = 0800 7000 str r3, [r0], #4 @ PAGE_OFFSET + 0MB 注:0800 7000地址的内容为0800 0c1e add r3, r3, #1 << 20 注:r3=0810 0c1e str r3, [r0], #4 @ PAGE_OFFSET + 1MB 注:0800 7004地址的内容为0810 0c1 e add r3, r3, #1 << 20 注:r3=0820 0c1e str r3, [r0], #4 @ PAGE_OFFSET + 2MB 注:0800 7008地址的内容为0820 0c1e add r3, r3, #1 << 20 注:r3=0830 0c1e str r3, [r0], #4 @ PAGE_OFFSET + 3MB 注:0800 700c地址的内容为0830 0c1e设置好了页表,接下来设置与MMU和Cache相关的CP15寄存器:
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
mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4
mcr p15, 0, r4, c2, c0 @ load page table pointer
mov r0, #0x1f @ Domains 0, 1 = client
mcr p15, 0, r0, c3, c0 @ load domain access register
mrc p15, 0, r0, c1, c0 @ get control register v4
/*
* Clear out 'unwanted' bits (then put them in if we need them)
*/
@ VI ZFRS BLDP WCAM
bic r0, r0, #0x0e00
bic r0, r0, #0x0002
bic r0, r0, #0x000c
bic r0, r0, #0x1000 @ ...0 000. .... 000.
/*
* Turn on what we want
*/
orr r0, r0, #0x0031
orr r0, r0, #0x2100 @ ..1. ...1 ..11 ...1
#ifdef CONFIG_CPU_ARM920_D_CACHE_ON
orr r0, r0, #0x0004 @ .... .... .... .1..
#endif
#ifdef CONFIG_CPU_ARM920_I_CACHE_ON
orr r0, r0, #0x1000 @ ...1 .... .... ....
#endif
这一段有很多协处理器指令,请读者对照[S3C2410用户手册]和代码中的注释查看各指令的含义。大体上来说做了以下事情:首先禁用指令和数据Cache,等待Write Buffer写回内存,然后用r4寄存器的值设置CP15的TTB寄存器,然后设置Domain权限位,我们先前填写的页描述符都属于第0个Domain,Domain寄存器中第0个Domain的权限位设置为11,表示访问不必检查AP位。接下来读出CP15的控制寄存器的值来修改,准备启用MMU,根据内核配置决定是否启用数据和指令Cache,修改之后一并写回控制寄存器,使设置生效:
mcr p15, 0, r0, c1, c0
参考资料
[S3C2410用户手册] User's Manual S3C2410A - 200MHz & 266MHz 32-Bit RISC Microprocessor. 1.0. 版权 © 2004 Samsung Electronics.
[ARM参考手册] ARM Architecture Reference Manual. 版权 © 1996-2000 ARM Limited.
[ARM Linux演义] 网上到处转载的帖子: ARM Linux演义. 作者已不可考,据说是r58452网友.
索引
C
Cache,高速缓存, 虚拟内存管理
Cache Hit, ARM920T的CP15协处理器
Cache Line, ARM920T的CP15协处理器
Cache Miss, ARM920T的CP15协处理器
Cache Thrash,Cache抖动, Cache
Direct Mapped Cache,直接映射Cache, Cache
Fully Associative Cache,全相联Cache, Cache
n-way Set Associative Cache,n路组相联Cache, Cache
M
MMU,Memory Management Unit,内存管理单元, 虚拟地址和物理地址的概念
P
Page Frame,页框,物理页面, 虚拟地址和物理地址的概念
Page Table,页表, ARM920T的CP15协处理器
Page,页, 虚拟地址和物理地址的概念
(参见 Page Frame,页框)
Paging,换页, 虚拟内存管理
Page in,换入, 虚拟内存管理
Page out,换出, 虚拟内存管理
PA,Physical Address,物理地址, 虚拟地址和物理地址的概念
(参见 VA,Virtual Address,虚拟地址)
S
Swap Device,交换设备, 虚拟内存管理
T
Tag, Cache
TLB,Translation Lookaside Buffer, ARM920T的CP15协处理器
Translation Table Walk, ARM920T的CP15协处理器
V
VA,Virtual Address,虚拟地址, 虚拟地址和物理地址的概念
(参见 PA,Physical Address,物理地址)
Virtual Memory Management,虚拟内存管理, 虚拟内存管理
W
Write Back, Cache(参见 Write Through)
Write Through, Cache(参见 Write Back)
1 对于32位的CPU,从CPU核这边看地址线是32条(图中只是示意性地画了4条地址线),可寻址空间是4GB,但是通常嵌入式处理器的CPU外部地址引脚不会有这么多条地址线,因为引脚是芯片上十分有限而宝贵的资源,而且也不太可能用到4GB这么大的物理内存。另一方面,在启用MMU的情况下VA地址空间和PA地址空间是完全独立的,PA地址空间既可以小于也可以大于VA地址空间,例如有些32位的服务器可以配置大于4GB的物理内存。
2 这里说Cache是用VA来索引数据的,只是针对一些嵌入式处理器,实际上大多数PC和服务器的CPU都有两级Cache,靠近CPU核的一级缓存是以VA来索引数据的,而靠近物理内存的二级缓存则是以PA来索引数据的。
3 如果读者看了[S3C2410用户手册]可能会注意到有MVA(Modified Virtual Address)这个概念,这属于ARM的快速上下文切换(Fast Context Switch)机制,适用于一些小型的RTOS,每个进程的地址空间不超过32M,Linux并没有利用快速上下文切换机制,因此在我们的讨论当中,VA和MVA不加区分,认为是相同的。
4 我们说Cache的大小是16K,是指Cache能缓存16K的内存数据,其实Cache还需要保存相应的Tag和其它标志位,其存储容量应该是大于16K的。另外,Cache的构造和内存不同,不必以字节为单位来存储,例如VA[31:5]这个Tag有27个bit,既不是3个字节也不是4个字节。
5 事实上,以上解释并不完全正确,这里还有一个更复杂的细节,启用MMU的指令在执行时,后面两条指令已经预取到CPU流水线里了,如果利用那两条指令跳转到0xc000 8110不就行了?但是流水线是靠不住的,跳转和异常都会清空流水线,[ARM参考手册]的Chapter A2详细解释了这种情况,按该手册的建议应该采用等价映射的方法解决这个问题。
更多推荐
所有评论(0)