现在的嵌入式系统往往有并发的特征,就像多任务操作系统的多进程一样,其内核会提供几种机制处理任务间通信,本文对四种常见通信模式进行了分析,并对其各自的优劣作较细致的比较。
目前,越来越多的工业产品、军用品、医疗产品和消费品都在使用微机,单片机这些并发产品或是使用并发软件系统来控制,或是在其中加入了关键的并发软件组件。
在这些软件系统中,多个任务经常以异步方式执行,有时各个任务之间需要进行通信,来同步它们之间的运行或从一个任务来控制另一个任务的运行。
而常见的并发任务进程间通信具有4种机制,它们分别是管道机制、消息队列机制、远程调用机制以及共享内存机制。
三种机制应用较多
一个管道(有时也称管线)是一个能够用来从一个任务传送数据到另一个任务的简单信息通道。该通道用软件实现并由操作系统管理。在应用程序看来,管道类似于一个数据文件,管道可以用标准的C库的I/O函数来打开、关闭、写入、读出。不过和文件不同的是管道是单向的,它有一个源端和一个目的端,有时也称为汇集端。在源端的任务只能向管道写,在目的端的任务只能从管道读。
管道传输流数据,即连续的字节或流。因此访问一个管道可以比作是访问一个顺序文件。在源端的任务用写入字节到一个文件时所用的同样的系统调用来向管道写数据;在目的端,接受任务读取数据,同样是采用读取一个文件时所用的相同系统调用。一个管道的每个端只能被一个任务使用。
一个管道也像一个队列。源端处的任务根据管道的长度(可以解释为在被一个读操作清空前它能容纳的字节多少)可连续地向管道写入数据,直到管道充满为止。当目的端的任务也按开始到最终进入的顺序从管道读取数据,数据将按其进的顺序被递送出去。
一个管道允许从一个任务到另一个任务的流数据传送,而一个消息队列却能允许任务间的任意结构的消息传送。而且,消息队列不是单向的,在一个任务生成了一个消息(或获得对一个消息的访问权)时,该任务就可发送或接收队列中的消息。
与通过管道读写的机制相比,消息队列I/O操作采用特殊的消息发送和消息接收调用,这些调用函数是被特地设计来处理有任意大小的数据结构的传输的。
有些消息队列的实现方案支持消息类别标识。当一个任务将一个消息放到队列中时,它可将一个类别标识,这个标识可以是一个整数或是其他类型的ID,同该消息关联起来。接收消息的任务就能通过检查这个标识域而无须查看消息内部来确定消息的内容和性质。任务可以采用它们自己的消息内容的标识方法。但由消息队列服务规定一个标准的标识方法会简化标识,并提高可靠性。
而共享内存机制是一块由多个进程共享的内存区域,每个访问共享内存的进程都把这块内存看成是该进程私有的内存空间。共享内存是为进程(而不是为线程)间的通信而配备的,一个进程中的线程其实已经在共享它们所有的全局数据。
共享内存的工作机理首先是一个进程必须建立一个共享内存区。其方法是调用一个操作系统功能,指定区域的大小和命名符号。操作系统创建这个区域,把它映射到进程的数据区,并返回一个指向该共享内存起始地址的指针。该进程可用这个指针来读写这个共享内存。
其他任何要访问该共享内存的进程必须通过操作系统调用将此共享内存连接到它的地址空间上。对此连接子程序的调用必须包括命名符号,这样操作系统才能找到所要的共享内存区,因为在一个给定的时间内可能有多个活动的共享内存区。操作系统将此共享内存映射到这些进程的数据区,并返回一个含有该内存起始地址的指针。
要使用共享内存的编程人员必须选择命名符号并在创建和连接共享内存的调用中使用它。编程人员还要负责设定有多少进程会把共享内存连于它们的地址空间。
共享内存是所有IPC机制中最快、同时也是最危险的,快是因为传输以内存的存取速度进行;危险是因为它存在与多线程全局变量访问中一样的问题。也就是说,对共享内存管理不当会导致竞赛局面,因此进程应使用一个同步对象来控制共享内存的访问。
远程调用尚未广泛应用
远程调用机制(RPC)是仅就进程间通信,而不是线程间(线程共享一个进程内的所有代码)提供的另一种机制。顾名思义,一个远程调用允许一个进程中的一个程序直接调用另一个进程中的一个程序。这两个进程可能运行在同一台计算机上,也可能运行在两个由网络连接的不同的计算机上。
一个RPC的目的就是要使调用另一个进程中的一个程序变得如同调用一个本地进程中的程序一样容易。在实践中,这个目的很难达到。一般来说,RPC需要定义所有能被远程调用的程序,即要使用一种元语言来定义程序名、数据类型以及数据向哪个方向传递(是传给被调用程序,或返回给调用程序,还是两者都有)。一个RPC生成程序将这些程序的定义编译到占位程序中,这些占位程序将程序参数用一种独立于机器的格式封装,然后利用操作系统提供的RPC服务来启动一个远程程序。当调用一个远程程序时,编译器产生一个对RPC生成器所创建的一个占位函数的调用。虽然源代码中的实际程序调用编写起来就像本地程序调用一样容易,但还必须做好很多的工作,比如先规定好所有远程程序的接口等。
RPC机制功能强大但相当复杂,因此它也是嵌入式系统以及一般系统中通信研究的难点。由于其复杂性,远程调用至今还处在研究中,尚未能在嵌入系统中广泛使用。
RPC是基于客户/服务器模式的,因此一般被划分为客户端和服务器端两部分。客户部分主要负责把应用程序的调用信息按一定的格式传送到服务器,并把服务器发回的响应按调用方式返回应用程序,服务器部分主要接收客户发来的调用请求并完成相应调用。
RPC的服务器端结构如表左侧所示。基础通信层为RPC的通信处理层提供基本的通信接口,由操作系统实现。通信处理层利用基础通信层的功能给上层提供更合理的接口,其功能包括发送指定消息、接收指定消息、处理消息结构、处理通信错误等。
请求和服务管理是RPC服务器的主要部分。它除完成对服务的管理,如启动服务、结束服务、服务登记等外,也完成客户请求的处理。认证系统是独立的部分,它完成RPC认证处理,RPC支持多种认证方式。外部数据表示模块解决异种机间数据表示差异问题。它将本机的数据格式转换为统一的外部数据表示格式,把接收到的用外部数据表示格式的数据转换为本机数据格式。端口绑定部分解决服务器的通信端口发现问题。统计模块用于统计调用信息,如函数被调用次数,出错次数等。应用层不属于RPC范畴,它是RPC的使用者(如NFS)。
RPC客户端的层次结构如表右侧所示。同服务端一样,RPC是构筑在基础通信层上的,基础通信层为RPC提供了基本的通信能力。RPC在此基础上增加通信处理层,提供数据发送、接收函数和超时处理机制,最重要的是采用了滑动窗口方式来进行调用数量控制。在通信处理层上,RPC完成调用进程的实现和任务管理。RPC将每个调用作为一个任务,同时因为可能有多个任务运行,引进了任务管理,使得调用的进程可以采用状态机方式实现。
通过实例对比
下面通过一个简单的图例来进一步说明管道机制、消息队列机制和共享内存机制间的原理差别。首先采用消息队列或管道来实现客户/服务器进程间文件拷贝的通常步骤是:
1. 服务器进程从输入文件读取。该文件的数据由内核读入自己的内存空间,然后从内核拷贝到服务器进程。
2. 服务器进程向一个管道或消息队列写入数据。这通常需要把数据从进程拷贝到内核。
3. 客户进程再从该IPC通道读出这些数据,通常需要把这些数据从内核拷贝到进程。
4. 最后这些数据由客户进程缓冲区拷贝到输出文件。
以上过程共需要4次数据拷贝,而且这4次拷贝都是在内核和某个进程间进行的,处理器资源和I/O资源开销比较大,如图1所示。
而如果让交换数据的两个进程共享一个缓冲区,则可解决交换数据在内核和进程间多次传递的问题,节省资源。交换数据的进程间必须能够协调访问内存区。如图2所示,采用共享内存机制实现客户/服务器进程间文件拷贝的过程也包括4个步骤:
1. 服务器进程使用信号灯取得访问共享内存区的权利。
2. 服务器进程从输入文件读数据到共享缓冲区。
3. 服务器进程读入数据完毕,使用信号灯通知客户。
4. 客户进程从该共享内存区写数据到输出文件。
使用共享内存的方法只拷贝两次,即从输入文件到共享内存区,从共享内存区到输出文件。从两个图例可以看出共享内存比管道或消息队列的方法节约资源并且速度更快。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)