导言
在曩昔几年中,Linux成功地替代了一些最首要的传统RTOS(实时操作体系)渠道,成为了各式各样的嵌入式设备和运用中首选的嵌入式操作体系。尽管一度曾被认为是不重要的渠道,但今日嵌入式Linux现已成为干流,广泛运用于消费电子、手持和无线设备、数据联网以及电信设备等范畴。Google公司在2007年11月发布的Android手机操作体系正是依据Linux内核的操作体系,使得Linux在数字移动电话业获得跨越式开展。
笔者在从台式频谱仪到手持式频谱仪的项目研制中完结了RTOS到Linux的运用移植。本文介绍了全体的规划思路和一些关键问题的完结细节。
1 RTOS到Linux的移植剖析
简直一切的RTOS都有一个简略的编程模型,它由多线程的履行(一般称为使命)构成,包含在单一的地址空间中。在RTOS中,单一主程序下多使命一起运转,具有很高的实时呼应才干。
曩昔大大都嵌入式处理器没有内存办理单元,因而RTOS是单地址空间形式,即它们的物理地址和逻辑地址都是相同的。可是现在大大都的中高端处理器装备了MMU(内存办理单元)。在MMU的支撑下,Linux选用虚拟内存办理,将地址空间分为物理地址和虚拟地址,因而体系操作硬件时要进行地址映射。
依据两类体系的体系结构,RTOS移植到Linux的底子结构如图1所示。
由图1可看出,移植的底子进程为:
① RTOS的悉数运用代码移植到一个Linux单进程;
② RTOS的使命转换成Linux线程;
③ RTOS的物理地址空间映射到Linux的虚拟地址空间。
在详细的运用移植进程中,还应考虑在Linux体系下处理上层运用实时呼应底层硬件中止,运用层与内核层的异步通讯、数据交换,以及多进程、多线程的规划等问题。
2 RTOS到Linux的移植完结
2.1 地址映射
大都RTOS是针对较早的无MMU的CPU而规划,所以疏忽了内存办理部分,即便当MMU面世后也是这样——不差异物理地址和虚拟地址。大大都 RTOS还悉数运转在特权形式,尽管表面上看来是增强了功用,但悉数的RTOS运用和体系代码都能够拜访整个地址空间、内存映射过的设备以及其他I/O操作。这样,即便存在不同,也很难把RTOS运用程序代码同驱动程序代码差异开来。
关于当时包含MMU的处理器而言,Linux体系供给了杂乱的存储办理体系,使得进程所能拜访的虚拟内存抵达4 GB。
在Linux体系中,进程的4 GB虚拟内存空间[1]被分为两个部分——用户空间与内核空间。用户地址空间一般散布为0~3 GB,剩余的3~4 GB为内核空间。上层运用程序一般状况下只能拜访用户空间的虚拟地址,不能拜访内核空间的虚拟地址。运用程序只要经过体系调用(代表运用程序进程在内核态履行)等办法才干够拜访到内核空间。
而外设I/O资源是不在Linux内核虚拟地址空间中的(如SRAM或硬件接口寄存器等),若需求拜访某外设I/O资源,必须先将其物理地址映射到内核虚拟地址空间中,然后才干在内核空间中拜访它。
Linux内核拜访外设I/O资源的办法有两种:静态映射(map_desc)和动态映射(ioremap)。关于静态映射,内核在体系启动时经过map_desc结构体静态创立I/O资源到内核地址空间的线性映射表(即page table),这种映射表是逐个映射的联系。开发人员能够自界说该I/O内存资源映射后的虚拟地址。创立好了静态映射表,在内核或驱动中拜访该I/O资源时则无需再进行ioremap映射,能够直接经过映射后的I/O虚拟地址去拜访它。
这儿首要评论更常用的动态映射办法。动态映射办法是直接经过内核供给的ioremap函数动态创立一段外设I/O内存资源到内核虚拟地址的映射表,然后能够在内核空间中拜访这段I/O资源。代码如下:
#define bcon*(volatile unsigned long*)ioremap(0x56000010,4)//动态映射
上述代码的意义是将0x56000010开端的4字节的物理地址映射到内核的虚拟地址中,回来的开端虚拟地址值赋给bcon宏界说。对宏界说的操作即对物理地址的操作。
ioremap宏界说在asm/io.h内:
#define ioremap(addr, size)__ioremap(addr, size, 0)
__ioremap函数原型为(arm/mm/ioremap.c):
void __iomem * __ioremap(unsigned long phys_addr, size_t size, unsigned long flags);
其间,phys_addr为要映射的开端的I/O地址;size为要映射的空间的巨细;flags为要映射的I/O空间和权限有关的标志。
该函数回来映射后的内核虚拟地址(3G~4G),接着便能够经过读写该回来的内核虚拟地址去拜访这段I/O内存资源。所以,在移植的开端就应该在头文件中完结设备物理地址的映射,便利后续的开发。
2.2 多进程多线程规划
大大都的RTOS内核都供给多使命的办理机制。使命是一个具有独立功用的无限循环的程序段的一次运转活动,是实时内核调度的单位。多使命在内核的办理、调度下并行履行,并且使命都是无限循环的,继续完结其功用。多使命实时操作体系示意图如图2所示。
在比较两类嵌入式体系的架构之后,移植的进程中很自然地将RTOS的多使命转换成Linux的多进程、多线程。
进程是Linux体系资源办理的最小单位,是程序的一次履行进程,是Linux资源分配的底子单位。线程是在进程内部,它是比进程更小的能独立运转的底子单位,是Linux体系分配CPU时刻的底子单位。线程比进程更节省资源,节省时刻。在详细的移植进程中,选用主进程等候上层衔接,主进程下多线程并行履行。一起选用互斥信号量处理线程拜访资源的同步问题。
Linux主进程程序流程如图3所示。
2.3 运用层与内核层通讯
因为RTOS的单地址空间形式使得其内核层与运用层没有差异,所以在数据交换、实时呼应等方面有必定的优势。而Linux体系供给了严厉的内存办理机制,能确保体系愈加安稳地运转。但一起增加了运用层与内核层,以及运用层与底层硬件通讯的难度。本节内容首要处理运用层与内核层的信号告诉、数据交换这两个关键问题。
2.3.1 异步信号告诉机制
RTOS是对外来事情在限制时刻内能作出反应的体系。在RTOS中,时刻是一种重要的体系资源,对外部事情的呼应和使命的履行都必须在限制的时刻内完结。在多机体系中,还必须在限制的时刻内完结音讯的发送和接纳。在RTOS中,输出成果的正确性不只取决于计算所构成的逻辑完毕,还要取决于成果发生的时刻。
Linux在发行开始并未界说为一款实时操作体系。跟着Linux内核的不断开展,现在安稳的Linux2.6内核现已具有了很好的实时呼应才干。本文的研讨项目中,需求上层运用对底层硬件进行实时呼应。RTOS并没有严厉差异上层运用和内核,其多使命并行履行,能很好达地到实时呼应的意图。而移植到Linux体系中,上层运用和底层硬件并不能直接通讯,要经过内核驱动层。尽管能够选用查询办法完结,可是实时性不高,一起糟蹋CPU资源。本文选用异步信号告诉机制,完结了上层运用对底层硬件的实时呼应。
异步告诉[2]的意思是:一旦设备安排妥当,则自动告诉运用程序,这样运用程序底子不需求查询设备状况,这一点十分类似于硬件上“中止”的概念,比较精确的称谓是“信号驱动的异步I/O”。信号是在软件层次上对中止机制的一种模仿,在原理上进程收到信号与处理器收到中止请求是相同的。信号是异步的,一个进程不用经过任何操作来等候信号的抵达,原理如图4所示。
在详细的程序规划进程中,上层运用为了能处理一个设备开释的信号,要完结3项作业:
① 经过F_SETOWN操控指令设置设备文件的具有者为本进程,这样从设备驱动发送的信号才干被本进程接纳到。
② 经过F_SETFL操控指令设置设备文件支撑FASYNC,即异步告诉形式。
③ 经过signal()函数衔接信号和信号处理函数。
在上层运用设置捕获信号后,还应在设备驱动端设置信号源,在适宜的机遇让设备驱动开释信号,其相关代码也包含3部分:
① 支撑F_SETOWN指令,能在这个操控指令处理中设置filp﹥f_owner为对应进程ID。
② 支撑F_SETFL指令的处理,每逢FASYNC标志改动时,驱动程序中的fasync()函数将得以履行。
③ 在设备资源可获得时,调用kill_fasync()函数开释相应的信号给上层运用。
上述3项作业和上层运用的3项作业是逐个对应的。按其进程规划程序,即可完结上层运用经过内核层对底层硬件的及时呼应。
2.3.2 proc办法数据同享
除了前面说到的信号、套接字、信号量外,Linux还有管道、报文行列、同享内存等进程间通讯机制。在移植进程中,因为Linux体系分为运用层和内核层,所以不只要进行进程间的通讯,还要完结运用层与内核层的数据交换。以上的机制多是依据进程间通讯,并不能很好地满足要求。在这儿选用proc文件体系的办法在Linux内核层和运用层之间进行数据交换。
在Linux体系中,proc文件体系是一个虚拟文件体系,用于内核向用户导出信息。运用proc文件体系通讯是比较便利的一种运用层与内核层的数据交换办法,能够将对虚拟文件的读写作为与内核中实体进行通讯的一种手法。内核的很大都据都是经过这种办法出口给上层运用的,内核的许多参数也是经过这种办法来让上层便利设置的。实际上,许多运用严重地依赖于proc文件体系,因而它简直是必不可少的组件。
关于proc文件体系的运用,有如下的接口函数:
struct proc_dir_entry *create_proc_entry(const char *name,mode_t mode,struct proc_dir_entry *parent);
typedef int (read_proc_t) (char *page, char **start, off_t off, int count, int *eof, void *data);
typedef int (write_proc_t) (struct file *file, const char __user *buffer,unsigned long count, void *data);
void remove_proc_entry(const char *name, struct proc_dir_entry *parent);
以上函数效果分别是创立proc文件体系节点、读写proc节点,以及删去proc节点。详细移植的proc程序流程如图5所示。
2.4 调试运转
依据移植的底子结构,在处理了以上几个关键问题后,底子完结了整个移植的进程。最终要做的便是程序的调试。关于程序语法的调试,在编译的进程中处理。依据Linux渠道下的编译器gcc的提示信息,修正呈现的语法类过错。在确保了运用程序文件的成功编译后,选用gdb调试软件进行功用的调试,一起结合打印函数printf盯梢调试。在程序恰当的方位参加printf打印信息,例如依据创立proc节点的回来值来打印成功或许失利的信息,能够很直观地了解程序的运转状况,是很有用的调试办法。经过两种手法的结合,最终完结运用程序的调试。成果表明,能够在Linux体系下正常运转。
结语
现在越来越多的开发团队正在抛弃第一代实时操作体系,挑选更安稳的开放式的嵌入式Linux渠道。参阅本文归纳的运用程序的移植进程以及相关的关键技术,开发人员能够经过更少的时刻,将曾经的RTOS的代码成功地移植到一个现代化的Linux渠道上来。