实时Linux进程间的通信

实时Linux进程间的通信

介绍完实时系统中的进程通信之后,将会介绍两种用于进程通信的机制,分别是FIFOs和共享内存。另外对两种机制进行比较,特别是在检测新的写入和数据一致性这一块。然后会说明一下两种机制在实时系统应用中的可能性。

进程通信已经成为实时系统要处理的一个重要问题,在分时系统中,进程通信是通过开销很大的形式化结构来实现的,这对于应用来说会造成定时上的问题。

实时Linux中的应用被分为了两个部分:实时和非实时。(这句话说的是实时系统中的应用被分为实时和非实时,而不是系统。)实时这部分,应该是体积小、简单并且快速,它只包含与时间直接相关的代码,并且被分配在内核地址空间中,还可以被高等级的特权执行。非实时这部分,被允许被分配在用户空间,还能实现数据处理、存档以及用户接口这些功能。

这两部分也需要一些有效的方法来实现相互的通信。接下来会介绍两种有关进程通信的机制,分别是FIFOs和共享内存。

实时的FIFOs

实际上,实时的FIFOs是一个点对点的队列,是用来传输数据的,它类似于UNIX FIFOs标准。它们的通信方式是单工通信,也就是说,它们只能沿着一个固定的方向来传输数据。所以,要建立两个实时FIFOs才能实现全双工通信模式,一个FIFO负责一个方向上的数据传输。但是这种点对点的通信管道,不能支持一个写者,多个读者的运行模式。

实时FIFOs被分配在内核空间中,实时任务的接口包括创建、注销、读写,读和写是原子操作,也是不可阻塞的。在实时Linux中所有的实时任务都被分配在内核空间中的,所以我们使用了整数来查找其对应的实时FIFO,而不是文件描述符。(数字标记和文件标识符都是索引的作用,为什么不能用文件标识符?因为在内核?)这些数字的声明和赋值是在实时任务的初始化例程中完成的。实时任务有一个重要的特征,当它读取一个空的FIFO时,将会返回0个字节。也就是说,实时任务不会从没有数据的FIFO中执行读取操作。两个实时任务可以通过FIFO来相互通信,也可以用来相互触发。

对于非实时来说,实时FIFOs一般被当做字符设备,这个接口遵循了UNIX API中字符设备的标准:一个文件描述符标记着一个FIFO,然后打开这个FIFO,更多的操作是通过调用读/写函数来实现。当从用户这边读取数据时,一般都是选择系统调用去等待数据准备好,但是轮询的方法更好一点。

在实时这边,一个处理程序是和实时FIFO是相关的。当数据从用户空间的进程写入到实时FIFO时,处理程序将被执行。这个特征适合控制实时任务的执行。当用户进程向FIFO写入命令信息,fifo处理程序立即被调用,它会处理信息并且相应的操作。实时和非实时这两部分中关于实时FIFOs的接口布局,如图1所示。

实时Linux进程间的通信

共享内存

在两个进程之间需要交换大量的数据时,共享内存是一种最有效的进程通信机制。当内置的同步不被要求时,进程之间共享内存的模式就像线程之间共享变量一样,但是它们仍有各自保护的地址空间。实时Linux中的实时应用和非实时应用被分配在不同的地址空间,这部分,我们的关注点在内核模式和用户空间进程是如何共享内存的。

在实时Linux中,共享内存主要体现在物理存储预留了一部分用于非实时和实时任务之间的共享。有两种方法分配这些内存,第一种是分配在系统引导部分,第二种是分配给特殊的设备。如果使用第一种解决方法,也就是告诉系统需要预留一个存储池用于段的存储,所以在进程中不需要使用它,这种解决方法的主要问题是因为内存碎片的原因导致共享内存的大小是有限的。

第二种解决方法是使用存储缓冲区(mbuff)模式和/dev/mbuff设备去匹配共享内存。例如存储区在系统起始阶段是不能更新的,更不能被内存碎片限制大小,它被分配在内核地址空间,并且存储缓冲区提供了在用户空间的存储匹配功能。另外,存储区是按照逻辑连续分配的,而不是按物理地址连续分配的。因为它不能交换出去,所以适合实时任务和用户空间之间的通信,或者是一些用于内核和用户之间交换数据的高速带宽。

共享内存的主要特点是异步接收,它降低了死锁的风险,因为一个进程不能阻塞另一个正在访问公共区域的进程。另一个特有的改变是不需要把大数据结构再一次的写入整个块。因为没有内置同步,所以要注意的是数据的一致性。

实时FIFOs和共享内存的比较

选择实时FIFOs还是共享内存,要取决于应用自身的通信特点。在表1中展示了实时FIFOs和共享内存的主要特点。

实时Linux进程间的通信

