SoC嵌入式软件架构设计之二:内存管理单元的软、硬件协同设计

来源:互联网 发布:酷狗铃声制作专家 mac 编辑:程序博客网 时间:2024/04/30 23:33

软硬件整合一直是SOC芯片设计的核心技术,由系统软件架构师和芯片系统设计人员共同评估、设计SOC的各个模块,以性能、成本、软件编程灵活性、软件扩展性等作为考量依据,决定模块哪些流程可以软件硬化,哪些环节可以硬件软化。本文是软硬件整合的教科书式的经典案例!


以此文祝福所有父亲节日快乐,我们所有的努力也是为了让我们的父母亲和我们的子女生活得更好。


引子


程序的大部分代码都可以在必要的时候才加载到内存去执行,运行完后可以被直接丢弃或者被其他代码覆盖。我们PC上同时跑着很多的应用程序,每个应用程序使用的虚拟地址空间几乎可以整个线性地址空间(除了部分留给操作系统或者预留它用),可以认为每个应用程序都独占了整个虚拟地址空间(字长是32的CPU是4G的虚拟地址空间),但我们的物理内存只是1G或者2G。即多个应用程序在同时竞争使用这块物理内存,其必然会导致某个时刻只存在程序的某个片段在执行,也即是所有 程序代码和数据分时复用物理内存空间 —这就是内存管理单元(MMU)工作核心作用所在。


处理器系列的芯片(如X86、ARM7以上、MIPS)一般都会有MMU,跟操作系统一块实现虚拟内存管理,MMU也是Linux、Wince等操作系统的硬件要求。而控制器系统的芯片(面向低端控制领域,ARM1,2,MIPS M系列,80251等)一般都没有MMU,或者其只有单一的线性映射机制。


本文要谈的是控制器领域SoC的内存管理单元的硬件设计,其重要的理念同样是代码和数据分时复用物理内存空间,在保障系统功能和性能的基础上最大限度地节省物理内存的目的。相关的文章包括:SoC软件架构设计之一:系统内存需求评估节省内存的软件设计技巧


一、内存管理单元(MMU)的工作机制

在阐述控制器领域的内存管理之前,还是要先介绍处理器领域的虚拟内存管理机制,前者很大程度上是对后者核心机制精髓的借鉴。实现虚拟内存管理有几个模块是协调工作的:CPU、MMU、操作系统、物理内存,如图示(假设该芯片系列没有cache):


我们根据上图来分析一下CPU访问内存的过程,假设寻址是0x10000008,一页大小为4K(12比特)。则虚拟地址会分成两个部分:页映射部分(20bit,0x10000)+页内偏移(12bit, 0x8)。CPU通过总线把地址信号(0x10000008)送给MMU,MMU会把该地址的页映射部分(20bit)拿到TLB中匹配。


TLB是什么东西?Translation Lookaside Buffer,网上有称为“翻译后备缓冲器”。这个翻译都不知道它干什么。它的作用就是页表的缓冲,我喜欢叫它为页表cache。其结构图如下:



可以想象,TLB就是索引地址数组,数组的每个元素就是一个索引结构,包含虚拟页地址和物理页地址。其在芯片内部表现为寄存器形式,一般寄存器都是32位,实际上TLB中的页地址也是32位寄存器,只不过索引比较时是比较前20bit,后12bit其实也是有用的,例如可以设置某个bit是表示常驻的,即该索引是永远有效的,不能更换,这种场景一般是为适合一些性能要求特别高的编解码算法而设计的。非常驻内存的一般在某个时刻(如TLB填满时访问一个新的页地址)就会发生置换。


1) 假如 0x10000008的前20bit在TLB中第M个索引中命中,这时就表示该虚拟页在物理内存中已经给它分配好对应的物理内存,页表中也已经做好记录。至于虚拟地址对应的代码页是否从外存储(flash,card,硬盘)的程序中加载到内存中还需要要另外的标记,怎么标记呢?就是利用上面所讲的TLB低12位的某一bit(我们称为K)来标识,1标识代码数据已经加载到内存,0表示还没加载到内存。假如是1,那就会用M中的物理地址作为高20bit,以页内偏移0x8作为低12bit,形成一个物理地址,送到内存去访问。此时该次访问就会完成。


2) 假如 K是0,那意味着代码数据尚未加载到内存,这时MMU会向中断管理模块输出信号,触发一个中断进行内核态,由操作系统负责将对应的代码页加载到内存。并修改对应页表项的K比特和TLB对应项的K比特为1.


