QNX IPC机制

QNX相关历史文章:

  • QNX简介
  • QNX Neutrino微内核

介绍

Interprocess Communication(IPC,进程间通信)在QNX Neutrino从一个嵌入式实时系统向一个全面的POSIX系统转变起着至关重要的作用。IPC是将在内核中提供各种服务的进程内聚在一起的粘合剂。在QNX中,消息传递是IPC的主要形式,也提供了其他的形式,除非有特殊的说明,否则这些形式也都是基于本地消息传递而实现的。QNX Neutrino提供以下形式的IPC:


QNX IPC机制_第1张图片
IPC

Synchronous message passing

一个线程调用MsgSend()往目标线程发送消息时会阻塞住,直到目标线程调用MsgReceive(),进行消息处理并调用MsgReply()回复后才会解除阻塞。
在QNX Neutrino中,服务器线程通常是循环的,等待接收客户端发过来的消息。可以看看客户端线程和服务器线程在消息传递过程中的状态变化:

QNX IPC机制_第2张图片

  1. 客户端线程
  • 客户端线程调用MsgSend()后,如果服务器线程还没调用MsgReceive(),客户端线程状态则为SEND blocked,一旦服务器线程调用了MsgReceive(),客户端线程状态变为REPLY blocked,当服务器线程执行MsgReply()后,客户端线程状态就变成了READY
  • 如果客户端线程调用MsgSend()后,而服务器线程正阻塞在MsgReceive()上, 则客户端线程状态直接跳过SEND blocked,直接变成REPLY blocked
  • 当服务器线程失败、退出、或者消失了,客户端线程状态变成READY,此时MsgSend()会返回一个错误值。
  1. 服务器线程
  • 服务器线程调用MsgReceive()时,当没有线程给它发送消息,它的状态为RECEIVE blocked,当有线程发送时变为READY
  • 服务器线程调用MsgReceive()时,当已经有其他线程给它发送过消息,MsgReceive()会立马返回,而不会阻塞;
  • 服务器线程调用MsgReply()时,不会阻塞;

Message copying

QNX的消息服务,是直接将消息从一个线程的地址空间拷贝到另一个线程地址空间,不需要中间缓冲,因此消息传递的性能接近底层硬件的内存带宽。消息内容对内核来说没有特殊的意义,只对消息的发送和接收者才有意义,当然,QNX也提供了定义良好的消息类型,以便能扩充或替代系统提供的服务。
消息在拷贝的时候,支持分块传输,也就是不要求连续的缓冲区,发送和接收线程可以指定向量表,在这个表中去指定消息在内存中的位置。这个与DMA的scatter/gather机制类似。

QNX IPC机制_第3张图片
multipart transfer

分块传输也用在文件系统中,比如读数据的时候,将文件系统缓存中的数据分块读到用户提供的空间内,如下图:


QNX IPC机制_第4张图片

对于简单的单块消息传递,就不需要通过IOV(input/output vector)的形式了,直接指向缓冲区即可。对于发送和接收的接口,多块发送和单块发送如下:


QNX IPC机制_第5张图片

Channels and connections

在QNX Neutrino中,消息传递是面向通道(channel)和连接(connection)的,而不是直接从线程到线程的。接收消息的线程需要创建一个channel,发送消息的线程需要与该channel建立connection
服务器使用MsgReceive()接收消息时需要使用channels,客户端则需要创建connections,以连接到服务器的通道上,连接建立好之后,客户端便可通过MsgSend()来发送消息了。如果进程中有很多线程都连接到一个通道上,为了提高效率,这些所有的连接都会映射到同一个内核对象中。在进程中,channelsconnecttions会用一个小的整型标识符来标记。客户端connections会直接映射到文件描述符,在架构上这是一个关键点,可以消除另一层转换,不需要根据文件描述符来确定往哪里发消息,而是直接将消息发往文件描述符即可。


QNX IPC机制_第6张图片
Connections map elegantly into file descriptors

有几个与channel有关联的列表:

  • Receive,等待消息的LIFO线程队列;
  • Send,已发送消息但还未被接收的优先级FIFO线程队列;
  • Reply, 已发送消息,并且已经被收到,但尚未回复的无序线程列表;
    不管在上述哪个列表中,线程都是阻塞状态,多个线程和多个客户端可能等待在同一个channel上。
    QNX IPC机制_第7张图片
    threads blocked while in a channel queue

Pulses

除了同步发送/接收/回复服务外,QNX还支持固定大小的非阻塞消息,这种消息被称为Pulse,携带一个小的负载(四个字节数据,加一个字节的代码)。Pulse通常被用在中断处理函数中,用作通知机制;也允许服务器在不阻塞客户端的情况下,向客户端发送信号。

QNX IPC机制_第8张图片
Pulses pack a small payload

优先级继承与消息

服务器进程按照优先级顺序来接收消息和脉冲,当服务器中的线程接收请求时,它们将继承发送线程的优先级。请求服务器工作的线程的优先级被保留,服务器工作将以适当的优先级执行,这种消息驱动的优先级继承避免了优先级反转的问题。