两种机制提供了一些优点,实时FIFOs提供了数据队列,所以没有协议可以预防写入覆盖,它们也同样支持同步的阻塞,所以进程不需要对到达的数据进行轮询。另外,共享内存需要握手协议来预防数据被覆盖重写,但是它没有点对点的限制,也能够被任意数量的用户空间和实时进程共享。另外,它也提供快速的更新数据,甚至我们在一个大的数据结构中进入个别的项目时,也可以快速更新数据。在这部分中,我们也会考虑在应用设计中存在的共同问题:检测新的写入,数据的一致性,以及它们可能的解决方案。关于新的写入的检测,因为读和写要遵守UNIX标准协议,所以FIFOs要比共享内存好。在第二部分中,我们已经介绍了有关使用实时FIFOs来交换数据的协议,而进程也会使用read()和write()函数来完成这些操作。但是,如果使用共享内存的方法,则不需要这些函数,读和写的操作可以通过直接读写指针完成。所以,操作系统不需要检测共享内存是否被更新,唯一的解决方法是建立明确的握手协议。

这样做的方法之一是使得每一个新消息的消息标识符递增。接收者对标识符进行调查,并且和前一个进行比较,如果它们不一样,则接收到新的消息。当接收者读取信息时,它必须在状态结构中显示消息标识符,从而使得发送者知道该消息已经被读取。另一方面,只有前一个消息标识符在状态结构中显示后,发送方才能发送其他消息。这种解决方法假定了时间周期轮询模型在用户空间和内核中都成立。如果这不是应用设计模型,但是共享内存是,那么这可能不是一个好的选择。关于这个问题的解决方案将会在第5部分介绍。

如果我们只使用实时FIFOs,那么在数据一致性上将不会存在问题,因为FIFOs能够排队,但是我们必须确保FIFOs的空间是足够的大。当使用共享内存时,实时Linux设计会施加约束条件,因为Linux用户进程在共享内存执行读取或者是写入的过程时,一些实时(内核)任务可能会打断进程的执行。如果一个Linux进程在读的过程中被打断,这个进程在开始让数据变得陈旧,在最后刷新数据。如果它在读的过程中被打断,这个进程在开始刷新数据,在最后使得数据变陈旧。这两个问题一般都是致命的。

因为实时进程能够打断用户空间进程,所以这个问题很容易发生。在用户进程使用共享内存时,我们可以使用指定的共享标志来实现进程之间的互斥。当实时任务进入共享内存时,它会时刻检测标志,当标志位被置1时,进程将会延迟读和写的操作,直到该标志位被置0。另外,如果下面的条件成立,可能会造成实时任务无限期的推迟:

-实时任务在用户空间进程中的同一个周期运行(或在该周期的整数倍);

-因为访问临界区时是同步的,所以实时进程总是打断Linux进程;

第一个条件意味着是实时代码运行比较慢或者慢于用户进程,第二个意味着Linux代码运行确定性的实时编码。在同一时间,两者都不是真的,并且都是很少的。如果它发生了,连续的延期可以通过实时代码检测,并且可以通过触发动作来到达保持控制系统的目的。

实时FIFOs和共享内存的应用

下面会通过两个例子来介绍实时FIFOs和共享内存的应用。第一个例子实在工业控制中的应用,另外一个例子是介绍如何通过实时FIFOs来创建事件驱动实时任务。工业控制应用程序的组织如图2所示。

实时Linux进程间的通信

共享内存被用于实时任务到用户空间应用的大数据传送。实时任务和内核线程之间的通信通过RS-485串行端口来实现,它们是周期运行的,周期时间为100ms。系统参数归档,事件归档(写入磁盘),以及人类系统交互(我么可以容忍一定的延迟,但是不要太长,避免打破浓度运算符)这三部分属于用户空间应用程序。

实时FIFOs的主要用途是发送命令到实时任务,当命令通过实时FIFOs发送给实时任务时,FIFO处理程序将会被立即调用,并且处理命令。这就是实时任务启动和结束的过程,这种机制也可以用于改变一些内部的实时任务的参数,也可以使用不复杂的共享内存来存储公共变量。实时FIFOs的另一种用途是表示实时任务中在共享存储器的数据的是否准备好,从而触发用户空间线程来读取共享内存。如果使用阻塞来实现同步,一个用户空间线程可以阻塞,通过实时FIFO来等待来自实时任务的消息。当消息到来时,它将不阻塞,读取共享内存中的数据,进程接收数据后再次阻塞,然后等待FIFO中可用的新数据。

前面实例中的实时任务都是周期性的,在有些情况下,只有当某些事件发生时,系统才适合去执行任务,这些实时任务是事件驱动,如果这是一个中断事件,它们被叫做中断驱动实时任务。中断驱动实时任务被中断服务程序直接出发启动,这里不需要实时FIFOs帮助我们做任何事情。当发生一些事件的同时,如果我们想触发一个任务,实时FIFOs将变的非常有用。一个用户进程触发一个事件驱动实时任务的数据流说明如图3a所示。

实时Linux进程间的通信

用户进程通过实时FIFO发送命令,命令是FIFO的处理程序,所以触发实时任务去唤醒并做一些工作。如果我们有另一个实时任务而不是用户进程,这种情况几乎是相同的。在案例中,一个实时任务会触发另一个。这适合在系统利用高来优先级处理罕见的事件,因为实施保证了它们能够被迅速的处理。


分享到:


相關文章: