linux并不是严格意义上的实时操作系统,为了实际需要,工程师们必须想尽办法来祢补这一不足,于是出现了rtlinux和rtai等并不强调商业性的软件。免费的rtlinux显然庞大而并不兼容大部分的嵌入式平台,最新版本的rtlinux也只能支持I386和PPC而已。Rtai是不错的选择,但要把它移植到你的平台上去,为了适应你的linux版本,你的CPU,你必须的花费许多的工作,比如说最近比较流行的AT91RM9200DK,光修改linux版本补丁就要花费许多的功夫。Rtlinux和rtai为了增强linux操作系统的实时性,主要是通过开辟内核模块与应用程序之间可以共享的内存快来实现的。它们在内核空间控制硬实时任务的运行,并通过一个名为FIFO的共享内存块来与应用程序进行通信。他们是很不错的软件,我想用不了多久他们就会具备更强大的可移植性。但我在本文主要是想详细的介绍一个适合小型嵌入式系统使用的增强linux操作系统实时性的方法。当然,原理也是开辟一个实时应用程序与内核模块之间可以共享的内存。
众所周知,内核空间和用户空间只能通过系统调用来共享数据,如果进程要等待一个中断的发生,它所能做的就是把自己挂在等待队列里,直到中断服务程序来唤醒它。然后,进程才把内核空间的的数据通过特定的系统调用写到用户空间里。大部分程序员为了避免这样造成的不可忍耐的延时,都会把对数据的操作都放在内核空间里运行,也就是扩大中断服务程序的功能。但如果开辟两个空间可以共享的内存块,程序员就不必要这么为难了。我以AT91RM9200DK的平台为例,linux操作系统版本为2.4.19-rmk7,不需要半天时间,就可以实现两个空间的共享内存。
AT91RM9200DK的SDRAM的大小为31Mbyte,正常情况下,System RAM的大小也是31Mbyte,我们要把31Mbyte的高端地址空出2M来作为我们的共享内存块,这个内存块是独立的,不能为linux操作系统的内存管理所用了。首先必须通知内核它的内存只有30Mbyte了,我的方法是在u-boot的环境变量里设置mem=29M。然后在include/asm-arm/目录下建立头文件:new_fifo.h,代码如下:
#ifndef NEW_FIFO
#define NEW_FIFO
#endif
#ifdef NEW_FIFO
#define AT91_NEW_FIFO_BASE 0x21d00000
#define num_base(a) (0x21d00000 (0x1000 * a))
/SDRAM最后1M空间的起始地址,我把它以0x1000Byte的大小划分成256个FIFO/
#define SPI_NUM_FIFO 2
/SPI设备占用了一个FIFO,是第三个FIFO/
#define MAX_NUM_FIFO 256
#define READONLY 0
#define READEN 0x1
#define WRITONLY 0x2
#define WRITEN 0x4
typedef struct new_fifo{
int code,key;
int start,size;
int flags;
char data[4000]; /数据区/
int endflag;
} *at91_fifo;
static char * new_fifo_fun(int num,int flags,int code,int size)
{
at91_fifo fifo_p;
int num_addr;
char * data;
if(num > MAX_NUM_FIFO)
return -1;
num_addr = num_base(num);
/printk("the num_addr is %p n",num_addr);
fifo_p = (at91_fifo)ioremap(num_addr,(1024 * 4));
/ printk("the fifo_p is %pn",fifo_p);
fifo_p->code = code;
/ printk("the code addr is %pn",&(fifo_p->code));
fifo_p->flags = flags;
fifo_p->size =size;
data = &(fifo_p->data[0]);
/printk("the data addr is %pn",data);
return data;
}
#endif
在设备驱动程序中,首先在注册中断服务程序之前,要调用new_fifo_fun函数,得到数据区首地址的指针。这个指针在这个设备驱动程序中可被设置成全程变量。然后在中断服务程序中直接对数据进行读写。
比如说,在文件头部写:
static char * data;
然后在初始化文件中,注册中断之前加入:
data = new_fifo_fun(SPI_NUM_FIFO,WRITEN,0,100);
最后在中断服务程序中加入:
for(i=0;i<100;i )
*(data ) = i;
接下来,需要做的工作必须到用户空间去做,我设计了一个简单的进程,就是读取SPI的FIFO空间的数据,通过/dev/mem来读取SDRAM高地址的数值,使用的是mmap函数,全文如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
main()
{
int *mmaddr;
int i,fd;
fd=open("/dev/mem", O_RDWR);
mmaddr = (int *)mmap(0, 1024,PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0x21d02000);
for(i=0;i<10;i )
{printf("the mmaddr data is %pn",*(mmaddr ));
printf("%dn",i);
}
}
打印出来的结果是:
the mmaddr data is (nil)
0
the mmaddr data is 0xd24e92c2
1
the mmaddr data is 0xf01ab26d
2
the mmaddr data is 0x64
3
the mmaddr data is 0x4
4
the mmaddr data is 0x3020100
5
the mmaddr data is 0x7060504
6
the mmaddr data is 0xb0a0908
7
the mmaddr data is 0xf0e0d0c
8
the mmaddr data is 0x13121110
9
太好了,说明我们数据读写都成功拉。同样的,如果要把用户空间的数据写到内核空间也是可以的。只不过实时系统比较少有这样的要求。如果这个时候,进程在用户空间监视FIFO里的某几个数值,当这个数值变得符合要求的时候,进程认为中断已经发生,并可以读取数据了。
但是,直接对共享内存空间的数据操作比通过系统调用能够增加多少的实时性呢?这个我没有进行精确的计算,但以前我做过一个试验:用2.4.14版本的linux,平台以MPC823E(motorola的PPC)为CPU,主频为50M,扩展了一个语音压缩调制系统,该系统的中断线中断频率是几乎1ms一次。使用系统调用的结果就是10个中断的数据几乎就被有冲掉3-4个,因为语音的要求系统又不能开更多的缓存,后来就使用了这篇文章里说的这个方法,0.1ms级的中断都扛住了。这对以后平台上跑更多的进程比较有保障。
上一篇:linux 使用的一些问题杂集
下一篇:Linux启动过程中文件系统的加载