嵌入式Linux中文站

S3C2410内存管理单元MMU基础实验



ARM芯片S3C2410内存管理单元MMU基础实验

(11)实验十一:MMU
在理论上概括或解释MMU,这不是我能胜任的。我仅基于为了理解本实验中操作MMU的代码而
对MMU做些说明,现在先简单地描述虚拟地址(VA)、变换后的虚拟地址(MVA)、物理地址(PA)
之间的关系:
启动MMU后,S3C2410的CPU核看到的、用到的只是虚拟地址VA,至于VA如何最终落实到物理
地址PA上,CPU是不理会的。而caches和MMU也是看不见VA的,它们利用VA变换得来的MVA
去进行后续操作——转换成PA去读/写实际内存芯片,MVA是除CPU外的其他部分看见的虚拟地
址。对于VA与MVA之间的变换关系,请打开数据手册551页,我摘取了“Figure 2-8. Address
Mapping Using CP15 Register 13”:
图4 VA与MVA的关系
如果VA<32M,需要使用进程标识号PID(通过读CP15的C13获得)来转换为MVA。VA与MVA的转
换方法如下(这是硬件自动完成的):
if(VA < 32M) then
MVA = VA | (PID << 25) //VA < 32M
else
MVA = VA //VA >= 32M
利用PID生成MVA的目的是为了减少切换进程时的代价:如果两个进程占用的虚拟地址空间(VA)
有重叠,不进行上述处理的话,当进行进程切换时必须进行虚拟地址到物理地址的重新影射,这
需要重建页表、使无效caches和TLBS等等,代价非常大。但是如果像上述那样处理的话,进程切
换就省事多了:假设两个进程1、2运行时的VA都是0-32M,则它们的MVA分别是(0x02000000-
0x03ffffff)、(0x04000000-0x05ffffff)——前面说过MMU、Caches使用MVA而不使用VA,这样就不必
进行重建页表等工作了。
现在来讲讲MVA到PA的变换过程:请打开数据手册557页,“Figure 3-1. Translating Page
Tables”(见下述图5)非常精练地概括了对于不同类型的页表,MVA是如何转换为PA的。图中的页
表“Translation table”起始地址为“TTB base”,在建立页表后,写入CP15的寄存器C2。
使用MVA[31:20]检索页表“Translation table”得到一个页表项(entry,4字节),根据此entry的低2
位,可分为以下4种:
1、0b00:无效
2、0b01:粗表(Coarse page)

entry[31:10]为粗表基址(Coarse page table base address),据此可以确定一块1K大小的内存——称为
粗页表(Coarse page table,见图5)。
粗页表含256个页表项,每个页表项对应一块4K大小的内存,每个页表项又可以分为大页描述
符、小页描述符。MVA[19:12]用来确定页表项。一个大页(64K)对应16个大页描述符,这16个大
页描述符相邻且完全相同,entry[31:16]为大页基址(Large page base)。MVA[15:0]是大页内的偏移地
址。一个小页(4K)对应1个小页描述符,entry[31:12]为小页基址(Small page base)。MVA[11:0]是小
页内的偏移地址。
3、0b10:段(Section)
段的操作最为简单,entry[31:20]为段基址(Section base),据此可以确定一块1M大小的内存
(Section,见图5),而MVA[19:0]则是块内偏移地址
4、0b11:细表(Fine page)
entry[31:12]为细表基址(Fine page table base address),据此可以确定一块4K大小的内存——称为细
页表(Fine page table,见图5)。
细页表含1024个页表项,每个页表项对应一块1K大小的内存,每个页表项又可以分为大页描述
符、小页描述符、极小页描述符。MVA[19:10]用来确定页表项。一个大页(64K)对应64个大页描
述符,这64个大页描述符相邻且完全相同,entry[31:16]为大页基址(Large page base)。MVA[15:0]是
大页内的偏移地址。一个小页(4K)对应4个小页描述符,entry[31:12]为小页基址(Small page base)。
MVA[11:0]是小页内的偏移地址。极小页(1K)对应1个极小页描述符,entry[31:10]为极小页基址
(Tiny page base)。MVA[9:0]是极小页内的偏移地址。
图5 Translating Page Tables
访问权限的检查是MMU主要功能之一,它由描述符的AP和domain、CP15寄存器C1的R/S/A位、
CP15寄存器C3(域访问控制)等联合作用。本实验不使用权限检查(令C3为全1)。
下面简单介绍一下使用Cache和Write buffer:
1、“清空”(clean)的意思是把Cache或Write buffer中已经脏的(修改过,但未写入主存)数据写入主

