SoC嵌入式软件架构设计

内存是SoC(System on Chip,片上系统)集成设计的重要模块,是SoC中成本比重较大的部分。内存管理的软硬件设计是SoC软件架构设计的重要一环,架构设计师必须要在成本和效率中取得平衡,做到在节省内存的同时保证整个系统的性能。系统内存需求评估是对嵌入式软件架构师的最基本要求,同时也是其最重要的技能之一。一般在SoC项目立项的时候,架构师就要完成系统内存需求评估。

    下面以一个多媒体电子解决方案中的SoC设计为原型,说明大致的评估流程:

    1. 根据产品规格,对各个应用场景进行     产品规格一般会描述应用功能场景和性能。架构师要对各个场景进行功能和性能分解,分析各个场景在内存使用上的关系。包括:

    1)列出所有的应用场景,明确各个应用的生命周期,在什么时候开始,什么时候结束。

    2)系统是否要同时支持多个应用(多进程),例如听歌曲的时候要浏览图片,这意味中两个应用是同时利用内存,不能进行应用内存分时复用;

    3)系统是否要同时支持多种介质,例如同时访问卡设备和闪存设备,一般在单进程时都只是访问单种存储设备,除非是实现数据复制,但在多进程的时候,不同的进程访问不同的存储设备也很正常,同时访问不同的存储设备意味着两种驱动是同时使用内存。

    4)系统是否要同时支持多种文件系统。不同的存储设备可能部署不同的文件系统,其同样存在2)中的问题。

    5)明确系统支持的编解码格式,其表现为算法内存需求。不同的编解码格式对内存的要求不同的,同样的算法时,不同的速率也导致不同的内存需求。

    6)系统性能要求,例如LCD刷屏,有大块framebuffer自然会有更好的性能。 


    2. 对系统软件进行分层,明确每一层模块的组成     

    1)系统分启动、驱动、操作系统、文件系统、中间件(算法、UI)、应用框架、应用等层次,一般的消费类电子产品,如多媒体设备、游戏机等产品系统都会分成多个层次。每个层次又会有多个模块组成,如驱动分字符设备驱动和块设备驱动,一般按键属于字符设备,存储设备一般属于块设备;存储设备里可能支持nand flash、SD-MMC card、Uhost等;文件系统又有FAT32、exfat等等;应用层当然会包括很多个应用程序。

    2)明确每个应用实现所需要的软件层次。有些应用可能要很多层,如音乐,从应用到应用框架(UI+按键)、API、中间件(解码)、操作系统、驱动等层次,而设置应用是不需要解码中间件的。

        3. 明确每个软件层次中内存分时复用的模块,找出最大内存需求的模块

如《节省内存的软件设计技巧》这篇文章提到,应用、驱动、中间件、数据段都存在着复用的需求。要在2)中的各个软件层次中区分各个不同的组成模块,明确各个模块是否能够进行分时复用。在复用的情况下,找出最大内存需求的模块,如nand flash驱动要比card驱动复杂,那nand flash驱动的内存需求自然要高;而音乐应用自然要比设置或者FM等应用要复杂,其内存需求自然也要更多。

    4.对最大内存需求模块的代码进行分析,大致明确其常驻内存代码和分块(bank)管理的代码。

    常驻代码段一般是调用频繁的、性能要求高的代码段,如中断管理、消息管理等。一般应用中大量的代码是可以按需加载执行的,如音乐的音效管理和音量设置这些功能代码并不要求很高的执行性能,其是可以分时加载到内存执行,能够达到内存分时复用的目的。

    5.确定各个软件层次的常驻代码空间和分时复用内存的空间

    在成本的要求下尽可能减少常驻代码空间,会导致代码执行性能的降低,因为bank代码执行前要先进行加载,一般是从nand flash或者card中读取;在成本的要求下我们也想尽可能减少bank代码复用的内存空间,同样会导致bank代码切换频繁而降低性能,因此也不能一味地减少内存,而是仔细分析各个子模块的功能和性能对内存的大致要求。例如两个子模块的函数实现是8k和4k,那我们可以考虑2K的复用空间,即前者分成4个bank,后者2个bank,是否能够达到性能;如果复用空间设置为4K,效率会高一些,但成本会增加;如果设置为1k,那前者就会有8个bank,切换次数过多。

    6.明确可以固化的代码空间

    应用的常驻代码是不能固化的,因为不同的应用都需要常驻代码,即其是变化的,而像操作系统的中断管理、时间管理、任务调度管理等代码一般是不变的,其可以固化到ROM中,这样能达到节省内存的目的。

     7.考虑其他特殊的需求

    通过6,我们可以大致得到整个系统的内存需求。这时要考虑一些特殊的场景的内存需求,看之前制定的内存是否能够满足这个场景。例如启动阶段的内存需求分布,OS引导初始化时的内存要求等。这些并不是产品的规格,同样是架构设计师要考虑的。

     一般会对6中得到的内存再次评估,以进行细微的调整。

