ARM处理器之Cache与MMU

目录

 

Cache简述

协处理器指令

MMU及地址映射

MMU代码示例


Cache简述

对于ARM芯片中包含这指令Cache和数据Cache以及MMU,这些MMU和Cache是通过协处理器(coprocessor)CP15来操作的,协助主处理器,在ARM9系统里面有CP0到CP15总共16个协处理器

下面有一段程序,假设sum为地址A,i为地址B,根据反汇编我们可以发现会不断地读写地址A和B,不断地执行for循环中的代码,取指令和执行指令,在JZ2440中SDRAM非常慢,怎么提高程序执行效率

#include <stdio.h>

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


int main()
{
        int num;
        num = sum();
        printf("num = %d\n", num);
        return 0;
}

这涉及到程序局部性原理:

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

把小段空间的程序全部读到指令Cache,执行程序时优先从指令Cache中取指令,如果没有指令再去内存读指令,CPU访问数据时,将数据读到数据Cache,以后读数据时,优先从数据Cache中读取,没有数据再去访问内存,这样就可以加快了速度,在S3C2440中对于指令Cache和数据Cache都只有16KB

 

如果开启了Cache,对于该程序执行过程概述如下:

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

  • CPU以地址A查找Cache,一开始Cache没有任何数据,导致Cache miss
  • CPU把地址A发到SDRAM (并不止返回地址A的数据,而是返回一系列数据即Cache line,在ARM9中一个Cache line就是8个word即32byte),数据读入Cacheline称为cachefill(填充Cache),并且把A的数据返回给CPU

2.程序再次读取地址A的数据

  • CPU以地址A查找Cache,Cache中有数据,即Cachehit(Cache命中),直接从Cache返回数据给CPU

3.程序读地址B的数据,CPU以地址B查找Cache, 之前读地址A的数据中Cache line包含着附近的内容,而B就在A附近,因此也会导致Cachehit,直接返回(空间局部性)

4.假设Cache满了,想访问新数据C时,a.导致Cache替换把老数据置换出去 b.填充新数据

 

这过程只涉及到数据的读,并没有涉及到数据的写,对于数据Cache和指令Cache的操作是差不多的,对于写的过程涉及到writebuffer,在上图中有体现到,如果我们想读取GPIO的引脚状态,就不应该使用Cache,因为数据需要立马返回,而不应该存在Cache中,CPU应该直接访问硬件,所以对于寄存器我们需要设置为non cache non buffered(即不用Cache和writebuffer),读的时候读硬件,写的时候写硬件,而不是缓冲到cache中去

 

对于Cache和writebuffer就有四种方式,如下图所示

模式一:不使用Cache和writebuffer

模式二:NCB模式,读和写都会操作到硬件,写的时候CPU直接把数据给writebuffer,然后就去执行下一条指令,有writebuffer来执行缓慢的写操作过程

模式三:WT模式(写通,即马上写硬件),读的时候优先执行Cache的内容,Cache有数据的话直接返回数据,如果没数据会linefill,所有的写操作都会先写入writebuffer,writebuffer会马上把数据写到硬件上去,CPU写给writebuffer就马上执行下一条指令,不等待写操作完成

模式四:WB模式(写回,即使用Cache和writebuffer),读操作是类似的,都优先从Cache里面读数据,写的时候,分为两种情况,一种是Cache miss的话,会直接把数据写给writebuffer,writebuffer会马上写到硬件上去,CPU不等待写操作完成;如果Cachehit的话,CPU会把数据写在Cache并标记为dirty,表示此数据需要更新,在以后合适的时机会写给writebuffer,由writebuffer把数据写到硬件上,写操作是以后的事,CPU只是标记数据

 

协处理器指令

