“第021课 MMU和Cache”的版本间的差异

来自百问网嵌入式Linux wiki
第109行: 第109行:
  
 
下节讲协处理指令
 
下节讲协处理指令
 +
 +
 +
 +
 +
 +
= 第003节_MMU及地址映射 =
 +
对于JZ2440它有64M内存(SDRAM),假设现在有N个APP同时运行,则:
 +
 +
①它们同时保存在SDRAM里;
 +
 +
②它们的地址各不相同;
 +
 +
 +
之前我们讲过链接地址,链接地址就是程序运行时所处地址。
 +
 +
假设APP1所处的地址是Addr1,APP2所处的地址是Addr2,APPn所处的地址是Addrn,
 +
 +
则编译某个App时,需要单独指定它的的链接地址,这是一个不可能完成的任务。
 +
 +
因为,假如只有几个程序,为每个程序单独的指定地址还能够实现,但对于一个开放式的嵌入式系统,应用程序可能有成百上千个,你不可能重新编译这成百上千的应用程序,并且这些应用程序运行时保存的地址,也是不可预料的。
 +
 +
 +
为了解决上述问题,于是就引入了'''虚拟地址'''。
 +
 +
也就是说虽然这些应用程序它们保存在内存中的位置各不一样,但对于CPU,它们运行时,都在同一个虚拟地址上。
 +
 +
 +
举个例子,如视频中的两个hello应用程序,编译后查看反汇编代码,可以看到这两个程序的起始地址都是0x80A4。于是CPU运行两个APP时,都会去0x80A4读指令,然后经过MMU转换成Addr1、Addr2。这样,不同的APP可以在任意地址,经过MMU地址转换后,在内存上是不同的地址,互不干扰。
 +
[[File:chapter21_lesson3_001.jpg|700px]]
 +
 +
这里说的同时运行,并不是真正的同时运行,CPU是分时操作,APP1先工作很短一段时间,再APP2工作很短的一段时间,宏观的来看就是两个在同时工作。
 +
 +
'''因此,引入虚拟机地址的原因之一:让APP可以以同样的链接地址来编译;'''
 +
 +
 +
在电子系统里面,内存都是有限的,无论是嵌入式系统还是电脑,比如我们的JZ2440内存就只有64M,这时假如有一个APP,需要1G的内存。应用程序执行时,不是一次性将所有代码都放入内存,而是将要运行的部分依次放入,当放入的代码指令大于64M后,会先将SDRAM里暂时用不到代码指令先置换出来,再放入需要运行的代码指令。这样尽管SDRAM很小,也可以运行内存需要很大的应用程序,而这个置换管理的工作,就是由MMU完成的。
 +
[[File:chapter21_lesson3_002.jpg|700px]]
 +
'''因此,引入虚拟机地址的原因之二:让大容量APP可以在资源少的系统上运行;'''
 +
 +
 +
此外,不同的APP之间应该相互独立,避免APP1能直接访问到APP2,以防止APP1影响APP2。
 +
 +
'''因此,引入虚拟机地址的原因之三:权限管理,禁止访问其它空间;'''
 +
 +
 +
CPU发出虚拟地址(VA)到达MMU,MMU转换成物理地址(PA)发给硬件,那么MMU怎么根据什么将一个虚拟地址转换成物理地址?
 +
 +
a.表格
 +
 +
最简单的方法就是弄一个表格,将VA和PA对应起来,根据VA就能找到PA。这种方法优点是简单,缺点是有点浪费空间,需要同时记录VA和PA的地址。
 +
 +
 +
b.改进
 +
 +
在表格里面,我们只保存PA,PA1对应的VA是0~1M-1,PA2对应的VA是1M~2M-1,以此类推。这样改进后,只需要原来表格容量的一半即可。最后还需要把基地址告诉MMU,启动MMU。
 +
 +
[[File:chapter21_lesson3_003.jpg|700px]]
 +
 +
 +
怎么使用MMU?
 +
 +
1.在内存中创建这些表格(页表);
 +
 +
2.把页表基地址告诉MMU;
 +
 +
3.设置CP15,启动MMU;
 +
 +
 +
前面图中的是一级页表,对于一级页表,条目/描述符对应的大小是1M,条目/描述符的格式可以参考S3C2410的芯片手册,MMU章节。
 +