程序的大部分代码都可以在必要的时候才加载到内存去执行,运行完后可以被直接丢弃或者被其他代码覆盖。我们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):

SoC嵌入式软件架构设计

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

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

SoC嵌入式软件架构设计

       可以想象,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更便宜(一般很少见)。只要能够达到高效地进行内存管理,实现物理内存分时复用的目的,那都可以称为是成功或者有效的。在介绍真正的内存管理单元硬件设计之前,我们先简单介绍一种利用工具链来实现内存分时复用的机制,然后再结合MMU和这个工具链实现的分块处理方法去设计我们新的内存管理单元,包括其硬件工作机制和软件设计和关键机制。


        由于以下内容涉及到在审专利,经过考虑,暂时将以下内容隐藏,适当时候再公开,抱歉!

×××××××

后记补充

         在集成没有MMU的CPU时,SoC要实现内存管理,需要另外设计一个内存管理模块,实现MMU的核心功能,即代码分页(块)映射的功能,而且需要简化设计以达到最高的效率,同时代码分块需要直接地体现在链接脚本上。为了追求效率,编译链接后的可执行性文件还会被离线解析组织成一个更简化的执行文件,把不需要的段都删除,并将分块代码按逻辑顺序放好,以便于操作系统在必要时更快地加载。当然,操作系统的代码内存管理也需要配合内存管理硬件电路,并能够解析重新打包后的执行程序文件。因此内存管理的实现是需要架构师从软件和硬件上全面考虑,尽可能地在实现核心功能的基础上简化电路和设计,涉及的模块包括:硬件机制设计、物理内存分配、代码分块原则、linker脚本定义、打包执行文件、操作系统定制等等。后续阐述的架构设计将会包括以上内容。

第三:

上一节讲述了在没有MMU的CPU(如80251、MIPS M控制器系列、ARM cortex m系列)上实现虚拟内存管理的集成硬件设计方法,新设计的内存管理管理单元要实现虚拟内存管理还需要操作系统、代码分块(Bank)的支持,详见SoC嵌入式软件架构设计之二:没有MMU的CPU实现虚拟内存管理的设计方法。这里要阐述Bank设计的一些原则。

    Bank设计是为了实现不同时刻运行的Bank(代码块)运行在同一块内存上,所以在运行之前操作系统需要将已存在内存的代码/数据进行缓存处理,并加载将要运行的Bank到该内存上。为了实现这个目的,需要明确以下要点:

     1.为了提高效率,我们认为代码是不会自修改的,即代码是只读的,则在Bank切换的时候可以直接将已经存在内存的Bank代码丢弃。我们只需要将当前已经存在内存的Bank代码的Bank号入栈即可,新加载的代码可以直接覆盖该块内存。不同的Bank有不同的虚拟地址,为什么可以放到同样的物理内存实是新设计的内存管理单元的电路决定的。参考前一节的文章(SoC嵌入式软件架构设计之二:没有MMU的CPU实现虚拟内存管理的设计方法)介绍,关键是同一个Bank组的不同虚拟地址信号对应的物理输出信号是一样的。

     2.程序调用后返回到一个Bank的某一行时同样需要加载该Bank代码,这时操作系统会将之前的Bank号出栈,并根据Bank号将对应的代码加载到该块内存。从1和2来看,调用Bank代码和返回一个Bank设计到Bank号的入栈和出栈,如果设计的Bank代码中的函数的虚拟运行地址带有明确的Bank号信息,那函数的调用和返回就是一个入栈和出栈过程,这样操作系统可以减少出入栈的工作,代码运行也更顺畅。

     3.Bank代码中的变量数据处理:

     1)全局变量。如果全局变量定义在公共区域,那Bank代码切换过程中不需对其进行处理。如果全局变量定义在Bank内存区域,则Bank切换时需要对这部分全局变量进行缓存处理。即在Bank号入栈之后,将Bank中的数据存到堆中,在Bank返回时除了从外存储设备加载对应的代码时,还要将其对应的数据从堆中恢复到Bank内存。为了加快数据的恢复,往往默认一个Bank数据空间的最大值,这样就不需要记录每个Bank的数据空间的大小。

     2)静态变量。跟全局变量一样。

     3)常量段。其是只读,跟代码一块处理。

     4)局部变量。局部变量是在栈中分配空间的,所以不需要进行缓存。

     5)buffer。假如该Buffer只是某个Bank调用,而该Bank除了代码还有剩余空间大于buffer大小,那将buffer设置在代码段之后,并定义一个指针局部变量,程序中直接指向该buffer的首地址。

     如果我们将Bank内的全局变量全部转为局部变量,那操作系统就不需要对数据进行缓存管理,就不需要堆空间。但是局部变量对应的栈空间就加大了。一个Bank可能有多个函数,而多个函数是可能会用到同样的全局变量的。但这种情况需要的全局变量往往不大,可以考虑都转为局部变量。如果不需要进行数据缓存,那系统管理将会非常简单。

     4.中断处理不能进行Bank切换。Bank切换需要进行读写外存储设备,会造成很大的延时,所以在中断里面不应该产生Bank切换。

     5.操作系统、驱动、应用各层次频繁调用的代码应设置为常驻代码,如果发生切换会损失效率。如果频繁调用的代码很固定,如操作系统的调度管理等代码可以固化到ROM中,以减少成本。

     6.Bank内存分块大小要适中,在保持切换性能的基础上选择较小的内存块。Bank块设置过小,就会导致Bank切换频繁,损失效率,Bank设置过大会造成内存浪费。

     7.Bank内存的起始地址应该对齐扇区(512字节),这样读外存储设备能够达到最好的性能。