对ARM芯片除了CPU外还有很多协处理器,协处理器就是协助主处理器,对于ARM9有CP0-CP15总共16个,而CP15用来管理Cache和MMU,想启动Cache,问题是怎么把数据给CP15,或者再总CP15中得到数据,对于CP15里面也有各种寄存器C0-C15,对于C?还含有备份寄存器,例如C7里面有备份寄存器 C7'、C7''、C7'''等 ,假设想访问C7'',需要引入协处理器指令mrc和mcr指令,对于mrc指令是把协处理器里面的值传给CPU寄存器,而mcr指令是把CPU寄存器的值写给协处理器,下面有该指令的介绍

后面两个参数用来区分哪一个C1,一般写为"C0,0",这句话的一是主CPU的r1写入CP15的c1中

mcr P15, 0, r1, c1, c0, 0

 CP15的c1的值写入CPU的r1

mrc P15, 0, r1, c1, c0, 0 

对于指令Cache需要将CP15中的C1寄存器的bit12设置为1,对于数据Cache需要我们使能MMU后才能开启

程序示例,运行程序,会发现程序执行速度明显增快

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	
	mov pc, lr

 

MMU及地址映射

对于JZ2440来说有64M的SDRAM,假设有N个APP同时运行,它们保存同时保存在SDRAM中,并且地址各不相同,链接地址为程序运行时所处地址,假设APP1为地址1,APP2为地址2,APPN为地址n,则编译的某个APP的时候,需要单独指定它们的链接地址,这是不可能完成的任务,如果一两个还可以指定,N个APP就实现不了,APP多不可能重新编译,也不可能预测它所处的位置

引入虚拟地址的概念:

下面有两个APP,其反汇编初始运行地址都是0x80b4,APP都处于同一个虚拟地址,CPU都以虚拟地址0x80b4去读取指令,这些虚拟地址(VA)会转换为物理地址(PA),即VA到PA,处理转换的过程就是MMU(负责地址转换),APP同时运行只是对人类来说,而实际上是先让APP1运行若干毫秒,再让APP2运行若干毫秒等轮流运行的,当运行到APP1时,可以让同一块虚拟地址指向APP1所在的物理地址,当运行到APP2时,可以让同一块虚拟地址指向APP2所在的物理地址

$ cat hello1.c
#include <stdio.h>

int main()
{
        while(1);
        return 0;
}
$ cat hello2.c
#include <stdio.h>

int main()
{
        printf("hello,world\n\r");
        while(1);
        return 0;
}

引入虚拟地址的原因:

  • 让APP以同样的链接地址来编译
  • 让大容量APP可在资源少的系统上运行,VA到PA的映射都是MMU实现的(SDRAM有限,对于JZ2440来说只有64M,APP假设要求内存需要1G,需要1G内存的应用可否运行在64M的内存上,答案是可以运行的,执行应用程序的时候,只会执行其中的某一段代码,然后如果跳转再去执行另一段代码,不可能一下子执行完1G的指令,所以一开始可以用其中一块VA映射到某一块PA,依次执行并映射,如果SDRAM满了,就先置换出PA的内容然后再让新的VA映射到新的PA上,这样就可以让1G的应用程序在64M的内存运行起来,因此中间有很多置换和映射,这些操作是操作系统完成的)
  • MMU不止有地址映射的功能,还有权限管理的功能(APP1、APP2、APPN,如果APP1写得很烂,访问的内存越界了,想去修改APP2的内容,就有可能把APP2给干掉了,因此有权限管理,让APP1只能够访问自己的内存,因此可以禁止APP来访问其他空间)

因此MMU有两个功能,一是地址映射,而是权限管理

CPU发出VA到达MMU,MMU转换为PA然后访问硬件,MMU怎么转换,有一个表格,里面放VA和PA,假设VA1对应PA2,VA2对于PA2...,这样就浪费空间,通过改进表格中只放PA,其中PA1对应0~1M-1,PA2对应1M~2M-1,PA3对应2M~3M-1...,只需要之前表格的一半内存,构建这个表格后需要把表格基地址告诉MMU,然后就可以启动MMU