3) 假如 0x10000008的前20bit在TLB所有索引中都没有命中,则MMU也会向中断管理模块输出一个信号触发中断进入内核态,由操作系统将0x10000008右移12位(即除以4K)到页表中去取得对应的物理页值,假如物理页值非0有效,说明代码已经加载到内存了,这时将页表项的值填入到某一个空闲的TLB项中;假如物理页值为0,说明尚未给这个虚拟页分配实际的物理内存空间,这时会给它分配实际的物理内存,并写好页表的对应项(这时K是0),最后将这索引项写入TLB的其中一条。


2)和3)其实都是在中断内核态中完成的,为什么不一块做了呢?主要是因为一次中断不应该做太多事情,以加大中断延时,影响系统性能。当然如果有芯片将两者做成一个中断也是可以理解的。我们再来看看页表的结构。页表当然也可以按TLB那样做成索引数组,但是这样有两个不好的地方:


1)页表是要映射所有的虚拟页面的,其维护在内存中也需要不小的空间。页大小是4K时,那映射全部就是4G/4K=1M条索引,每条索引4*2=8个字节,就是8M内存。


2)假如按TLB那种结构,那匹配索引的过程就是一个for循环匹配电路,效率很低,要知道我们做这个都是在中断态完成的。


所以一般的页表都是设计成一维数组,即以整个线性虚拟地址空间按页为单位依次作为数组的下标,即页表的第一个字(4字节)就映射虚拟地址空间的最低4K,第二个字映射虚拟地址最低的第二个4K,以此类推,页表的第N个字就映射虚拟地址空间的第N个4K空间,即(N-1)*4K~4KN的地址空间。这样页表的大小就是1M*4=4M字节,而且匹配索引的时候只是一个偏移计算,非常快。


承前启后,在引出第二部分之前先明确两个概念:

1. Bank表示代码分块的意思,类似于上面提到的页的概念。

2.不同代码分时复用内存:不同代码即意味着不同的虚拟地址对应的代码,(程序链接后的地址都是虚拟地址),内存即物理内存,即一定大小的不同虚拟地址的代码在不同的时刻都跑在同一块一定大小的物理内存空间上。每一块不同的代码块即是不同的代码Bank。


二、控制器领域SOC内存管理单元的软、硬件协同设计

这里专指没有内存管理单元的SoC设计,一般为了降低成本,在性能足够时,如果16位或者24位字长CPU能够解决问题,一般都不会去选32位字长的CPU,除非是计算性能考虑,或者32位CPU的license更便宜(一般很少见)。只要能够达到高效地进行内存管理,实现物理内存分时复用的目的,那都可以称为是成功或者有效的。所以在介绍真正的内存管理单元硬件设计之前,我们先简单介绍一种利用工具链来实现内存分时复用的机制。


1、工具链实现的CODE BANKING机制


CODE BANKING机制是由Keil C针对16位8051系列单片机退出的内存管理解决方案,其目的是扩展访问内存地址空间。16位单片机的可访问空间是64K字节,假如程序和数据大于64K(系统和应用稍为复杂点就很有可能啦)怎么办?Keil C推出的解决思路是利用P1口作为地址线的扩展去访问,最大支持2M空间,即32个64K,需要5个P1口线。其译码(即P1口线的高低电平选择)是由keil C的特别编译器在编译时自动产生的。当然,也需要在代码和链接脚本中主动说明某个函数是bank代码,否则所有函数在调用之前都插入一段译码代码,那程序都不用跑了。而且其也有公共代码区,例如中断处理,操作系统常驻段等等,在每个64k中都同样有一份。


我们会问,上面讲的CODE BANKING机制只是扩大可访问的内存地址空间,跟内存分时复用似乎没有关系。其实我们是可以利用这个机制来实现不同代码分时复用的。


如果P1的某条扩展地址线并没有接到实际的物理内存上,即该线无论输出1还是0,这个地址访问的物理内存其实是一样的。例如有个地址是0x10008,假如P1.0这根线没接到内存,那第17bit1其实是没有产生译码作用的,即0x10008访问的是0x00008的内存。也即是两个不同的地址其实对应的是同样的物理空间。所以我们可以将不同的函数编译链接到不同的虚拟地址,但其最终会被加载到同样的物理内存中去执行。这样就实现了不同代码的内存分时复用。


不过这种方法也有一定的缺陷:

1) 最多只有 32个Bank,不能再多了。一般多媒体消费产品系统都难以满足。

2) 应该开发要经常关注调用过程,要经常关注是否调了 Bank代码,调试起来也比较麻烦,16位地址还要结合P1口才能确定真正的物理地址。

当然,CODE BANKING机制的初衷是为了扩大可访问的内存地址空间,而不是给我们这样用的


2. 软、硬件协同设计


终于要进入我们的正题了。其实有了上面的介绍,下面的阐述你会很容易领会。我们会模仿真正的MMU去进行我们的内存管理单元硬件设计,并且考虑相关的细节。


我们要清楚,利用控制器领域的CPU(例如251MIPS M系列等)进行SoC设计,一般都会高度集成,K级别的内存都会使用SRAM技术集成到SoC中。SoC内存管理单元结构图如下:



将这个图和MMU的结构示意图比较,可以发现有以下不同:

1 SRAM中没有页表。

2 Bank号寄存器组可以理解成跟TLB的索引数组

3 Bank内存管理电路跟MMU的管理电路的核心电路应该说是一致的,当然会非常简单,其都是实现一个循环匹配的过程,实际上就是一个选择器电路。


可以说,Bank内存管理电路就是MMU单元的简化版,只是内存中不再存在页表。我们来看看它的工作机制,看在没有页表的情况下如何做到映射。这是由操作系统和应用程序、Bank内存管理单元一块完成的,架构师的责任!这一定会是SOC软硬件协同设计的一个教科书式经典案例!

1 SRAM内存中划分常驻代码数据区域和若干个Bank区域。有同时竞争内存使用的程序模块就使用同一个Bank空间,如两个应用层(音乐和FM)程序约定分时使用某个Bank,而两个存储驱动(如card驱动和flash驱动)约定分时使用另外一个Bank空间。我们把使用某个Bank空间的所有虚拟Bank号称为一个Bank组。所以系统中会存储多个Bank组,而每个Bank组使用的实际物理地址空间是之前约定好的。


2一个虚拟地址分两个部分,一个是 Bank号,另一个是Bank内的偏移。假如CPU字长是24bit,设定Bank号的比特数是8,则虚拟Bank大小就是16bit64K。物理地址也分两个部分,一个是Bank组号(对应物理内存块),另一个是Bank内偏移。


3实际物理 Bank大小是小于虚拟地址Bank大小的,可以是1k2k等等,低16bit的地址都是bank内偏移地址,虚拟和物理内存是一一对应的。物理bank大小由操作系统来约定,应用程序编写模块函数时遵守即可。


4在整个虚拟地址空间中划分不同的 Bank组,就需要设定每一组Bank对应的Bank号的范围。假设有8Bank组,那每个Bank组的Bank号数目就是256/8=32个。


5 Bank号寄存器组和上面设定的Bank组是一一对应的,某个时刻每个Bank组只有一个Bank号写入到对应的Bank号寄存器中。


6通过以上约定和分析,可以知道,对于一个虚拟地址,是可以知道它是属于哪个 Bank组的,而通过Bank组和Bank内偏移是可以确定它在内存中的确切地址,也意味着它实现MMU中的页表的功能。如下图:



假设某个地址,我们来想象一下内存管理单元的工作过程:


1将该地址的高 N比特和Bank号寄存器组进行匹配,如果匹配命中,证明该地址所在的Bank代码已经在内存中,内存管理单元直接转换为物理地址进行访问。


2假设不命中,证明对应的 Bank代码没有在内存中,这时会触发一个异常进入中断,这时操作系统会判断该地址所属哪个Bank组,找出其所对应的文件代码,将其从外存储设备中读到内存,并更换对应的Bank号寄存器。这时返回后再次访问时就不会再次发生中断,而是命中读取。


3提到触发异常,而我们的 Bank内存管理电路是自己设计的,并不是CPU原有的模块,怎么触发异常呢?一种方法是在不命中时由内存管理单元返回一条未知指令,而每种CPU都有一种未知指令异常,这时CPU执行这条指令就会发生异常,进入中断。而因为这条指令是人为增加的,执行后PC值也增加了,在中断管理程序中要将之前保存的PC调整到未知指令执行之前的位置。


另外,架构师还应该按以上机制定义的规则定制好链接脚本,以便应用和驱动人员进行编程时不需要关心底层内存管理的实现。底层对上层透明才是优秀的设计!



来自微信公众号 yueqian嵌入式企鹅圈

0 0
原创粉丝点击