2、“使无效”(invalidate):使之不能再使用,并不将脏的数据写入主存
3、对于I/O影射的地址空间,不使用Cache和Write buffer
4、在使用MMU前,使无效Cache和drain write buffer
与cache类似,在使用MMU前,使无效TLB。
上面有些部分讲得很简略,除了作者水平不足之外,还在于本书的侧重点——实验。理论部分就
麻烦各位自己想办法了。不过这些内容也足以了解本实验的代码了。本实验与实验9完成同样的
功能:使用按键K1-K4作为4个外部中断——EINT1-3、EINT7,当Kn按下时,通过串口输
出“EINTn,Kn pressed!”,主程序让4个LED轮流从0到15计数。代码在目录MMU下。下面摘取与
MMU相关的代码详细说明。
先看看head.s代码(将一些注释去掉了):
1 b Reset
2 HandleUndef:
3 b HandleUndef
4 HandleSWI:
5 b HandleSWI
6 HandlePrefetchAbort:

7 b HandlePrefetchAbort
8 HandleDataAbort:
9 b HandleDataAbort
10 HandleNotUsed:
11 b HandleNotUsed
12 ldr pc, HandleIRQAddr
13 HandleFIQ:
14 b HandleFIQ
15 HandleIRQAddr:
16 .long HandleIRQ
17 Reset: @函数disable_watch_dog, memsetup, init_nand,
@nand_read_ll在init.c中定义
18 ldr sp, =4096 @设置堆栈
19 bl disable_watch_dog @关WATCH DOG
20 bl memsetup_2 @初始化SDRAM
21 bl init_nand @初始化NAND Flash
22 bl copy_vectors_from_nand_to_sdram @在init.c中
23 bl copy_process_from_nand_to_sdram @在init.c中
24 ldr sp, =0x30100000 @重新设置堆栈
@(因为下面就要跳到SDRAM中执行了)
25 ldr pc, =run_on_sdram @跳到SDRAM中
26 run_on_sdram:
27 bl mmu_tlb_init @调用C函数mmu_tlb_init(mmu.c中),建立页表
28 bl mmu_init @调用C函数mmu_init(mmu.c中),使能MMU
29 msr cpsr_c, #0xd2 @进入中断模式
30 ldr sp, =0x33000000 @设置中断模式堆栈
31 msr cpsr_c, #0xdf @进入系统模式
32 ldr sp, =0x30100000 @设置系统模式堆栈
33 bl init_irq @调用中断初始化函数,在init.c中
34 msr cpsr_c, #0x5f @设置I-bit=0,开IRQ中断
35 ldr lr, =halt_loop @设置返回地址
36 ldr pc, =main @b指令和bl指令只能前后跳转32M的范围,
@所以这里使用向pc赋值的方法进行跳转
37 halt_loop:
38 b halt_loop

