嵌入式系统通信机制的研究与应用*
2010-08-11段哲
段 哲
(中国船舶重工集团公司第七二二研究所 武汉 430079)
1 引言
在计算机和信息技术高速发展的今天,计算机和计算机技术大量地应用在我们的日常生活中,广泛应用的嵌入式系统便是其中的一种。在PC市场已趋于稳定的今天,嵌入式系统的发展速度正在加快,嵌入式系统不仅广泛应用于工业、交通、通信、科研、医疗卫生、等日常生活等领域,而且很多应用于各种航天、军工与医疗等高安全的工作需求的工作环境中,系统出现问题可能带来巨大的经济损失,甚至危及人的生命。另一方面由于航天、军工与医疗等工作环境中的特殊性,要求的设备必须满足一些环境或者电磁兼容或者可靠性的要求。嵌入式Linux系统具有高实时性和高可靠性的特点,因而越来越被广泛地应用在上述领域。
在应用中,Linux系统下的通信无疑是值得研究的重点和难点。本文比较了在Linux系统下进程之间相互通信的几种IPC(InterProcess Communication)技术,同时结合C语言的一些语言特性,针对采用Linux帧缓冲设备的嵌入式系统给出了一个有效的通信机制。该实现对含有需要实时处理的多个功能模块进行分时控制,在牺牲系统资源的基础上,保证了系统的高实时性和高可靠性,从而满足了各种航天、军工与医疗等高安全的工作需求的工作环境的要求。
2 进程间通信技术
2.1 管道
把从一个进程连接到另一个进程的一个数据流称为管道,它是UNIX系统IPC的最古老形式,并且所有UNIX系统都提供此种通信机制。管道有下面的特点:
1)半双工:即数据只能在一个方向上流动,需要通信时,需要建立起两个管道;
2)只能在具有公共祖先的进程之间使用,即只能用于父子进程或者兄弟进程之间;
3)管道对于管道两端的进程而言,就是一个文件但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在于内存中。
2.2 信号
信号是为了使进程获得某项重要的通知而发送给它的重要事件。这时进程必须立即停止当前的工作,转而处理该信号。每一个信号都用一个整数代表信号的类型。这些信号定义在系统文件/usr/include/asm/signal.h中,我们在日常使用Linux的过程中经常接触到信号操作,比如当某个程序正在运行时为了终止程序的运行按下Ctrl-C键,或使用Kill命令把该进程杀掉,实际上都是使用了信号作进程间通信。当系统捕获了某信号时,就会响应该信号指定的动作,系统才对它进行处理,没有发出信号的进程就处于等待状态。
信号是软件层次上对中断机制的一种模拟,在实际应用中,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。通常来说信号的生命周期分为以下四个阶段:
1)信号产生:信号事件的发生主要有两个来源:硬件来源和软件来源;
2)信号注册:信号在进程中注册指的是使进程知道需要处理某个信号;
3)信号注销:信号在进程中注销指进程等待处理某个信号,且该信号没有被进程阻塞,则在运行相应的信号处理函数前,进程把信号从未决信号链中卸载;
4)信号处理:进程注销信号后,立即执行相应的信号处理函数,执行完毕后,信号的本次发送对进程的影响彻底结束。
2.3 FIFO
管道应用的一个重大限制是它没有名字,因此,它只能用于具有亲缘关系的进程间通信;FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中,这样,即使与FIFO的创建不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信。值得提出的是FIFO严格遵循先进先出(First In First Out),对管道及FIFO的读总是从开始处返回数据。
2.4 共享内存
共享内存可以说是最有效的进程间通信方式。最显而易见的好处就是效率高,进程可以直接读写内存,而不需要任何数据的复制。对于管道和消息队列等通信方式,需要在内核和用户空间进行四次数据复制,而共享内存只复制两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。Linux从2.2内核开始支持多种共享内存方式,如mmap()系统调用等。
2.5 消息队列
消息队列就是一个消息的链表。可以把消息看做一个记录,具有特定的格式以及特定的优先级,对消息队列有写权限的进程可以向其中按照一定的规则添加新消息。对消息队列有读权限的进程则可以从消息队列中读走消息。
3 实例分析
由于嵌入式系统自身的优势,有多种通信方式,结合实际项目的需要,所以提出了本文的设计方案。
传统的设计机制是基于图1所示的各功能模块的系统组成结构。该系统包括了显示模块、串口模块、网络模块、键盘模块、数据处理模块,以及主控制模块6个模块。其他类型的嵌入式系统都可在此基础上进行缩减或扩展,其基本的软件设计思路是完全相同的。系统的一致性为研究和开发统一的软件实现机制提供了广阔的应用空间。
3.1 传统机制的缺陷
在很多嵌入式系统设计中,通常是在主函数Main(*argv,*argn)中,创建多个线程,进行数据的交互,从而满足实时多任务的处理。虽然我们可以使用互斥量来解决线程之间互相破坏的问题,但当一个互斥量已经被别的线程锁定后,如果一直没有被解锁,等待它的线程将一直被挂着,程序就陷入死锁状态,这时,所有线程都因等待互斥量而被挂起,它们中任何一个都不可能恢复运行,程序无法继续运行下去。这样的设计模式会导致各模块的错综复杂的纠缠,使系统各功能模块具有极高的耦合性。一旦某个线程出现问题,就会导致系统瘫痪,甚至死机现象,造成不可估量的后果。
由于很多嵌入式系统应用于各种航天设备、军工设备等高安全需求的环境中,系统出现问题可能带来巨大的经济损失,甚至危及人的生命;而一个不合理的实现机制很难甚至不可能保证其行为,尤其是现在的系统越来越复杂,实现的功能越来越多,包含的模块越来越多,该问题就越来越严重。只有系统在一个合理的实现机制下运行,才能保证系统的可靠性与稳定性。
3.2 新机制的实现
在进程间通信采用信号、消息机制,可以很好地处理具有多模块功能的嵌入式系统的实时要求,多任务需求,从而满足更复杂功能的设备的需求。
系统采用中央集中控制策略,主控制模块执行各种决策控制(参见图1),主动向外围设备(子模块)写信息,而采用信号(软中断)的方式接收外围设备(子模块)发送的信息。在主控制模块与外围设备(子模块)之间存在如图2所示的数据交互界面。
图2 主控制模块与外围设备的数据交互界面
各模块通过进程间通信即消息、信号机制,完成设备所需的功能。
其中主控制模块设定为父进程,其它模块为子进程。利用fork()函数可以创建新的子进程。利用Linux多进程地址空间的独立性,使中央控制模块与各外围设备控制模块相互隔离,以免相互影响。但是在实现时必须注意多进程的同步问题,解决这个问题的办法是在程序中设置信号量,允许进程通过检测和设置它的值来实现同步,保证在此期间其他进程不能进行类似的操作。通过Linux系统的信号机制,给中央控制模块与各外围设备控制模块提供实时通信,提高CPU的处理效率。通过Linux系统的消息机制,给中央控制模块与各外围设备控制模块提供数据通信。通过消息队列可靠地传递各模块发送或接收的数据。
新机制采用的多进程分配空间各自独立,空间消耗上比多线程大,但与整个系统的高安全性和高可靠性相比,我们可以在系统资源允许的情况下,以牺牲系统资源为代价,来满足各种航天设备、军工设备等高安全需求的环境要求。所以本文提出的方案是适宜的。
4 主要代码示例
为了更好的说明问题,在本例中我们打开两个子进程,进行多进程操作与进程间通信演示,软件框图如图3所示。
图3 多进程通信框图
if(pid1==0)
{
int times=0;
if(signal(SIGPATOCH1,sig_usr)==SIG_USR)
{
printf("can'tcatch SIGPATOCH1");
return;
}
for(;;)
{
sleep(3);
times++;
memset(buf,0,100);
sprintf(buf,"msgtype1,msginfo%d",times);
msgwrite(ctop_queueid,buf,strlen(buf),1);
kill(getppid(),SIGCH1TOPA);
}
}
子进程2完成功能:每隔5秒向父进程发一个消息,然后发信号SIGCH2TOPA,父进程收到该信号后读取该消息队列,实现代码如下:
if(pid2==0)
{
int times=0;
for(;;)
{
sleep(5);
times++;
memset(buf,0,100);
sprintf(buf,"msg type 2,msginfo%d",times);
msgwrite(ctop_queueid,buf,strlen(buf),2);
kill(getppid(),SIGCH1TOPA);
}
}
父进程实例代码:
if(signo==SIGCH1TOPA)
{
memset(buf,0,100);
sprintf(buf,"%s",ch_to_par_msg.mtext);
strcat(buf,tempbuf);
msgwrite(ptoc_queueid,buf,strlen(buf),1);
//取消SIGPATOCH1信号的发送,
kill(pidsub1,SIGPATOCH1);
}
else if(signo==SIGCH2TOPA)
{
memset(buf,0,100);
sprintf(buf,"%s",ch_to_par_msg.mtext);
strcat(buf,tempbuf);
msgwrite(ptoc_queueid,buf,strlen(buf),1);
//取消SIGPATOCH1信号的发送,
kill(pidsub2,SIGPATOCH2);
}
}
5 结语
本文利用嵌入式系统提供的进程通信机制:信号、消息队列,给出了一种适合于经典嵌入式系统的系统实现,并给出了一些简单范例代码;该机制在系统资源允许的情况下,以牺牲系统资源为代价来保障系统的高安全性和高可靠性,从而满足各种航天设备、军工设备等高安全需求的环境中要求,并在实际应用中取得了良好的实现效果。
[1]W.RICHARD STEVENS BILL FENNER.UNIX网络编程[M].杨继张,译.北京:清华大学出版社,2006,1
[2][美]W.Richard Stevens Stephen A.Rago.UNIX环境高级编程[M].第二版.尤晋元,张亚英,戚正伟,译.北京:人民邮电出版社,2006,5
[3]于明俭,陈向阳,方汉.Linux程序设计权威指南[M].北京:机械工业出版社,2001,4
[4]杨水清,张剑,施云飞,等.ARM嵌入式系统开发技术详解[M].北京:电子工业出版社,2008,11
[5][美]K.Wall,M.Watson,M.Whitis,et al.GNU/Linux编程指南[M].王勇,王一川,林花军,等译.北京:清华大学出版社,2000,7