摘要:介绍了RTLinux的两个重点特点:硬实时性和完备性,及其在嵌入式系统应用中的一些重要功能,并结合实时处理的具体实例对其编程方法加以说明。
近年来,基于PC的嵌入式系统得到迅速的发展。在各种不同的操作系统中,由于Linux操作系统的廉价、源代码的开放性以及系统的稳定性,使其在基于PC的嵌入式系统中的应用日益广泛。RTLinux(RealTime Linux)[1]是一种基于Linux的实时操作系统,是由FSMLabs公司(Finite State Machine Labs Inc.)推出的与Linux操作系统共存的硬实时操作系统。它能够创建运行的符合POSIX.1b标准的实时进程;并且作为一种遵循GPL v2协议的开放软件,可以达GPL v2协议许可范围内自由地、地使用、修改和再发生。本文介绍了RTLinux的特点及功能,并结合一个实时处理的具体实例对其编程方法加以说明。
1 RTLinux的特点
RTLinux(AReal-Time Linux,亦称作实时Linux)是Linux中的一种实时操作系统。它由新墨西哥矿业及科技学院的V. Yodaiken开发。目前,RTLinux有一个由社区支持的版本,称为RTLinux Free,以及一个来自FSMLabs的商业版本,称作RTLinux Pro。
RT-Linux开发者并没有针对实时操作系统的特性而重写Linux的内核,因为这样做的工作量非常大,而且要保证兼容性也非常困难。将linux的内核代码做一些修改,将linux本身的任务以及linux内核本身作为一个优先级很低的任务,而实时任务作为优先级的任务。即在实时任务存在的情况下运行实时任务,否则才运行linux本身的任务。TRLinux能够创建运行的符合POSIX.1b标准的实时进程;并且作为一种遵循GPL v2协议的开放软件,可以达GPL v2协议许可范围内自由地、地使用、修改和再发生。
它是Linux在实时性方面的扩展,采用已获得的双核技术:一个微型的RTLinux内核把原始的Linux内核作为它在空闲时的一个线程来运行。这开启了在两个不同的内核层面上――实时的RTLinux内核和常用的,非实时的Linux内核――运行不同程序的新方式。原始的Linux内核通过RTLinux内核访问硬件。这样,所有硬件实际上都是由RTLinux来进行管理的。目前,有两种不同的RTLinux版本:RTLinux/Free(或者RTLinux/Open)和RTLinux/Pro. RTLinux/Pro是一个由FSMLabs开发的完全商业版本的实时linux。RTLinux/Free是一个由社区开发的开源版本。
在Linux操作系统中,调度算法(其于吞吐量准则)、设备驱动、不可中断的系统调用、中断屏蔽以及虚拟内存的使用等因素,都会导致系统在时间上的不可预测性,决定了Linux操作系统不能处理硬实时任务。RTLinux为避免这些问题,在Linux内核与硬件之间增加了一个虚拟层(通常称作虚拟机),构筑了一个小的、时间上可预测的、与Linux内核分开的实时内核,使得在其中运行的实时进程满足硬实时性。
1.1 硬实时性
硬件实时部分被作为实时任务来执行,并从外部设备拷贝数据到一个叫做实时有名管道(RTFIFO)的特殊I/O端口;程序主要部分作为标准Linux进程来执行。它将从RTFIFO中读取数据,然后显示并存储到文件中,实时部分将被写入内核。设计实时有名管道是为了使实时任务在读和写数据时不被阻塞。图3所示的是RTFIFO结构图。 图3 RT-FIFO结构图
RTLinux将标准Linux内核作为简单实时操作系统(RTOS)(或叫子内核)里优先权的线程来运行,从而避开了Linux内核性能的问题。 从图3可以看出,RTLinux拥有两个内核。这就意味着有两组单独的API,一个用于Linux环境,另一个用于实时环境。此外,为保证实时进程与非实时Linux进程不顺序进行数据交换,RTLinux引入了RT-FIFO队列。RT-FIFO被Linux视为字符设备,多可达150个,分别命名为/der/rtf0、/dev/rtf1……/dev/rtf63。的RT-FIFO数量在系统内核编译时设定。
RTLinux程序运行于用户空间和内核态两个空间。RTLinux提供了应用程序接口。借助这些API函数将实时处理部分编写成内核模块,并装载到RTLinux内核中,运行于RTLinux的内核态。非实时部分的应用程序则在Linux下的用户空间中执行。这样可以发挥Linux对网络和数据库的强大支持功能。
操作系统的定时机制,可以提高任务调度器的效率,但增加CPU处理定时中断的时间开销。RTLinux采用一种折衷的方案,不将8354定时器设计成10毫秒产生定时中断的固定模式,而是根据近事件(进程)的时间需要,不断调整定时器的定时间隔。这样既可以提供高的时间值,又避免过多增加CPU处理定时中断的时间开销。RTLinux系统同时将各时间间隔相加,保持一个系统全局时间变量,并使用软中断的方式来模拟传统的100Hz定时中断,将其传递给Linux系统使用。
1.2 完备性
过去,实时操作系统仅是一组原始的、简单的可执行程序,它所做的仅仅是向应用程序提供一程序库,但如今,实时应用程序通常要求能够支持TCP/IP、图形显示、文件和数据库系统及其它复杂的服务。将一个简单的小型实时内核与Linux内核共存,用简单的小型实时内核处理实时任务,将非实时任务交给Linux内核去处理,而Linux内核本身也作为一个RTLinux实时内核在空闲时运行的进程。实时内核中的实时任务可以直接访问硬件,不使用虚拟内存,给实时进程提供了很大的灵活性;运行在Linux用户空间中的非实时任务,可以方便地使用系统提供的各种资源(网络、文件系统等),并受到系统的保护,增加了系统的安全性。
2 RTLinux的主要功能
RTLinux提供了一整套对硬实时进程的支持函数集。在此,仅对在嵌入式系统中重要的三个方面:进程间的通讯、中断和硬件设备的访问以及线程间的同步加以阐述。
在中断控制硬件与LINUX之间放置一个软件仿真层。具体做法是,在LINUX源码中出现cli、sti和iret的所有地方都用仿真宏:S_CLI、S_STI和S_IRET来替换。所有的硬件中断就都被仿真器所截获。
当需要关中断时,就将仿真器中的一个变量置0。不论何时若有中断发生,仿真器就检查这个变量。如果是1(LINUX已开中断),就立即调用LINUX的中断处理程序;否则,LINUX中断被禁止,中断处理程序不会被调用,而是在保存着所有挂起中断的信息的变量的相应位置1。当LINUX重新开中断,所有挂起中断的处理程序都会被执行。这种仿真方式可以称之为"软中断"。
2.1 进程间的通信(IPC)
实时FIFO是能够被内核实时进程和Linux用户空间进程访问的快进快出队列,是一种单向的通讯机制,可以通过两路实时FIFO构成双向的数据交换方式。在使用实时FIFO前先要对实时FIFO通道初始化:
#include<rtl_fifo.h>
int rtf_create(unsigned int fifo,int size)
使用后应该注销实时FIFO通道:
int rtf_destroy(unsigned int fifo)
在初始化实时FIFO通道后,RTLinux内核的实时进程和Linux用户空间的进程都可以使用标准的POSIX函数open、read、write和close等对实时FIFO通道进行访问。内核实时进程还可以使用RTLinux的专有函数rtf_put和rtf_get对实时FIFO通道进行读写。
RTLinux共享内存由mbuff.o模块支持,可以使用下面的函数分配和释放共享内存块:
#include <mbuff.h>
void *mbuff_alloc(const char *name,int size)
void mbuff_free(const char *name,void *mbuf)
函数mbuff_alloc有两个参数,共享内存名name和共享内存块的大小size。如果指定的内存共享名并不存在,分配成功时返回共享内存指针,访问计数置为1,分配失败时返回空指针;如果指定的内存共享名已经存在,返回该块共享内存的指针,并将访问计数值直接加1。在实时内核模块中使用该函数时,应该将函数mbuff_alloc和mbuff_free分别放在init_module和cleanup_module模块之中。
2.2 中断和访问硬件
硬中断(实时中断)具有的延时,在系统内核中只有少数的实时进程使用。函数rtl_request_irq和rtl_free_irq用于安装和卸载指定硬件中断的中断服务程序。
#include<rtl_core.h>
int rtl_request_irq(unsigned int irq,unsigned int (*handler)(unsigned int ,struct pt_regs *))
int rtl_free_irq(unsigned int irq)
中断驱动的线程可以使用唤醒和挂起函数:
int pthread_wakeup_np(pthread_t thread)
int pthread_suspend_np(void)
一个中断驱动的线程可以调用函数pthread_suspend_np(pthread_self())阻塞自身线程的执行,然后由中断服务函数调用函数pthread_wakeup_np唤醒该线程的换行,直到此线程再次调用函数pthread_suspend_np(pthread_self())将自身挂起。
软中断是Linux内核常常使用的中断,它能够更安全地调用系统函数。无论如何,对于许多任务来说并不能提供硬实时性能,将会导致一定的延时。
Int rtl_get_soft_irq(void (*handler)(int,void*,struetpt_regs ),const char* devname)分配一个虚中断并安中断;void rtl_free_soft_irq(unsigned int irq)释放分配的虚中断。
RTLinux与Linux一样通过/dev/mem设备访问物理内存,具体由模块rtl_posixio.o提供此项功能。首先应用程序应该打开/dev/mem设备,通过函数mmap对某段物理内存进行映射后,即可使用映射后的地址访问该段物理内存。另一种访问物理内存的方法是通过Linux将会失败。另一种访问物理内存的方法是通过Linux的函数ioremap(2)。RTLinux访问I/O端口的函数如下(对于x86结构):
输出一个字节到端口:
#include <asm/io.h>
void outb(unsigned int value,unsigned short port)
void outb_p(unsigned int value,unsigned short port)
输出一个字到端口:
#include<asm/io.h>
void outw(unsigned int value,unsigned short port)
void outw_p(unsigned int value,unsigned short port)
从端口读一个字节:
#include<asm/io.h>
char inb(unsigned short port)
char inb_p(unsigned short port)
从端口读一个字:
#include<asm/io.h>
short inw(unsigned short port)
short inw_p(unsigned short port)
其中带后缀_p的函数使读写端品时有一个小的延时,这在快速的计算机访问慢速的ISA设备时是必需的。
2.3 线程同步
当多个实时线程需要访问共享资源时,如果没有一种同步机制,将破坏共享资源中数据的完整性。RTLinux提供一种简单的加锁方法mutex来控制对共享资源的存取,并支持POSIX的pthread_mutex_family函数组[3]。目前有以下函数可以使用:
pthread_mutexattr_getpshared //得到指定属性线程共享属性值;
pthread_mutexattr_setpshared //设置指定属性线程共享属性值;
pthread_mutexattr_init //初始化mutex的属性;
pthread_mutexattr_destroy //删除mutex的属性;
pthread_mutexattr_settype //设置mutex信号的类型;
pthread_mutexattr_gettype //得到mutex信号的类型;
pthread_mutex_init //按指定的属性初始化mutex;
pthread_mutex_destroy //删除给定的mutex;
pthread_mutex_lock //锁定mutex,如果mutex已被锁定,阻塞当前线程直到解锁;
prhread_untex_trylock //锁定mutex,如果mutex已被锁定,函数立即返回;
pthread_untex_unlock //解锁mutex;
互斥信号类型有PTHREAD_MUTEX_NORMAL(default POSIX mutexes)的PTHREAD_MUTEX_SPINLOCK(spinlocks)
3 RTLinux的编程实例分析
下面结合一个具体的程序parport.c[4],对RTLinux的编程特点加以说明。程序parport.c中的实时线程在并口的2、3脚(并口的数据D0和D1)上周期输出信号1,而对应硬件中断7的实时中断服务程序将在并口的2、3脚输出信号0。连接并口的2脚和10脚(并口的确认信号线,对应于计算机的中断7),则可在并口的2、3脚上产生一个方波信号。parport.c源程序如下:
#include <rtl.h>
#include <rtl_sync.h>
#include <time.h>
#include <ptrhrad.h>
#include <asm/io.h>
#include <linux/kd.h>
pthread_t thread;
unsigned int intr_handler(unsigned int irq,struct pt_regs *regs) { //中断服务函数
outb(0,0x378); //输出字节0到并口数据线
rtl_hard_enable_irq(7); //使能硬件中断7
return 0;
}
void * start_routine (void *arg){ //实时线程
struct sched_param p; //定义实时线程控制参数的数据结构
p.sched_priority = 1; //设置优先级为1
pthread_setschedparam (pthread_self(),SCHED_FIFO,&p); //设置实时线程的控制参数
pthread_make_periodic_np(pthread_self(),gethrtime(),100000);
//启动周期为10ns的实时线程
while (1){
pthread_wait_np(); //实时线程挂起
outb(3,0x378); //实时线程周期执行,输出3到并口数据线
}
return 0;
}
int init_module(void) {//初始化模块
int status;
rtl_irqstate_t f; //保存当前的中断状态标志到变量f,并禁止中断
status=rtl_request_irq(7,irtr_handler); //设置硬件中断7的处理程序
rtl_printf("rtl_request_irq:%d",status); //输出的控制台
outb_p(inb_p(0x37A) |0x10,0x37A); //使能并口中断(硬件上)
rtl_hard_enable_irq(7); //使能中断7(软件上)
rtl_restore_interrupts(f); //按照变量f恢复当前的中断状态标志,并使能中断
return pthread_create (&thread,NULL,start_routine,0);
//创建实时进程thread
}
void cleanup_module(void){ //清除模块
rtl_free_irq(7); //禁止中断7
pthread_delete_np(thread); //删除实时进程thread
}
程序parport.c的make文件如下:
all:parport.o
include rtl.mk
clean:
rm -f *.0
按照如下命令对程序进行编译:
make
运行程序对采用以下命令:
modprobe rtl_sched //调入所需的处理模块
insmod parport.o //调入parport.o模块
连接并口的2脚和10脚,即可通过示波器在并口的3脚上观测到输出的方波信号。
可以看到,RTLinux的实时程序被编写成可加载的Linux内核模块,它能被动态地加入内存,不能执行Linux系统调用,模块的初始化代码对实时任务的结构作初始化,把实时任务时限、周期和释放时间等实时参数传递给RTLinux。
通过对Linux的改动,提供一种可靠且廉价的硬实时操作系统RTLinux。RTLinux开发者可以充分利用Linux提供的各种方便来编写任务的非实时部分,加速自己的任务进度。
[1]. arg datasheet https://www.dzsc.com/datasheet/arg_2147916.html.
免责声明: 凡注明来源本网的所有作品,均为本网合法拥有版权或有权使用的作品,欢迎转载,注明出处。非本网作品均来自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。