随着应用需求的不断扩展和软件技术的不断进步,人们对于处理器的能力要求越来越高,而单核处理器在现有半导体制造工艺条件下,仅仅依靠提升处理器主频来提升性能已经到达物理极限,难以继续维持“摩尔”定律。因此,多核处理器架构开始成为芯片设计厂商快速提升处理器性能的有效途径[1]。
多核处理器这一硬件平台本身并不能实现性能的倍增效益,它只有与上层应用及操作系统进行密切配合,才能充分发挥多核的优势。在多核处理器盛行的时代,现有软件如何从传统的单核模式向多核模式转变,已成为大家关注的热点。在这样的背景下,对目前性能卓越的单核强实时操作系统进行多核扩展,使其支持目前多核处理器平台是非常有必要的,而且也有相当的实用价值。
uC/OS-II是一种可移植、可裁剪及可抢占型多任务实时操作系统[2],该系统目前只支持单核。SBC8641D[3]开发板是由两个E600[4]内核组成的双核开发板。为了能利用双核处理器的硬件优势,本文以双核开发板SBC8641D为平台,对uC/OS-II做单核移植,再做多核扩展,让uC/OS-II可以充分利用多核处理器的硬件优势显著地提升系统的性能。
MPC8641D处理器构架
飞思卡尔半导体公司的MPC8641D[5]是一款高性能的面向嵌入式应用的双内核处理器,该处理器集成了PowerPC构架的两个E600内核,每个E600内核的性能都可以达到1.5GHz。两个内核各自配备了32KB的一级指令Cache和数据Cache,并且各自独享1MB的二级缓存,两个核通过MPX总线互联,MPX总线的主频最高可达667M。构架如图1所示。
为了实现uC/OS-II在SBC8641D的多核扩展,首先需要做uC/OS-II移植。
单核uC/OS-II在SBC8641D上的移植
对于uC/OS-II操作系统内核的移植[6],主要分如下三步进行:
第一步实现上下文切换。确保任务之间可以根据内核调度策略进行基本的任务切换,实现多任务并发执行。
第二步实现中断框架挂接。给uC/OS操作系统加入中断功能,使SBC864D开发板的中断控制器OpenPIC可以接收外部产生的中断信号。
第三步实现时钟中断。即给操作系统加入心跳,让操作系统产生时钟滴答,任务调度程序要按照时钟滴答的节奏运行,下面介绍其实现过程。
上下文切换实现
上下文切换的原理是当发生任务切换的时候,保存当前任务的寄存器值到当前任务的堆栈中,将下一个即将要切换过来的任务堆栈中的寄存器值恢复到当前CPU寄存器中,使其恢复执行。
MPC8641D处理器使用PowerPC e600内核,任务的上下文切换需要保存和恢复32个通用寄存器GRR0至GPR31、6个特殊寄存器:链接器LR、异常寄存器XER、计数寄存器CTR、条件寄存器CR以及两个状态保存/恢复寄存器SRR0和SRR1。当发生任务切换时将上述38个寄存器的值依次保存到上一个任务的堆栈中;将下一个任务堆栈中的38个寄存器的值依次恢复到e600内核的38寄存器中。为简化起见,上下文切换不考虑浮点寄存器的保存和恢复。进程堆栈中CPU寄存器值的分布如图2所示。
中断挂接实现
MPC8641D处理器集成了一个OpenPIC可编程中断控制器,通过配置OpenPIC中断控制寄存器可以设置各类中断向量号,MPC8641D响应所有外部中断的偏移地址是0x500。所以需要在0x500处写入一段通用的程序,该程序读取发生中断的中断向量号,然后根据中断向量号确定需要执行的中断处理器程序,跳转到中断处理程序中运行。
时钟中断实现
在uC/OS-II中时钟中断处理函数检查是否有由于延时而被挂起的任务成为就绪任务,如果有则把该任务放入就绪队列中。如果该任务比正在运行的任务具有更高优先级,则执行调度器进行任务切换来运行这个更高优先级的任务。MPC8641D中响应时钟中断的偏移地址是0x900,所以我们只需在偏移地址0x900处写入一条跳转指令,跳入uC/OS-II的时钟中断处理函数中执行即可。实现了上下文切换、中断挂接以及时钟中断,uC/OS-II就可以在SBC8641D上的一个处理器核上运行起来,另外一个处理器核未启用。
uC/OS-II多核扩展
uC/OS-II启动过程多核扩展
MPC8641D处理器有两个e600内核,我们把uC/OS-II运行的处理器核称为主核,另外一个处理器核称为从核。uC/OS-II在主核上完成系统的初始化。主核首先初始化内部寄存器和相关硬件环境,然后跳转到c_main()函数中调初始化MMU、串口及时钟,最后启动初始任务,由初始任务向从核发送启动信号,从核获得启动信号后会初始化自身内部寄存器,然后跳转到入口函数secondary_start()中。secondary_start()对从核的MMU寄存器进行初始化,使从核和主核可以访问所有内存资源,最后从核运行绑定在该核上的idle任务,等待主核上uC/OS-II发出任务调度请求。整个启动过程如图3所示。
扩展之后的uC/OS-II用主核初始化硬件环境并启动系统,从核享受主核的劳动成果[7]。我们实现了主核上uC/OS-II的初始化和从核的启动之后,接下来要讨论如何实现主核和从核的通信、以及同步和互斥。
主核和从核的通信及同步互斥机制设计
通过MPC8641D处理器中OpenPIC可编程中断控制器来实现主核和从核的通信,通过配置OpenPIC的4组核间中断寄存器实现了4个核间中断接口函数:
send_IPI_self(int vector):发送IPI给自己,中断号由vector指定;
send_IPI_mask(int mask,int vector):发送IPI给某一个或某几个CPU,发送目标由掩码mask决定,中断号由vector决定;
send_IPI_all(int vector):发送IPI给所有CPU,中断号由vector指定;
send_IPI_allbutself(int vector):发送IPI给除自己以外的所有CPU,中断号由vector指定。
为了能使用全局变量实现两个处理器核的通信,我们可以通过配置MPC8641D中两个E600内核的HID1寄存器开启MESI协议[8] ,保证内存和Cache的一致性。 MESI协议是目前最流行的基于总线监听的Cache共享一致性协议,开启MESI协议后硬件将自动保证内存和Cache的一致性。
我们使用自旋锁来实现主核和从核的同步与互斥,自旋锁(spinlock)是采用忙等的锁机制,它不同于信号号及其它阻塞的锁机制,处理器核在获取自旋锁锁的过程中,会一直测试锁的状态,直到其可用为止,即自旋锁在锁不可用时是不会主动放弃CPU的,因此,自旋锁只能用于多核平台。其实现的代码如下:
void arch_spin_lock(arch_spinlock_t *lock)
{
while (1) {
if (__arch_spin_trylock(lock) == 0)
break;
do {
} while(lock->slock != 0);
}
}
void arch_spin_unlock(arch_spinlock_t *lock)
{
lock->slock = 0;
}
其中__arch_spin_trylock(lock)函数是一个原子操作,它测试lock是否被上锁,如果未上锁则返回原值0,然后把lock置为1;如果已上锁,则返回1。
上述自旋锁并没有关中断功能,只有在当前核上的中断处理程序从不访问该自旋锁保护的临界区时才会起作用。为了增加通用性,我们在上述自旋锁的基础上加上关中断的功能,这样改进之后的自旋锁不但可以实现不同核上任务之间的互斥,还可以实现当前核上任务与当前核上中断处理程序之间的互斥,需要注意的是关中断操作要放在申请自旋锁之前,开中断操作要放在释放自旋锁之后,否则会出现死锁。
任务调度模型设计
为了能让从核参与任务调度,我们必须对uC/OS-II的调度器进行修改,让主核和从核都可以挑选全局调度队列中优先级最高的两个任务来运行。为了保证最少的任务切换,如果当前处理器核运行的任务包含在优先级最高的两个任务之中,该处理器核不进行任务切换。在系统的实现上我们扩展参与任务调度的四个变量OSPrioCur、OSPrioHighRdy、OSTCBCur和OSTCBHighRdy为长度为2的数组[9]。uC/OS-II中有两处调度点:一处是中断返回调用OSIntExit()函数;另一处是任务切换调用OSSched()。我们在这两个函数中添加多任务切换模块,该模块的功能是获取最高优先级的2个任务,然后检查当前运行的任务是否在这2个任务之中,如果不在的话就进行任务切换。
多任务切换模块的执行流程如图4所示。
图4中的OSPrioCur[2]和OSPrioHighRdy[2]数组分别保存正在两个处理器核上运行的任务优先级,以及全局调度队列中最高的两个任务优先级。上述流程图执行的目的是确保两个处理核上运行的任务是最高优先级的两个任务。
上述两处调度点均由主核负责,当主核检测到从核需要任务切换时就会向从核发送核间中断,从核通过响应核间中断切换到更高优先级的任务。由于从核进行任务切换时会涉及到对全局调度队列的修改,所以我们必须用自旋锁来对全局调度队列进行保护,确保两个处理器核对全局调度队列的互斥访问。
效果测试与分析
我们分别建立了4至12个任务,对比了开启从核和关闭从核时所有任务的总耗时,测试结果如表1所示。
从上表中我们可以看出开启AP核后,所有任务的完成时间提升约50%,这是因为开启AP核使得调度队列中同时可以有两个任务在两个核心上并行运行,降低了BP核的负载,增加了系统的吞吐量。
但是系统性能并没有达到理想环境下的100%的提升,主要有两个方面的原因:一方面,系统增加了对从核的支持,必然导致两个处理器对全局数据结构的竞争(例如对全局调度队列的竞争),这会抵消任务的并行运行所带来的性能收益;另一方面,由于我们的任务调度算法出于实时性的考虑,是严格按照优先级进行调度的,而在多核处理器MPC8641D构架中,处理器核都有自身32KB的一级指令Cache和数据Cache,并且各自独享1MB的二级缓存。作为任务来说,由一个处理器核切换到另外一个处理器核,这会造成大量的缓存不命中,如果再需要把原来的处理器核的缓存冲刷入内存的话,这样的代价会更大[10]。这势必会影响到任务的响应时间和系统的实时性。综合这两个方面的因素,开启从核后,系统性能并不能有100%的提升。
本文针对uC/OS-II不能支持多核处理器的问题,研究uC/OS-II的内核结构,对其进行多核扩展,使得扩展之后的uC/OS-II实现多任务并行运行,为单核实时操作系统实现对多核处理器的支持提供了一条可行的解决方案。
后续工作将进一步研究并优化支持多核的任务调度算法,由于我们目前设计的任务调度算法是严格按照优先级进行调度,并没有考虑到Cache的热度,这影响到了系统的性能;另外如何定量的测定并分析多核扩展之后的系统实时性能,也是一个需要进一步研究的工作。
参考文献
- 黄国睿,张平,魏广博,多核处理器的关键技术及其发展趋势[M],计算机工程与设计, 2009
- [美]Jean J. Labrosse(著),邵贝贝(译).嵌入式实时操作系统uC/OS-II(第二版)[M]. 北京航空航天大学出版社,2003年
- Freescale Corporation.e600 PowerPC Core Reference Manual[M].Rev.0,03/2006
- Wind River SBC8641D Engineering Reference Guide ERG-R0331-001[M] Revision B,2007
- Freescale Corporation. MPC8641D Integrated Host Processor Family Reference Manual[M]. MPC8641DRM.Rev.2,07/2008:P107-P109
- 楚红雨,李磊民,黄玉清.实时操作系统uC/OS-II在ARM9 上移植的实现[J].计算机工程,2005年.
- 胡希明,毛德操.Linux内核代码情景分析(下)[M].浙江大学出版社,2001:P1436
- 王齐著.Linux PowerPC详解——核心篇[M].机械工业出版,2008:
- 章承科,基于多核处理器的实时操作系统的扩展[D] .成都电子科技大学,2006
- Curt Schimmel(美).现代体系结构上的UNIX系统一内核程序员的SMP和Caching技术[M]张辉译,北京人民邮电出版社,2003.
评论