怎么使用MMU:

  • 在内存中创建这个表格,称为页表,(前面说的页表为一级页表,里面的每一项称为条目或者描述符,每一个条目对应1M,1M的物理地址对应1M的虚拟地址,如果想映射更小的范围,需要二级页表,这里不用二级页表,知道MMU的概念就可以了)
  • 把页表基址告诉MMU
  • 设置CP15启动MMU

对于条目的格式如下图所示,用一级页表只需要关心其中的Section段描述符,其中"Section base address"就是物理地址(PA),还有后面的" AP Domain C B"用来进行权限管理的,其中的C和B是用来表示这块空间是否使用Cache和writebuffer

权限管理功能:

  • 完全不允许访问
  • 允许系统模式访问,不允许用户模式访问
  • 用户模式下,根据描述符中AP的值决定怎么访问

这里需要引入"域(domain)"的概念,对ARM9中CP15的C3有16个域,每个域用2位来表示4中权限,bit31、30设置为00则表示无法访问,即域15,将条目中的domain设置为域15,就用域15的权限控制,这块内存就访问不了

因此对于页表中的条目或者称为描述符的设置过程为:

  • 设置domain,查看CP15's C3确定域权限,如果是00就无法访问
  • 如果域权限是01,则使用AP来决定权限,AP来自页表中的描述符,而S、R是CP15中控制寄存器C1,根据这些组合来管理权限

 

对于学习者,我们只关心映射,权限管理可以设置为管理模式,这里补充一个概念,APP处于同一块虚拟地址映射到不同的PA上,每切换一下进程都需要重新修改页表,开销大,解法办法,引入MVA的概念,即修改后的虚拟地址,CPU发出VA到 CP15's C13(含有进程PID) 经过修改后得到MVA,再进入MMU,MMU根据MVA查表页表得到PA,使用PA访问硬件,下面有段程序其中的机制,MMU和Cache看到的都是MVA,在程序中不会去区分VA和MVA,我们提到虚拟地址VA时默认指的是MVA

  • 当虚拟地址小于32M时,MVA跟PID有关,这就可以解决切换进程频繁构造页表的问题 ,假设有两个进程 APP1 APP2 链接地址都是0x80b4开始
  • 假设PID分别是1和2,首先CPU运行APP1时,发出VA,MVA = VA | (1 << 25) 对应页表PA1即APP1所在物理地址,然后CPU运行APP2时,发出VA,MVA = VA | (2 << 25) 对应页表PA2即APP2所在物理地址
  • 因此使用同一块VA,由于PID不一样,对于的页表项也不一样,就不需要重新去构造页表,这样APP1切换到APP2时,只需要修改PID就行了,这样就是MVA引入的原因
if(VA < 32M)

​	MVA = VA | (PID << 25);

else

​	MVA = VA;

 

MMU代码示例

需要创建页表然后启动MMU,页表是保存在SDRAM,对于JZ2440需要先初始化SDRAM才能创建页表,其中create_page_table为C函数

...
	bl sdram_init

	/* 创建页表 */
	bl create_page_table

	/* 创建页表 */
	bl create_page_table

	/* 启动MMU */
	bl mmu_enable

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

	/* 清除BSS段 */
	bl clean_bss
...

对于创建页表,根据其中的条目描述符定义宏,对于寄存器我们需要设置为不用cache和writebuffer,因此有两种模式IO模式和MEM模式,MEM模式cache和writebuffer都使用,在32位系统中VA虚拟地址为0-4G,条目数为4G除以1M即4096个,每一个条目4字节,因此页表的大小为4096*4即16K

#define MMU_SECDESC_AP      (3<<10)
#define MMU_SECDESC_DOMAIN  (0<<5) //用域0
#define MMU_SECDESC_NCNB    (0<<2)
#define MMU_SECDESC_WB      (3<<2)
#define MMU_SECDESC_TYPE    ((1<<4) | (1<<1))

#define MMU_SECDESC_FOR_IO   (MMU_SECDESC_AP | MMU_SECDESC_DOMAIN | MMU_SECDESC_NCNB | MMU_SECDESC_TYPE)
#define MMU_SECDESC_FOR_MEM   (MMU_SECDESC_AP | MMU_SECDESC_DOMAIN | MMU_SECDESC_WB | MMU_SECDESC_TYPE)