本文继续阐述基于低端控制器CPU的SoC固件架构设计。第一节 SoC嵌入式软件架构设计之一:系统内存需求评估 讲述了系统内存需求的评估。这一节讲述内存空间的具体规划分配。CPU有两种体系结构:哈佛结构和冯诺依曼结构。哈佛结构是一种将程序指令存储和数据存储分开的存储器结构,如80251,代码空间与数据空间完全分开,独立编址;冯诺依曼结构是一种将程序指令存储器和数据存储器合并在一起的存储器结构,如MIPS,ARM等,其代码和数据空间是统一编址。这里就以冯诺依曼体系结构为例。 

     一、嵌入式系统软件分层

     系统软件层次包括:启动、驱动、操作系统、文件系统、libc、中间件、应用框架、应用等层次。

     1)驱动、文件系统和操作系统的时间管理、中断管理等接口一般都是通过API来进行调用;

     2)libc和中间件、应用框架在系统中的处理可能以API的形式进行调用,也可以直接作为静态库与应用直接进行链接。

     3)libc和中间件、应用框架作为静态库时,会减少API的占用空间(API往往是常驻空间,没理由调用API时还要从外存储中将API的代码加载到内存,这样效率太低),省去API层也可以提高调用速度,但会增加库函数的代码空间。如果库函数链接时可以运行在Bank内存中,由于Bank内存可以复用,增加的代码空间可以忽略,从这一点来看其又是一个优点。如判断某个文件是哪种解码格式时,其可以作为中间件来实现,并链接到应用的Bank空间,因为这是音乐解码前的预处理,可以和解码时刻的控制流复用同一块Bank空间。

     4)libc和中间件、应用框架以API形式来调用时,会产生API的常驻内存空间需求,在内存中也只存在一份真正的代码,供所有模块共同调用,而且应用开发者无需关心接口实现,也不允许开发者去修改。

     各个模块应根据实际情况来决定其供上层调用的形式。

     代码分页(块,Bank)设计请参考: SoC嵌入式软件架构设计之二:没有MMU的CPU实现虚拟内存管理的设计方法 和 SoC嵌入式软件架构设计之三:代码分块(Bank)设计原则。

     二、程序段组成

     这里程序段是指可执行文件中出现的段名,如.CODE、.DATA、.BSS等默认段名和其他自定义的段名。GNU工具链,各种编译输出段名称是可以在链接脚本中指定的,当然在编写代码时也可以指定函数或者代码的编译输出段名称,如在定义一个数据变量时添加一个属性__attribute__((section(“bank_data”)))时,该数据变量将会被重定位在bank_data段。下图是具有Bank代码段的程序与可执行文件段名的对应关系图:

SoC嵌入式软件架构设计

             三、SoC内置内存规划

         一般地,如果SOC中内置SRAM超过32K,数字工程师也会将内置内存进行分块,一是为了减少电路延时,二是为了让内存得到更有效率的利用。如某块内存在某个时刻是作为代码使用,有时也可能作为数据使用(如果是哈佛结构,那就要切换内存的选址译码电路,从代码空间转到数据空间),有时也可能用作特别的解码buffer使用,而有些解码的缓存是以24bit作为单位,如果所有内存都作为一块来设计,显然是满足不了这样的需求的。下图是常见的SRAM示意图:

SoC嵌入式软件架构设计

                 四、程序内存空间分配

            根据软件分层和程序段综合考虑,一般在物理内存的基础上先进行分层划分内存区域,再进行各层程序的段内存划分。有以下原则:

            1)各层的常驻段(代码和数据)应该紧凑分配,而各层的Bank空间与常驻空间分块,也应该紧凑分配。

            2)Bank空间的起始地址应该与扇区单位对齐,可取得最好的加载代码性能。

            3)先把常见的场景的内存分配好,再考虑特殊场景的需求,看看特殊场景能否复用普通场景的内存空间。

来源:Engineer-Bruce_Yang

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2016年1月23日
下一篇 2016年1月23日

相关推荐