Linux暴风雨般地占领了嵌入式系统市场。根据工业分析家分析,大约1/3到1/2的新的32位和64位嵌入式系统设计采用了Linux。嵌入式Linux已经在很多应用领域显示出优势,比如SOHO家庭网络和成像/多功能外设,并在以下几方面具备巨大的跨越式发展前景:(NAS/SAN)存储,家庭数字娱乐(HDTV/PVR/DVR/STB)和手持设备/无线设备,特别是数字移动电话。
新的嵌入式Linux应用不会象掌握在智慧和工艺之神-罗神手中那样,会突然从开发者的头脑中爆发出来。大量的项目必须采用数千行的,甚至数百万行的过去的现成代码。成百上千的嵌入式项目已经成功地将其它平台的现成代码移植到Linux之上,比如WindRiverVxWorks和pSOS,VRTX,Nucleus和其它RTOS,这些移植工作现在仍然有价值和现实意义。
到目前为止,大多数的关于移植旧的RTOS应用到嵌入式Linux的文献,已经在关注RTOS接口(API),任务,调度模式和怎样将他们映射到相应的用户空间去。在嵌入式程序的密集I/O空间中,同样重要的是,将RTOS的应用硬件接口代码向具有更加规范化模式的Linux设备启动程序的移植。
本文将纵览几种常用的内存映射I/O方法,它们经常出现于旧的嵌入式应用中。它们涵盖的范围,包括从对中断服务例程的特殊使用和用户线程对硬件访问,到出现于有些ROTS中的半规范化驱动程序模型。它对于移植RTOS代码到规范化模式的Linux设备启动程序具有启发性,并且介绍了一些方法。特别地,本文会重点讨论和比较RTOS代码中的内存映射,Linux基于I/O调度队列的移植,和重新定义RTOSI/O,以便在本地Linux驱动程序和守护进程里应用。
RTOSI/O概念
“不规范”是能够描述大多数在基于RTOS系统里的I/O的最佳词语。大多数RTOS针对较早的无MMU的CPU而设计,忽略了内存管理,即使当MMU问世也是这样,不区分物理地址和逻辑地址。大多数RTOS还全部在特权态(系统模式)运行,表面上看增强了性能。像这样,全部的RTOS应用和系统代码都能够访问整个机器地址空间,内存映射设备和I/O指令。实际上,将RTOS应用程序代码同驱动程序代码区分开非常困难,即使它们是有差别的。
这个不规范的结构导致了I/O的特殊实现。在很多情况下,完全缺乏对一种设备驱动程序模型的认同。根据这种工作的平等和没有分层的特性,回顾在基于RTOS软件中使用的一些重要概念和实践非常有指导意义。
在线内存映射访问
当在上个世纪八十年代中期商业化的RTOS产品可以买到的时候,大多数嵌入式软件包含巨大的主循环,主循环带有针对严格时间操作的注册I/O和中断服务例程。开发人员将RTOS和执行程序设计进他们的项目,主要为了加强同时性和帮助多任务同步,但是避开其它任何有“妨碍“的构造。同样地,即使一个RTOS提供了I/O调用形式方法,嵌入式程序员继续使用直接的I/O操作:
#defineDATA_REGISTER0xF00000F5
chargetchar(void){
return(*((char*)DATA_REGISTER));/*readfromport*/
}
voidputchar(charc){
*((char*)DATA_REGISTER)=c;/*writetoport*/
}
多数受过训练的开发者常常将这样的直接I/O代码从硬件代码独立分离开。但是我还曾遇见大量的意大利面条式的I/O处理代码。
当普遍深入使用直接内存映射I/O的时候,对Linux开始接触的嵌入式开发人员总是面临将所有的这类代码移植到用户空间,将定义寄存器地址的#define语句转换成mmap()调用。这种处理方法对于一些种类的原型很好,但是不能支持中断处理,限制了实时响应,特别不安全,不适合作为商业发布。
RTOS中断服务例程
在Linux中,中断服务专属于内核的范围。在一个RTOS中,中断服务例程代码是自由形态而且与应用程序代码没有区别(不外乎返回序列)。很多RTOS提供系统调用或者宏,来让代码自己检测它自己的切换点(比如WindRiverVxWorks的intContext())。中断服务例程通常也使用标准的库函数,伴随着可重入性和可移植性问题。大多数RTOS支持注册中断服务例程代码,中断仲裁句柄和中断服务例程调度。一些非常原始的嵌入式执行程序,仅仅支持在硬件矢量表里插入中断服务例程的开始地址。即使你试图直接在用户程序空间执行读和写的操作,你不得不将你的Linux中断服务例程放入内核程序空间。
RTOSI/O子系统
大多数RTOS会提供一个定制的标准C运行库(比如pSOS的pREPC),或者可以从独立软件开发商的编译器中选择打补丁的C库(libc)同样可以得到glibc。这样,在最小化情况下,多数的RTOS支持标准C类型I/O的一个子集(open/close/read/write/ioctl)。大多数情况下,这些调用和从他们衍生出来的调用可以转化为围绕基本I/O的非常薄的封装程序。有趣的是,因为大多数的?RTOS不支持文件系统,这些平台不提供针对flash和旋转媒质的抽象文件存储,常常使用完全不同的代码和/或者不同的应用程序接口(API)(比如pSOS的pHILE)。
WindRiverVxWorks在这方面比其它多数RTOS平台做的较好些,提供功能丰富的I/O子集,主要克服了网络接口/多媒体接口里的集成和广泛化障碍。
延时处理
很多RTOS也支持一种叫”下半部“("bottomhalf")的机制,它针对可中断和/或者可抢占切换的I/O延时处理方法。其他RTOS没有这样的机制,但是替代地提供类似中断嵌套的机制来获得同样的效果。
典型RTOS应用I/O架构
下面描述一个典型的I/O配置(仅仅输入)和它向主要应用程序传递数据的路径处理过程依次如下:
*一个硬件中断触发一个中断服务例程的执行。
*中断服务例程做基本的处理和完成本地的输入操作,或者让RTOS调度延时的处理。在一些情况下,延时处理过程由在Linux里面被叫做用户进程来处理,在这里就是通常的RTOS任务。
*无论在何时何地获得数据(中断服务例程或者延时切换),准备好的数据被放进队列(RTOS中断服务例程能够访问应用程序队列通过应用程序接口(API)和其它进程间通信(?IPC),请看下面的API表)。
*一个或者多个应用任务然后从队列读消息,来取出数据。
在传统的RTOS和Linux之间的典型I/O的比较输出常常由类似的机制来完成。替代使用write()或者相似的系统调用,一个或者多个RTOS应用程序任务,将准备好的数据放进队列。队列中的数据由以下过程取出:一个I/O程序或者响应”准备好发送”中断的中断服务例程,一个系统时钟,或者其它阻塞在获取队列中的应用任务,然后直接执行I/O操作(可以是轮询,也可以是通过DMA)。
将RTOSI/O映射进Linux
上面描述的基于队列的生产/消费I/O模型,仅仅是很多种在传统设计中所采用的特别方法之一。让我们继续用这个直接的例子,来讨论几种在嵌入式Linux下的实现:
大规模移植到用户空间
对于勉强了解Linux设备驱动设计细节,或者非常匆忙的开发者,可能将大多数这样基于队列设计程序完整无缺地移植到用户空间。在这种驱动程序映射配置中,内存映射的物理I/O口通过函数mmap()提供的指针可以在用户空间操作。
#include
#defineREG_SIZE0x4/*deviceregistersize*/
#defineREG_OFFSET0xFA400000
/*physicaladdressofdevice*/
void*mem_ptr;/*de-referenceformemory-mappedaccess*/
intfd;
fd=open("/dev/mem",O_RDWR);/*openphysicalmemory(mustberoot)*/
mem_ptr=mmap((void*)0x0,REG_AREA_SIZE,PROT_READ+PROT_WRITE,
MAP_SHARED,fd,REG_OFFSET);
/*actualcalltommap()*/
一个基于进程的用户线程进行与基于RTOS的中断服务例程或者延时任务一样的操作,然后使用SVR4进程间通信函数msgsnd()将消息放进队列,等待被另一个本地线程或者另一个进程利用函数msgrcv()来获取。这种快速”脏的”处理方法是好的原型,同时对于建立可发布型代码带来了巨大的挑战。首先重要的是需要在用户空间扫描中断。象DOS仿真(DOSEMU)项目提供基于信号的带SIG(Silly中断发生器)中断I/O,但是用户空间的中断处理过程非常慢(毫秒量级中断延时,所替代的基于内核的中断服务例程中断延时为数十微秒)。更进一步讲,在用户空间的切换调度不能保证用户空间的I/O线程100%的及时执行,即使采用可抢占Linux内核和实时调度策略。