39 HandleIRQ:
40 sub lr, lr, #4 @计算返回地址
41 stmdb sp!, { r0-r12,lr } @保存使用到的寄存器
42 ldr lr, =int_return @设置返回地址
43 ldr pc,=EINT_Handle @调用中断处理函数,在interrupt.c中
44 int_return:
45 ldmia sp!, { r0-r12,pc }^ @中断返回,
@^表示将spsr的值复制到cpsr
请注意第12、15行,我们将IRQ中断向量由以前的“b HandleIRQ”换成了:
12 ldr pc, HandleIRQAddr
15 HandleIRQAddr:
16 .long HandleIRQ
这是因为b跳转指令只能前后跳转32M的范围,而本实验中中断向量将重新放在VA=0xffff0000开始
处(而不是通常的0x00000000),到HandleIRQAddr的距离远远超过了32M。将中断向量重新定位在
0xffff0000处,是因为MMU使能后,中断发生时:
1、如果中断向量放在0x00000000处,则对于不同的进程(PID),中断向量的MVA将不同
2、如果中断向量放在0xffff0000处,则对于不同的进程(PID),中断向量的MVA也相同
显然,如果使用1,则带来的麻烦非常大——对于每个进程,都得设置自己的中断向量。所以
MMU使能后,处理中断的方法应该是2。
第22行copy_vectors_from_nand_to_sdram函数将中断向量复制到内存物理地址0x33ff0000处,在
mmu_tlb_init函数中会把0x33ff0000影射为虚拟地址0xffff0000。
第23行copy_process_from_nand_to_sdram函数将存在nand flash开头的4K代码全部复制到0x30004000
处(本实验的连接地址为0x30004000)。请注意SDRAM起始地址为0x30000000,前面的16K空间用来
存放一级页表(在mmu_tlb_init中设置)。
第27行mmu_tlb_init函数设置页表。本实验以段的方式使用内存,所以仅使用一级页表,且页表
中所有页表项均为段描述符。
mmu_tlb_init代码(在mmu.c中)如下:
1 void mmu_tlb_init()
2 {
3 unsigned long entry_index;
4 /*SDRAM*/
5 for(entry_index = 0x30000000; entry_index < 0x34000000;
entry_index+=0x100000){
6 /*section table's entry:AP=0b11,domain=0,Cached,write-through mode(WT)*/
7 *(mmu_tlb_base+(entry_index>>20)) =
entry_index |(0x03<<10)|(0<<5)|(1<<4)|(1<<3)|0x02;
8 }
9 /*SFR*/

10 for(entry_index = 0x48000000; entry_index < 0x60000000;
entry_index += 0x100000){
11 /*section table's entry:AP=0b11,domain=0,NCNB*/
12 *(mmu_tlb_base+(entry_index>>20)) =
entry_index |(0x03<<10)|(0<<5)|(1<<4)| 0x02;
13 }
14 /*exception vector*/
15 /*section table's entry:AP=0b11,domain=0,Cached,write-through mode(WT)*/
16 *(mmu_tlb_base+(0xffff0000>>20)) =
(VECTORS_PHY_BASE) |(0x03<<10)|(0<<5)|(1<<4)|(1<<3)|0x02;
17 }
第4-8行令64M SDRAM的虚拟地址和物理地址相等——从0x30000000到0x33ffffff,这样可以使得在
head.s中第28行调用mmu_init使能MMU前后的地址一致。
第9-13行设置特殊功能寄存器的虚拟地址,也让它们的虚拟地址和物理地址相等——从
0x48000000到0x5fffffff。并且不使用cache和write buffer。
第14-17行设置中断向量的虚拟地址,虚拟地址0xfff00000对应物理地址0x33f00000。
回到head.s中第28行,调用mmu.c中的mmu_init函数使能MMU,此函数代码为:
1 void mmu_init()
2 {
3 unsigned long ttb = MMU_TABLE_BASE;
4 __asm__(
5 "mov r0, #0 "
6 /* invalidate I,D caches on v4 */
7 "mcr p15, 0, r0, c7, c7, 0 "
8 /* drain write buffer on v4 */
9 "mcr p15, 0, r0, c7, c10, 4 "
10 /* invalidate I,D TLBs on v4 */
11 "mcr p15, 0, r0, c8, c7, 0 "
12 /* Load page table pointer */
13 "mov r4, %0 "
14 "mcr p15, 0, r4, c2, c0, 0 "
15 /* Write domain id (cp15_r3) */
16 "mvn r0, #0 " /*0b11=Manager,不进行权限检查*/
17 "mcr p15, 0, r0, c3, c0, 0 "
18 /* Set control register v4 */

19 mrc p15, 0, r0, c1, c0, 0 "
20 /* Clear out 'unwanted' bits */
21 "ldr r1, =0x1384 "
22 "bic r0, r0, r1 "
23 /* Turn on what we want */
24 /*Base location of exceptions = 0xffff0000*/
25 "orr r0, r0, #0x2000 "
26 /* Fault checking enabled */
27 "orr r0, r0, #0x0002 "
28 #ifdef CONFIG_CPU_D_CACHE_ON /*is not set*/
29 "orr r0, r0, #0x0004 "
30 #endif
31 #ifdef CONFIG_CPU_I_CACHE_ON /*is not set*/
32 "orr r0, r0, #0x1000 "
33 #endif
34 /* MMU enabled */
35 "orr r0, r0, #0x0001 "
36 /* write control register *//*write control register P545*/
37 "mcr p15, 0, r0, c1, c0, 0 "
38 : /* no outputs */
39 : "r" (ttb) );
40 }
此函数使用嵌入汇编的方式,第29行的"r" (ttb)表示变量ttb的值赋给一个寄存起作为输入参数,这
个寄存器由编译器自动分配;第13行的“%0”表示这个寄存器。MMU控制寄存器C1中各位的含
义(第18-37行),可以参考539页“Table 2-10. Control Register 1-bit Functions”。如果想详细了解本
函数用到的操作协处理器的指令,可以参考数据手册529页“Appendix 2 PROGRAMMER'S
MODEL”。
本实验代码在CLOCK目录下,运行make命令后将可执行文件mmu下载、运行。然后将mmu.h文
件中如下两行的注释去掉,重新make后下载运行mmu,可以发现LED闪烁的速度变快了很多——
这是因为使用了cache(见上面代码28-33行):
#define CONFIG_CPU_D_CACHE_ON 1
#define CONFIG_CPU_I_CACHE_ON 1

本文永久更新链接:http://embeddedlinux.org.cn/emb-linux/entry-level/200910/30-696.html



分享:

相关推荐

评论