[[File:chapter21_lesson3_004.jpg|700px]]
 +
 +
对于一级页表,我们只需要关系“Section”这一行,里面的PA是物理地址,剩下的AP、Domain、C、B用来进行权限管理。
 +
 +
 +
简单插讲一下概念。
 +
 +
 +
*权限管理:
 +
权限管理就是是否允许程序访问某块内存,有以下几种情况:
 +
 +
a.完全不允许访问;
 +
 +
b.允许系统模式访问,不允许用户模式访问;
 +
 +
c.用户模式下,根据描述符中的AP决定怎么访问;
 +
 +
 +
*域:
 +
在CP15寄存器有个C3,用来进行域控制。
 +
 +
ARM9中,有16个域,每个域用2位来表示4种权限。
 +
 +
[[File:chapter21_lesson3_005.jpg|700px]]
 +
 +
 +
*条目/描述符(AP):
 +
①设置domain;查看CP15 C3,确定域权限;
 +
 +
②如果域权限是01,使用AP来决定;
 +
 +
AP来自页表中的描述符,S、R来自CP15中的C1;
 +
 +
[[File:chapter21_lesson3_006.jpg|700px]]
 +
 +
最后再来补充一个概念,前面我们运行多个APP,切换进程时,需要重新把0x80B4地址对应到不同的物理地址上,也就是说,每切换一个进程,你都需要重新修改下页表,这个开销非常的大,那有什么办法优化呢?
 +
 +
引入'''MVA''',也就是修改后的虚拟地址。
 +
 +
<syntaxhighlight lang="c" >
 +
if (VA<32M)
 +
    MVA=VA|(pid<<25);
 +
else
 +
    MVA=VA;
 +
</syntaxhighlight>
 +
 +
 +
当虚拟地址小于32M时,MVA和进程的PID有关,否则等于VA,这就可以解决切换进程,频繁构造页表的问题。
 +
假设现在有两个APP,分别是APP1和APP2,链接地址都是0x80b4,PID分别是1和2。
 +
 +
①当CPU运行APP1时,发出VA,MVA=VA(1<<25),对应的页表是PA=APP1所在的内存;
 +
 +
②当CPU运行APP2时,发出VA,MVA=VA(2<<25),对应的页表是PA=APP2所在的内存;
 +
 +
虽然我们发出的都是同一个VA,但因为PID不一样,所对应的页表项也就不一样,也就不需要重新去构造页表,这样进程从APP1切换到APP2时,只需要修改PID即可,不需要去重新创建页表,这样就可以提高切换效率。
 +
 +
 +
='''《《所有章节目录》》'''=
 +
 +
<categorytree mode=all background-color:white;">ARM裸机加强版</categorytree>
 +
 +
[[Category:ARM裸机加强版]]
 +
 +
[[Category:ARM裸机加强版|第21课 MMU和Cache]]
  
 
=第002节_协处理器指令_开启ICache代码示例=
 
=第002节_协处理器指令_开启ICache代码示例=

2018年3月6日 (二) 09:12的版本

第001节_Cache简述及协处理器指令

如果对MMU ICache有所了解或者知道其概念作用,那么这节课可以跳过,我们很少会使用MMU或ICache

在2440芯片里面除了CPU之外 Instruction MMU 指令MMU Data MMU 数据MMU InstructionC ACHE(16KB) 指令cache Data CACHE (16KB) 数据cache

全都通过CP15协处理器来进行操作这些


协处理器的含义作用 coprocessor协助主处理器做某些事情, 比如在ARM系统中有cp0 – cp15一共16个协处理器,其中cp15负责管理mmu icache

写一个程序,0到100求和

int sum()
{
	int I;
	int sum =0;
	for(i=0; I <= 100; i++)
		sum += I;
	return sum;
}

查看反汇编代码

局部变量保存在栈中,也就是内存