Message-passing API

QNX IPC机制_第9张图片

Robust implementations with Send/Receive/Reply

异步系统的一个重要问题是事件通知需要运行信号处理程序。异步IPC难以彻底对系统进行测试,此外也难以确保信号处理程序按预期的运行。基于Send/Receive/Reply构建的同步、非队列系统结构,可以让应用程序的架构更健壮。

在使用各种IPC机制时,避免死锁是一个难题,在QNX中只需要遵循两个原则,就可以构建无死锁系统:

  • 永远不要两个线程相互发送消息;
  • 将线程组织为层级结构,并只向上发送消息;
    QNX IPC机制_第10张图片
    Threads should always send up to higher-level threads

    上层的线程可以通过MsgSendPulse()MsgDeliverEvent()来传递非阻塞消息或事件:
    QNX IPC机制_第11张图片
    A higher-level thread can "send" a pulse event

Events

QNX Neutrino提供异步事件通知机制,事件源可能有三种:

  • 调用MsgDeliverEvent()接口发送事件
  • 中断处理函数
  • 定时器到期
    事件本身可以有多种类型:Pulse、中断、各种形式的信号、强制解除阻塞的事件等。
    考虑到事件本身的多样性,服务器实现所有的异步通知显然不太合适,更好的方式是客户端提供一个数据结构或者cookie,服务器调用MsgDeliverEvent()时将事件类型写进cookie中。
    QNX IPC机制_第12张图片
    The client sends a sigevent to the server

ionotify()函数是客户端线程请求异步事件通知的一种方式,许多POSIX异步服务都基于这个之上来构建的,比如mq_notifyselect等。

Signals

信号类似于软中断,QNX支持的信号如下:


QNX Neutrino扩展了信号传递机制,允许信号针对特定的线程,而不是简单的针对包含线程的进程。由于信号是异步事件,它们通过事件传递机制实现。接口如下:


QNX IPC机制_第13张图片

QNX IPC机制_第14张图片
Signal delivery

当一个服务器线程想通知一个客户端线程时,有两种合理的事件选择: Pulse或信号

  • Pulse,需要客户端创建一个channel,并且调用MsgReceive()接收;
  • 信号,只需要调用sigwaitinfo(),不需要创建channel

POSIX message queues

POSIX通过message queues定义一组非阻塞的消息传递机制。消息队列为命名对象,针对这些对象可以进行读取和写入,作为离散消息的优先级队列,消息队列具有比管道更多的结构,为应用程序提供了更多的通信控制。QNX Neutrino内核不包含message queues,它的实现在内核之外。
QNX Neutrino提供了两种message queues的实现:

  • mqueue,使用mqueue资源管理的传统实现
  • mq,使用mq服务和非同步消息的替代实现

QNX消息机制与POSIX的Message queues有一个根本的区别:,QNX的消息机制通过内存拷贝来实现消息的传递;而POSIX的消息队列通过将消息进行存取来实现消息的传递。QNX的消息机制比POSIX的消息队列效率更高,但有时为了POSIX的灵活,需要适当的牺牲一点效率。

消息队列与文件类似,操作的接口相近。


QNX IPC机制_第15张图片

Shared memory

共享内存提供了最高带宽的IPC机制,一旦创建了共享内存对象,访问对象的进程可以使用指针直接对其进行读写操作。共享内存本身是不同步的,需要结合同步原语一起使用,信号量和互斥锁都适合与共享内存一块使用,信号量一般用于进程之间的同步,而互斥锁通常用于线程之间的同步,通通常来说互斥锁的效率会比信号量要高。

共享内存与消息传递结合起来的IPC机制,可以提供以下特点:

  • 非常高的性能(共享内存)
  • 同步(消息传递)
  • 跨网络传递(消息传递)

QNX中消息传递通过拷贝完成,当消息较大时,可以通过共享内存来完成,发送消息时不需要发送整个消息内容,只需将消息保存到共享内存中,并将地址传递过去即可。


QNX IPC机制_第16张图片

通常会使用mmap来将共享内存区域映射到进程地址空间中来,如下图所示:


QNX IPC机制_第17张图片
Arguments to mmap

Typed memory

类型化内存是POSIX规范中定义的功能,它是高级实时扩展的一部分。
POSIX类型化内存,提供了一个接口来打开内存对象(以操作系统特定的方式定义),并对它们执行映射操作。这个对提供BSP/板级特定的地址布局与设备驱动或用户代码之间的抽象时非常有用。

Pipes and FIFOs

管道是一种非命名IO通道,用于在多个进程之间的通信,一个进程往管道写,其他进程从管道读取。管道一般用于平行的两个进程单向的传递数据,如果要双向通信的话,就应该使用消息传递了。

FIFOs与管道本质是一样的,不同点在于FIFOs会在文件系统中保存为一个永久的命名文件。

你可能感兴趣的:(QNX IPC机制)