Vx Works的内存配置和管理

2012-04-25 05:52:02殷战宁
舰船电子对抗 2012年3期
关键词:堆栈寄存器内存

殷战宁,刘 琳

(中国电子科技集团公司51所,上海 201802)

0 引 言

对于一般编程人员尤其是Windows等通用平台的编程人员来说,内存配置基本上只要考虑全局变量、局部变量(堆栈变量)的合理安排,内存管理基本上只是使用 Malloc、Free等编程接口。但对于Vx Works嵌入式操作系统而言,由于内存芯片选型、内存的地址空间分配[1]、中央处理器(CPU)级内存管理硬件初始化、映像文件的地址定位、内存的静态和动态占用、内存碎片对系统稳定性的影响等诸多内容都由设计人员来规划和控制,所以涉及内容广泛,需要有一个较好的通篇认识才能解决好与内存有关的问题。

1 软硬件配置

1.1 CPU和内存芯片选型

内存需要相关的内存控制器来操作,以便能保证内存访问的时序、刷新等操作。内存控制器有些CPU自带,如 MPC5200带双倍速率(DDR)控制器、同步动态随机存储(SDRAM)控制器,PPC860通过用户可编程机制(UPM)来控制随机存储器(RAM)、S3c2410带SDRAM控制器等;有些CPU不带内存控制器,如X86类型,需要桥片来对内存进行时序控制。

对内存芯片进行选型,首先要弄清楚内存控制器的特性,如支持的芯片类型、配置参数的标度方法等,然后选取类型无误、数据手册方便进行参数转换或计算的内存,合适的选型可以降低参数配置的难度,提高内存的访问效率。

由于内存参数配置是主板上电后立即就要做的事情,所以,顺利与否以及稳定与否,极大地影响随后的代码重定位等工作;通过硬件仿真器来调试主板,工作也往往是从配置和测试内存开始的。摘录MPC5200内存配置寄存器2的部分位定义,如图1所示。

可以看到该寄存器的设置牵涉到tRCD、tRP、tRFC等内存片的参数。内存片MT48LC32M8A2的数据手册如表1所示。

表1 MT48LC32M8A2内存数据表

图1 MPC5200配置寄存器2的部分定义

可以看到CPU寄存器配置值的参数很方便就从内存手册里查到。如果选型不合适,则需要进行参数的换算,参数的具体关系可以参看专业介绍内存时序的资料。

这一步工作结果将具体反映到板级支持包(BSP)的 RomInit.s文件中,如 MPC5200的 BSP里面,由RomInitSdram函数来实现内存配置,其重要配置就有:

完成配置后,再通过内存控制器的Command寄存器等启动Refresh,使能操作。

X86的Vx Works由于有基本输入输出系统(BIOS)完成了内存的配置,所以在RomInit.s里面看不到类似的过程,其它类型CPU的主板基本上都需要该操作。

1.2 CPU和内存地址分配

内存的地址对不同的CPU可以有不同的分配方法,对于X86体系,内存一般从地址0开始,跳过A、B段(显示内存),C、D段(卡式设备内存空间),E、F段(BIOS空间)后,继续从0x100000连续分配。对于CPU命令架构()类CPU,则一般有CSx类寄存器,可以配置与某个CS引脚对应的地址范围和操作宽度等,一般也配置成从0开始;对于增强的精简指令集机制(ARM)类CPU,做法会有许多种,需要查询具体的芯片手册,如S3c2410,则固定CS6和CS7用作SDRAM的片选,地址范围固定从0x30000000开始。

此类寄存器的设置也需要在RomInit.s里面设置好。

1.3 CPU级内存管理硬件初始化

CPU一般都有存储器管理单元(MMU)、块地址翻译(BAT)等硬件机制来对内存空间分段或分页管理,对不同的段页配置不同的虚实地址对应关系、读写属性、Cache属性、保护属性等。这部分操作Vx Works开发人员无需直接访问寄存器,只需填写用来配置MMU或BAT的结构数组,如MMU配置:

该配置器就描述了一段内存的虚地址、实地址、大小、属性使能、属性等;主内存地址空间一般都配置为不做虚实转换、可写、可Cache等。该表项会由Usr MmuInit函数里面读取并配置到MMU,而且也可以通过Sys Mmu Map Add等维护函数做增删。

BAT表一般用于CPU命令架构(PPC)类寄存器,可以定义代码段、数据段、段大小、Cache属性等,可以与MMU组合使用,也可以单独使用,相关的BSP也提供结构数组进行维护。

MMU和BAT表项一般在BSP的SysLib.c文件里面。

对于X86类CPU,还有全局描述符表(GDT)、中断描述符表(IDT)等内存描述符需要初始化,内存描述符规定了一段空间的大小和属性,组成表格由段寄存器来选取,段寄存器对地址选址的计算实际上是通过内存描述符翻译后进行的。Vx Works的BSP简化了该操作,几个段寄存器都使用一个能覆盖所有地址空间的全功能的GDT进行工作。

对于Cache使能否,一般还会有针对所有的Cache、代码 Cache、数据 Cache、一级 Cache、二级Cache等不同的硬件开关,可能会在RomInit.s,Sys Alib.s通过汇编来操作,也可以在UsrInit,Usr-Root函数里面通过CacheEnable等函数来操作,需要检查确认。

1.4 映像文件的地址定位

一般而言,CPU上电后先是在只读存储器(ROM)(或Flash等)内运行,为了提高运行速度,往往需要把代码搬移到内存去继续执行,数据相关段(Data、没有初值的全局变量符号(BSS)段)则需指向内存区域并得到正确的初始化,堆栈段也需要指向内存,段与段之间需要满足一定的定位关系,比如不互相重叠等。

以最常用的bootrom_uncmp+ Vx Works启动组合为例:

(1)上电后执行在只读存储器(ROM)里面的bootrom_uncmp映像,初始化CPU、内存控制器等,堆栈底设置在宏RAM_HIGH_ADRS决定的位置(栈顶朝地址低端)。

(2)ROM 里 面 的 程 序 会 把 ROM 里 面 的bootrom_uncmp映像拷贝到起始位置是RAM_HIGH_ADRS的内存区域,然后跳转到内存来继续执行。此时代码段在RAM_HIGH_ADRS位置,数据段紧跟在代码段后面,BSS段紧跟在数据段后面,BSS段后面是中断使用的堆栈,然后是bootrom_uncmp将要使用的内存池,任务堆栈段会从内存池申请。所以,要想让bootrom_uncmp正常执行,需要确保RAM_HIGH_ADRS下面有足够的空间够ROM做搬移时的堆栈,RAM_HIGH_ADRS上面有足够空间存放代码、数据和用来做内存申请。

(3)内存里面bootrom_uncmp的执行会下载Vx Works,把Vx Works映像拷贝到起始位置是RAM_LOW_ADRS的内存区域,然后跳到该内存继续执行。为了确保bootrom_uncmp拷贝 Vx-Works期间不会发生Vx Works覆盖bootrom_uncmp的现象,“RAM_LOW_ADRS+Vx Works代码段大小+Vx Works数据段大小”所占的空间要尽量避免与bootrom_uncmp需要使用的空间重叠。

(4)Vx Works获得运行经过再次初始化后,代码段在RAM_LOW_ADRS位置,数据段紧跟在代码段后面,BSS段紧跟在数据段后面,BSS段后面是中断使用的堆栈,然后是WDB专用的内存池,然后是Vx Works将要使用的内存池,堆栈会先设置在RAM_LOW_ADRS开始朝下生长以满足usr Root启动前的使用,然后会设置到靠近系统内存顶端以满足usr Root的使用并利于回收到内存池,以后的任务堆栈就是从内存池里面申请。

可以看到RAM_HIGH_ADRS和RAM_LOW_ADRS 2个宏有着重要的定位意义,尤其是RAM_LOW_ADRS很大程度上决定了Vx Works的内存布局,需要斟酌一个好的位置以便既保证正常启动、又不至于造成内存浪费(太高了会使内存池缩小)。因为既影响代码,也影响链接,所以这两个宏的修改需要在config.h里面和makefile里面同时修改并确保一致。

其它类型组合,如bootrom、bootrom_res、Vx-Works_rom等,结合编译链接时产生的map文件或符号表文件,同样可以做出类似的分析。

根据地址定位关系可以大概知道内存池的起始位置,通过map文件或符号表文件来看,一般是BSS段的结束 +ISR_STACK_SIZE + WDB_STACK_SIZE,而内存池的结束位置由Sys Mem-Top函数决定,一般来说是 LOCAL_MEM_LOCAL_ADRS+LOCAL_MEM_SIZE- USER_RESERVED_MEM。

2 接口函数

接口函数主要是指创建内存池、动态申请和释放内存的函数,但需要额外说明的是,当书写C/C++源码时,如果定义了一个全局变量(指函数体外的变量)并赋予了初值,则该变量会静态占用数据段的空间,如果定义了一个全局变量但没有赋予初值,则该变量会静态占用BSS段的空间,如果定义了一个函数体内的变量,则该变量会动态占用堆栈空间,这些编程实际上是在隐蔽地申请(或释放)内存。下面列举常用的针对内存池的接口函数。