#define IO  1
#define MEM 0

void create_secdesc(unsigned int *ttb, unsigned int va, unsigned int pa, int io)
{
	int index;

	index = va / 0x100000;//得到条目位置

	if (io)
		ttb[index] = (pa & 0xfff00000) | MMU_SECDESC_FOR_IO;//对于PA只保留最高的16位
	else
		ttb[index] = (pa & 0xfff00000) | MMU_SECDESC_FOR_MEM;
}

程序从0地址开始运行,为了保证使能MMU,前后地址一致,0地址需要映射,0地址设置为IO模式,是为了对于JZ2440来说为了支持NOR或者NAND启动,其中ttb为SDRAM中一块没有占用的内存,最后需要告诉MMU,将链接地址设置为0xB0000000,因此需要先创建页表启动MMU后才能重定位,对于LCD的framebuffer我们需要设置其地址为IO模式,对于JZ2440的寄存器是从0x48000000~0x5B00001C开始因此都使用IO模式

/* 创建一级页表
 *    VA           PA           CB
 *    0            0            00
 *    0x40000000   0x40000000   11
 *
 *    64M sdram:
 *    0x30000000   0x30000000   11
 *    ......
 *    0x33f00000   0x33f00000   11
 *    
 *    register: 0x48000000~0x5B00001C
 *    0x48000000   0x48000000   00
 *    .......
 *    0x5B000000   0x5B000000   00
 *
 *    Framebuffer : 0x33c00000
 *    0x33c00000   0x33c00000   00
 *
 *    link address:
 *    0xB0000000   0x30000000   11
 */
void create_page_table(void)
{
	/* 1. 页表在哪? 0x32000000(占据16KB) */
	/* ttb: translation table base */
	unsigned int *ttb = (unsigned int *)0x32000000;

	unsigned int va, pa;
	int index;

	/* 2. 根据va,pa设置页表条目 */

	/* 2.1 for sram/nor flash */
	create_secdesc(ttb, 0, 0, IO);

	/* 2.2 for sram when nor boot */
	create_secdesc(ttb, 0x40000000, 0x40000000, MEM);

	/* 2.3 for 64M sdram */
	va = 0x30000000;
	pa = 0x30000000;
	for (; va < 0x34000000;)
	{
		create_secdesc(ttb, va, pa, MEM);
		va += 0x100000;
		pa += 0x100000;
	}

	/* 2.4 for register: 0x48000000~0x5B00001C */
	va = 0x48000000;
	pa = 0x48000000;
	for (; va <= 0x5B000000;)
	{
		create_secdesc(ttb, va, pa, IO);
		va += 0x100000;
		pa += 0x100000;
	}

	/* 2.5 for Framebuffer : 0x33c00000 */
	create_secdesc(ttb, 0x33c00000, 0x33c00000, IO);

	/* 2.6 for link address */
	create_secdesc(ttb, 0xB0000000, 0x30000000, MEM);
}

启动MMU,需要把地址告诉CP15的C2寄存器,设置所有的域为不进行权限检查,上面用的是域0,由于数据Cache需要再启动MMU才能使能,因此使能之后,程序跑的飞快,比只使能指令Cache还快

mmu_enable: 
	/* 把页表基址告诉cp15 */
	ldr r0, =0x32000000
	mcr p15, 0, r0, c2, c0, 0

	/* 设置域为0xffffffff, 不进行权限检查 */
	ldr r0, =0xffffffff
	mcr p15, 0, r0, c3, c0, 0

	/* 使能icache,dcache,mmu */
	mrc p15, 0, r0, c1, c0, 0
	orr r0, r0, #(1<<12)  /* enable icache */
	orr r0, r0, #(1<<2)  /* enable dcache */
	orr r0, r0, #(1<<0)  /* enable mmu */
	mcr p15, 0, r0, c1, c0, 0	

	mov pc, lr

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值