Chinaunix首页 | 论坛 | 博客
  • 博客访问: 83069
  • 博文数量: 11
  • 博客积分: 386
  • 博客等级: 一等列兵
  • 技术积分: 240
  • 用 户 组: 普通用户
  • 注册时间: 2011-12-02 17:11
文章分类

全部博文(11)

文章存档

2012年(11)

我的朋友

分类: 嵌入式

2012-09-16 23:25:04

寄存器

 

流水线和延迟槽(delay slot

IF—instruction fetch

RD—read register,读寄存器和指令译码

ALU—arithmetic/logic unit,运算

MEM—D-cache上读写数据

WB—write back,回写寄存器

branch delay:jump指令后面的一条指令一定会在branch分支的第一条指令前得以执行。

load delay:加载数据指令后面的一条指令不能用需要加载的数据,因为这个数据还没有加载到寄存器。

 

地址空间

访问kusegkseg2的两部分空间需要先设置TLB/MMU

kseg0kseg1的两部分空间映射到同一块物理空间,一般是物理内存的底512MB,比如0xA0000000对应物理内存的0地址。除了物理内存,通常这个512MB地址还包含了FLASH/ROM地址、IO寄存器地址等,映射方式取决于不同系统。

kseg0cachable地址,kseg1non-cachable

   核空间处于kseg0/kseg1区域,用户空间处于kuseg区域。简单的系统不区分内核空间或者用户空间,所以代码都在kseg0/kseg1区域。

MIPS上电第一条指令的地址是0xBFC0000,这个地址通常是FLASH/ROM空间。

下面一张图应该更清楚:

所以MIPS的内核空间可以有2G,但用户空间为2G。而且访问512MB以上的物理地址空间只能通过TLB进行访问。

 

协处理器

CP0的作用:MIPS芯片配置、cache管理、异常中断管理、TLB/MMU管理等。

CP1通常是浮点数协处理器。

count以流水线的频率的一半计数,用于需要精确计时的场合。

读写协处理器有专门的指令mfc/mtc

 

异常和中断

常见的异常有:中断、内存地址转换、非法操作、系统调用等等。

MIPS的异常入口点有:

对于异常来说,在开始处理异常以前,会完成当异常发生时处于MEM阶段的指令。

异常返回地址保存在EPC寄存器中,eret指令用于异常返回,它还会清楚SR(EXL)位。

MIPS的外部中断(硬件中断)和系统调用(软件中断)都属于最后一条异常。

 

异常处理

1.EPC寄存器赋值为被打断的指令地址

2.CPU变为核心态,禁止异常(通过SR[EXL]置位)

3.Cause寄存器赋值,判断是何种异常,如果是TLB miss/refill,也会把错误的地址赋值给相应的寄存器

4.CPU跳转到相应的异常入口

5.执行异常处理程序,异常处理程序要保存寄存器,保存在打断进程的内核栈里(linux 2.6有中断专用的栈),包括协处理器的寄存器等。如果要支持嵌套异常,需要清楚SR[EXL],并保存EPC的值。(不清除SR[EXL]的话,若在处理一个异常的时候,一个更高的异常发生,会处理那个异常,但是EPC不会更新?)

(既然将中断现场保存在内核栈上,所以事先应该完成了内核栈的切换,内核栈切换不需要用寄存器吗?没保存的寄存器能用吗?或许用的是k0k1寄存器,它们专供内核态使用)

6.返回异常,返回用的是eret指令

硬件做的事情:CPU状态设置、CAUSE寄存器、EPC寄存器等、CPU跳转到异常入口

软件做的事情:切换到内核态(包括栈指针指向内核栈)、保存现场(通用寄存器等)

 

所以,和异常中断息息相关的MIPS寄存器为SR[EXL]SR[IE]EPCCause。解释一下Cause寄存器,它的[6:2]ExcCode,用于描述异常类型,[15:8]IP,用于判断中断来源。

中断初始化代码片段(/arch/mips/kernel/traps.c):


  1. trap_init
  2.     set_handler(0x180, &except_vec3_generic, 0x80);
  3. //设置异常入口(将第一层的异常处理程序复制到0x80000180区域)
  4.     set_except_vector(0, handle_int); //中断处理
  5.     set_except_vector(8, handle_sys);//系统调用
  6. //异常处理表初始化,分别对应Cause寄存器的32种ExcCode
  7. except_vec3_generic
  8. NESTED(except_vec3_generic, 0, sp)
  9.     NMFC0(26,15,1)
  10.     andi k0,0x3ff
  11.     beqz k0, 1f
  12.     nop
  13.     li k0,0xa7E00180
  14.     lw k0,0x0(k0)
  15.     j k0
  16.     nop
  17. 1:
  18.     .set    push
  19.     .set    noat
  20.     mfc0    k1, CP0_CAUSE
  21.     andi    k1, k1, 0x7c
  22.     PTR_L    k0, exception_handlers(k1)//根据Cause寄存器执行不同的异常处理,可能是中断、也可能是其它,如系统调用等
  23.     jr    k0
  24.     .set    pop
  25.     END(except_vec3_generic)
  26. set_except_vector
  27. void *set_except_vector(int n, void *addr){
  28.     unsigned long handler = (unsigned long) addr;
  29.     unsigned long old_handler = exception_handlers[n];
  30.     exception_handlers[n] = handler;
  31.     return (void *)old_handler;
  32. }
  33. handle_int
  34. BUILD_ROLLBACK_PROLOGUE handle_int
  35. NESTED(handle_int, PT_SIZE, sp)
  36.     SAVE_ALL//在这里保存被打断的现场,保存在内核栈上
  37.     CLI
  38.     TRACE_IRQS_OF
  39.     LONG_L    s0, TI_REGS($28)
  40.     LONG_S    sp, TI_REGS($28)
  41.     PTR_LA    ra, ret_from_irq
  42.     j    plat_irq_dispatch//根据Cause的IP和中断状态,调用do_IRQ执行已注册的ISR
  43.     END(handle_int)
  44.     __INIT
注:以上资料来源于Dajie Tan写的MIPS Linux异常中断代码分析.pdf,感谢作者

 

Cache

cache由很多cache line组成,一条cache line由多个字节组成,比如32bytes

访问内存时(kseg0区域地址),内存地址若在cache中存在,称为hit,直接读写cache即可;若不在cache中,称为miss,则需要在内存中读取。

假设cache line32字节,内存以32字节对齐的方式加载到cache,但是加载到哪条cache line呢?有基本的两种做法:

1.内存可以加载到任意一条cache line,称为“全相联”

2.内存只能加载特定的cache line,比如一共512cache line,内存加载到address%512cache line,称为“直接相连”

第一种方法的缺点是,每次访问内存的时候,需要查找所有的cache line,速度慢。

第二种方法的缺点是,不同地址更有可能加载到同一条cache linecache数据进出频繁,命中率低。

为此,有一种称为“N路组相连”的方式,即把所有的cache line分组,每组由Ncache line。内存只能被加载到特定的组(直接相联),但在组内可以选择任意的cache line(全相联)。所以N路组相连的方式集合了“全相联”和“直接相连”的优点。

假设有16384128×128)组cache line,每组432字节的cache line,总cache4MB。若要加载第0个内存页面(4KB),则会加载到0127组的第一条cache line(假定cache为空,每组一次只用一line);第1个内存页面会加载到128255组的第一条cache line;第128个内存页面又会加载到0127组,但用第二条cache line,若4cache line全部占用,则会替换掉先前的某一条。

更详细的说明搜一篇Comcat写的The MIPS Cache Architecture.pdf,感谢作者

 

为保证内存与cache之间的一致性(cache-coherent),有几种做法:

write-back:当cache的数据被CPU更改之后,这个数据就标致为dirty,需要回写至内存;

write-through:每次CPU更改了cache中的数据,也会自动回写至内存(一般都用write-back吧);

invalid:当内存的数据更新,需要将cache置为invalid

说下使用场合,假设一个网卡通过DMA和内存传输数据,CPU发数据的时候,现在cache中构造网络报文,它用的当然是kseg0的地址(小于0xa0000000),然后触发DMA将数据传输至网卡,但是在触发DMA之前,一定要有一个cache write-back的操作;当网卡接收到数据,要传输给内存,CPU会给DMA控制器一个内存地址,这个地址应该是kseg1的地址(大于0xa0000000,其实DMA可能只认总线地址或者说实际的物理内存的地址,就不一定是大于0xa0000000了),anyway,假设地址是0xa345000,0然后网卡ISR中递交给协议栈的地址应该是kseg0地址,即0x83450000,但是在递交之前一定要有一个cache invalid的操作。当然递交协议栈可以用0xa3450000,但是这会非常慢。

总结下来就是:在DMA输出之前要write-back,在DMA输入之后要invalid

 

TLB/MMU

MMU用处有:

每个用户进程都可以有独立的地址,尽管这些地址看着相同

32位上也有4GB地址空间,即使实际的物理内存没那么大

不连续的物理内存可以变成连续的虚拟地址

有助于隐藏和保护地址空间

BTW:内核是一个多线程的,只有单一地址空间

4KB的页是一个折中的结果

TLBtranslation lookaside buffer)是一个硬件储存器,它起到cache作用,它储存着虚拟地址和物理地址的对应关系。普通的TLB可能储存着16/32/64项这种地址对应关系,容量比较小,所以不可能储存所有虚拟地址和物理地址的对应关系,转换内存还需要依靠软件的协助(TLB异常)。

上图TLB中的每一项为:

VPN2 为虚拟内存页号,PFN 为物理内存页号

一次可以对应两个物理内存页,也就是假设TLB16项,它可以查找32个物理内存页,当然VPN2是按双页地址输入的。EntryLo0对应偶数物理页,EntryLo1对应奇数。

ASID是进程号,即PIDPageMask指明页大小。

所以,ASIDVPN2构成了TLB查找的key

 

在内存中,软件还要维护一个表(page table)用以描述所有的虚拟地址和物理地址的转换。现代操作系统中,当CPU访问某个地址时,这个地址是虚拟地址,首先TLB会检查它有没有这个虚拟地址(全相联的方式),若有的话,则返回物理地址;这个物理地址会给cache模块,cache会检查它有没有这个物理地址(N路组相连的方式),若有则返回数据;若cache没有,则读取物理内存;

TLB中没有这个虚拟地址,则会发生TLB refill的异常,异常处理程序中会从内存维护的表中返回物理地址,再将这个物理地址随机写入TLB,然后TLB refill异常返回(异常返回后,重新访问这个虚拟地址,TLB就会返回其物理地址),之后cache会做同样的处理流程。这种方式就是Virtually Indexed, Physically tagged

BTWpage table一般是一个多级表(前一张表是后一张的目录)。

 

TLB refill的处理

通过查找page table看是否有正确的转换地址,没有的话,则调用处理地址错误的程序

有正确的转换地址的话,构造一个TLB项(ASIDVPNPFNflags等组成)

将新建的一项随机写入TLB即可

TLB有专门的读写指令,比如tlbwi就是写一个entryTLB的第index项,tlbwr是写一个entryTLB的随机项。

异常发生的时候,EntryHiVPN2自动装入访问失败的地址。

 

参考

see mips run

Dajie Tan写的MIPS Linux异常中断代码分析.pdf

Comcat写的The MIPS Cache Architecture.pdf

阅读(3339) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~