2.1 第1类函数

第1类函数是主内存的参数设置和使用:

设置内存池的属性,包括:

MEM_ALLOC_ERROR_LOG_FLAG

当内存分配出错则打出log信息:

MEM_ALLOC_ERROR_SUSPEND_FLAG

当任务内存分配出错,则把任务挂起(除非任务设置了VX_UNBREAKABLE属性):

MEM_BLOCK_ERROR_LOG_FLAG

当内存释放出错则打出log信息:

MEM_BLOCK_ERROR_SUSPEND_FLAG

当任务内存释放出错,则把任务挂起(除非任务设置了VX_UNBREAKABLE属性):

申请大小为nBytes的内存,返回该内存的ptr(其实就是该段内存的起始地址):

释放已申请的一段内存,传入该段内存的ptr(起始地址)作为参数:

申请elemSize*elem Num大小的内存,该段内存会被清0:

申请size的内存,内存起始地址满足alignment的要求。

2.2 第2类函数

第2类函数是单独的内存池的使用,实际上,主内存池也是一个单独的内存池,其指针是全局变量

创建一个新的内存池,起始地址是pPool,大小是poolSize,该段内存可以是原来系统内存池之外的某一段离散的内存,也可以是从系统内存池里面申请到的一段内存。建立了单独的内存池,可以在该段内存里面单独申请和释放内存,而不影响其它的内存。返回值是内存池指针:

内存池partId的参数配值,含义同 memOptionsSet:

在partId的内存池里面申请nBytes的内存:

释放已申请的pBlock内存回partId的内存池:

在partId里面申请nBytes的内存,内存起始地址满足alignment的要求。

2.3 第3类函数

第3类函数是内存池的维护函数:增加一段新的内存给主内存池:

增加一段新的内存给内存池partId:

察看主内存池的信息列表:

察看内存池partId的信息列表。

3 应用优化

Vx Works申请内存时使用空间首先满足的算法,找到合适的块,多出来的部分会单独形成一个空块,释放内存时会进行相邻空闲内存块的归并,却不会做碎片搬移和整理。因此动态内存虽然使用方便,但大量的小内存操作偶尔再穿插大内存的操作会造成内存池的碎片,最终没有足够的内存使用。而且动态申请和释放会带来时间上的损失。所以,在应用层,需要考虑静态和动态的平衡,考虑到动态情况下大量相同内存操作的优化。

频繁申请和释放的内存建议改成静态的方式,以避免时间上的损失,如果不想使用全局数组或结构这样的方式,也可以使用动态方式申请下来一块内存,然后进行强制类型转换。

如果跟任务动态运行有关,可以考虑放在任务的函数体内,成为堆栈变量(任务的堆栈大小在创建任务时确定,如果担心堆栈紧张,可以考虑对较大的变量只是把指针放在堆栈里面,而指针所指的内存则动态申请),不仅是动态的,而且了实现了任务与任务的隔离。

Vx Works上的网卡驱动就是采用这样的方式来管理接收和发送缓冲:通过动态方式申请下来一块内存,建立不同大小的cluster的数组,并为cluster设置管理属性,然后建立申请和释放cluster的函数。

动态的大量相同内存操作建议自己建立一套机制来管理,如通过message queue的协助来管理。

4 结束语

Vx Works是一个实时嵌入式操作系统,所以从嵌入式的角度来说,内存配置工作是必不可少的。从实时的角度来说,掌握了通用的内存管理函数后,还需要进一步了解这些函数对实时性和安全性的影响,从而规划一个比较稳健的内存管理系统。

[1]王金刚,高伟,苏琪.Vx Work程序员指南[M].北京:清华大学出版社,2003.

[2]周户平,张杨.Vx Work程序员速查手册[M].北京:机械工业出版社,2005.

猜你喜欢
堆栈寄存器内存
Lite寄存器模型的设计与实现
计算机应用(2020年5期)2020-06-07 07:06:44
“春夏秋冬”的内存
当代陕西(2019年13期)2019-08-20 03:54:22
嵌入式软件堆栈溢出的动态检测方案设计*
分簇结构向量寄存器分配策略研究*
基于堆栈自编码降维的武器装备体系效能预测
基于内存的地理信息访问技术
高速数模转换器AD9779/AD9788的应用
一种用于分析MCS-51目标码堆栈深度的方法
一种可重构线性反馈移位寄存器设计
通信技术(2010年8期)2010-08-06 09:29:16
上网本为什么只有1GB?