70: 	e50b3014    		str 	r3, [fp,#-20]  //这个应该就是sum 假设地址是A

78 	e50b3010		str   r3,  [fp, #-16] //这个应该就是I 假设地址是B
					ldr   r3,   [fp, #-16] //也就是地址B中取出值
					cmp  r3, #100 //跟100比较
				//如果大于100程序跳到  a8 如果小于100则执行下面的for循环
7c:
 a4
指令保存在内存中,CPU根据这些执行进行操作

1 不断的读写地址A和B

2 不断的执行for循环里面代码

2.1 取指令

2.2 执行指令

问SDRAM非常慢,那么怎么提高程序执行效率? 先引入一个感念,程序局部性原理

  • 时间局部性:在同一段时间里,有极大的概率访问同一地址的指令或数据

(在这个for循环中同一个地址指令经常被访问到)

  • 空间局部性: 有极大概率访问到相邻空间的指令/数据

我们在一个比较慢的SDRAM上能不能在CPU上开一个高速缓存,把这些指令放进高速缓存icache

指令cache只有16KB 数据cache也只有16KB 而我们的SDRAM有64MB空间,显然擦车不可能存储SDRAM中所有的内容,它只能存储一部分

cache的示意图

以数据开始为例

  • 1 程序要读地址A的数据
ldr r0, [A的数据]

a. cpu以地址A查找cache,一开始cache无数据,导致cache miss

返回一系列的数据,叫做cache line: 8word 32byte

b. cpu把地址A发到SDRAM,读入cache line,成为cache file 把地址A上的数据返回给CPU 2 程序再次读取地址A的数据 a cpu以地址A查找cache,cache hit有数据直接从cache返回数据给CPU

3 程序要读地址B的数据,CPU也是以地址B查找数据,cache hint直接返回

4 cache满了,CPU访问C a cache替换,置换老的数据 b 填充新数据

数据写 write buffer 查看2410芯片手册 附录 appendix4-caches, write buffer 585页

设置为NCNB (no cache no buffer)数据直接到达硬件不经过缓冲器

比如GPFDAT寄存器CPU读寄存器的时候想读到引脚状态,不应该从cache读取老的数据,而是不断直接访问硬件返回最新的数据 对于这些寄存器应该设置为NCNB


不使用cache但数据写到buffer中,CPU就不管了 由write buffer直接进行写操作 CPU直接操作下一条指令


第一种不使用cache buffer 适用于直接硬件操作 gpio 得到最新数据 第二种 不使用cache使用write buffer, cpu把写发给buffer,cpu就可以直接下一条指令 第三种 WT 写通方式 使用cache不使用buffer,马上写硬件 CPU直接写给write buffer 由write执行缓慢写操作 第四种 写回方式 miss: cpu数据直接到达write buffer hit: cpu数据写入cache标记为dirty,让后会在合适的时机由write buffer写给硬件

合适的时机 cache替换时dirty会写给write buffer写给硬件 或者强制Flash cache 写给write buffer 写给硬件


下节讲协处理指令



第003节_MMU及地址映射

对于JZ2440它有64M内存(SDRAM),假设现在有N个APP同时运行,则:

①它们同时保存在SDRAM里;

②它们的地址各不相同;


之前我们讲过链接地址,链接地址就是程序运行时所处地址。

假设APP1所处的地址是Addr1,APP2所处的地址是Addr2,APPn所处的地址是Addrn,

则编译某个App时,需要单独指定它的的链接地址,这是一个不可能完成的任务。

因为,假如只有几个程序,为每个程序单独的指定地址还能够实现,但对于一个开放式的嵌入式系统,应用程序可能有成百上千个,你不可能重新编译这成百上千的应用程序,并且这些应用程序运行时保存的地址,也是不可预料的。


为了解决上述问题,于是就引入了虚拟地址

也就是说虽然这些应用程序它们保存在内存中的位置各不一样,但对于CPU,它们运行时,都在同一个虚拟地址上。


举个例子,如视频中的两个hello应用程序,编译后查看反汇编代码,可以看到这两个程序的起始地址都是0x80A4。于是CPU运行两个APP时,都会去0x80A4读指令,然后经过MMU转换成Addr1、Addr2。这样,不同的APP可以在任意地址,经过MMU地址转换后,在内存上是不同的地址,互不干扰。 Chapter21 lesson3 001.jpg

这里说的同时运行,并不是真正的同时运行,CPU是分时操作,APP1先工作很短一段时间,再APP2工作很短的一段时间,宏观的来看就是两个在同时工作。

因此,引入虚拟机地址的原因之一:让APP可以以同样的链接地址来编译;


在电子系统里面,内存都是有限的,无论是嵌入式系统还是电脑,比如我们的JZ2440内存就只有64M,这时假如有一个APP,需要1G的内存。应用程序执行时,不是一次性将所有代码都放入内存,而是将要运行的部分依次放入,当放入的代码指令大于64M后,会先将SDRAM里暂时用不到代码指令先置换出来,再放入需要运行的代码指令。这样尽管SDRAM很小,也可以运行内存需要很大的应用程序,而这个置换管理的工作,就是由MMU完成的。 Chapter21 lesson3 002.jpg 因此,引入虚拟机地址的原因之二:让大容量APP可以在资源少的系统上运行;


此外,不同的APP之间应该相互独立,避免APP1能直接访问到APP2,以防止APP1影响APP2。

因此,引入虚拟机地址的原因之三:权限管理,禁止访问其它空间;


CPU发出虚拟地址(VA)到达MMU,MMU转换成物理地址(PA)发给硬件,那么MMU怎么根据什么将一个虚拟地址转换成物理地址?

a.表格

最简单的方法就是弄一个表格,将VA和PA对应起来,根据VA就能找到PA。这种方法优点是简单,缺点是有点浪费空间,需要同时记录VA和PA的地址。


b.改进

在表格里面,我们只保存PA,PA1对应的VA是0~1M-1,PA2对应的VA是1M~2M-1,以此类推。这样改进后,只需要原来表格容量的一半即可。最后还需要把基地址告诉MMU,启动MMU。

Chapter21 lesson3 003.jpg


怎么使用MMU?

1.在内存中创建这些表格(页表);

2.把页表基地址告诉MMU;

3.设置CP15,启动MMU;


前面图中的是一级页表,对于一级页表,条目/描述符对应的大小是1M,条目/描述符的格式可以参考S3C2410的芯片手册,MMU章节。 Chapter21 lesson3 004.jpg

对于一级页表,我们只需要关系“Section”这一行,里面的PA是物理地址,剩下的AP、Domain、C、B用来进行权限管理。


简单插讲一下概念。


  • 权限管理:

权限管理就是是否允许程序访问某块内存,有以下几种情况:

a.完全不允许访问;

b.允许系统模式访问,不允许用户模式访问;

c.用户模式下,根据描述符中的AP决定怎么访问;


  • 域:

在CP15寄存器有个C3,用来进行域控制。

ARM9中,有16个域,每个域用2位来表示4种权限。

Chapter21 lesson3 005.jpg


  • 条目/描述符(AP):

①设置domain;查看CP15 C3,确定域权限;

②如果域权限是01,使用AP来决定;

AP来自页表中的描述符,S、R来自CP15中的C1;

Chapter21 lesson3 006.jpg

最后再来补充一个概念,前面我们运行多个APP,切换进程时,需要重新把0x80B4地址对应到不同的物理地址上,也就是说,每切换一个进程,你都需要重新修改下页表,这个开销非常的大,那有什么办法优化呢?

引入MVA,也就是修改后的虚拟地址。

if (VA<32M)
    MVA=VA|(pid<<25);
else
    MVA=VA;


当虚拟地址小于32M时,MVA和进程的PID有关,否则等于VA,这就可以解决切换进程,频繁构造页表的问题。 假设现在有两个APP,分别是APP1和APP2,链接地址都是0x80b4,PID分别是1和2。

①当CPU运行APP1时,发出VA,MVA=VA(1<<25),对应的页表是PA=APP1所在的内存;

②当CPU运行APP2时,发出VA,MVA=VA(2<<25),对应的页表是PA=APP2所在的内存;

虽然我们发出的都是同一个VA,但因为PID不一样,所对应的页表项也就不一样,也就不需要重新去构造页表,这样进程从APP1切换到APP2时,只需要修改PID即可,不需要去重新创建页表,这样就可以提高切换效率。


第002节_协处理器指令_开启ICache代码示例

CPU中还有许多协处理器来协助主处理功能 比如2440有CP0 ~ CP15一共16个协处理器

CP15管理cache mmu 我们启动cache需要操作CP15 协处理器指令 先看硬件结构

CP15中也有许多寄存器 C0 ~ C15 启动C7’ 是备份寄存器

现在主CPU中某一个值R0传给CP15中的某一个寄存器

我们需要引入协处理器指令 mrc mov r1, r0 结果是r0 =传给=> r1 mrc c coprocessor =传给=> register



mcr 是把主处理器的值发给协处理器 register =传给=> coprocessor 查看一下语法格式 在2440中搜索mrc

得到语法格式


<MCR|MRC>{cond} p#,<expression1>,Rd,cn,cm{,<expression2>} 举个例子

mcr P15, 0, r1,c1 把主处理器的值发给协处理器 expression1 值设置为0,表示用不到 r1 是主cpu寄存器里面的值 c1 是cp15寄存器里的值

cm, 用不到,写为c0 expression2 值设置为0,表示用不到 cm和expression2用来区分哪一个c1,一般写为c0, 0

这条命令表示主cpu中r1 值写入 协处理器cp15 中的c1寄存器


反过来要从cp15寄存器读到主cpu寄存器 mrc p15, 0, r1, c1, c0, 0 这条命令表示协处理器cp15 c1寄存器的值读出来写入主cpu的r1寄存器

2410手册中有讲cp15寄存器的作用

其中寄存器1控制寄存器 下图为介绍控制寄存器1的功能

bit12位是控制cache指令的开启或者关闭,我们等下把bit 12设置为1

c7里面有许多不同的寄存器,对应不同的功能

寄存器7表示用来操作cache,根据语法规则cm{,<expression2>} 来区分选择那个c7


接下来写程序使能cache 注意2440里有data cache和指令cache 其中data cache要启用地址映射才可以使用,只能使用指令cache

打开start.s

reset:
	/* 关闭看门狗 */
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]

	/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
	/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
	ldr r0, =0x4C000000
	ldr r1, =0xFFFFFFFF
	str r1, [r0]

	/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
	ldr r0, =0x4C000014
	ldr r1, =0x5
	str r1, [r0]

	/* 设置CPU工作于异步模式 */
	mrc p15,0,r0,c1,c0,0
	orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA
	mcr p15,0,r0,c1,c0,0

	/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
	 *  m = MDIV+8 = 92+8=100
	 *  p = PDIV+2 = 1+2 = 3
	 *  s = SDIV = 1
	 *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
	 */
	ldr r0, =0x4C000004
	ldr r1, =(92<<12)|(1<<4)|(1<<0)
	str r1, [r0]

	/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
	 * 然后CPU工作于新的频率FCLK
	 */
		/*
		使能icache
	*/
	bl enable_icache

	/* 设置内存: sp 栈 */
	/* 分辨是nor/nand启动
	 * 写0到0地址, 再读出来
	 * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
	 * 否则就是nor启动
	 */
	mov r1, #0
	ldr r0, [r1] /* 读出原来的值备份 */
	str r1, [r1] /* 0->[0] */ 
	ldr r2, [r1] /* r2=[0] */
	cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */
	ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
	moveq sp, #4096  /* nand启动 */
	streq r0, [r1]   /* 恢复原来的值 */

	bl sdram_init
	//bl sdram_init2	 /* 用到有初始值的数组, 不是位置无关码 */

	/* 重定位text, rodata, data段整个程序 */
	bl copy2sdram

	/* 清除BSS段 */
	bl clean_bss

	/* 复位之后, cpu处于svc模式
	 * 现在, 切换到usr模式
	 */
	mrs r0, cpsr         /* 读出cpsr */
	bic r0, r0, #0xf     /* 修改M4-M0为0b10000, 进入usr模式 */
	bic r0, r0, #(1<<7)  /* 清除I位, 使能中断 */
	msr cpsr, r0

	/* 设置 sp_usr */
	ldr sp, =0x33f00000

	ldr pc, =sdram
sdram:
	bl uart0_init

	bl print1
	/* 故意加入一条未定义指令 */
und_code:
	.word 0xdeadc0de  /* 未定义指令 */
	bl print2

	swi 0x123  /* 执行此命令, 触发SWI异常, 进入0x8执行 */

	//bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
	ldr lr, =halt
	ldr pc, =main  /* 绝对跳转, 跳到SDRAM */

halt:
	b halt
如何使能icache 打开2410芯片手册
enable_icache:
	/* 设置协处理器使能icache */
	mrc p15, 0, r0, c1, c0, 0
	orr r0, r0, #(1<<12)  /* r0 = r0 or (1<<12) */
	mcr p15, 0, r0, c1, c0, 0	//吧修改好的r0写给cp15的c1寄存器
	mov pc, lr

刷屏效率变快