QNX IPC机制

QNX微内核介绍

IPC进程间通信

IPC在QNX Neutrino (微内核) 从一个嵌入式实时系统向一个全面的POSIX系统转变起着至关重要的作用
在QNX中,消息传递是IPC的主要形式,也提供了其他的形式,除非有特殊的说明,否则这些形式也都是基于本地消息传递而实现的。QNX Neutrino提供以下形式的IPC:
QNX IPC机制

Channels and connections

IPC中消息传递时基于CS架构实现的,最简单的,制定对方进程号,要发送的一方,将消息加一个头,告诉内核把该消息发送给 pid 12345就行了,其实QNX4的时候时这么做的. 但是

  1. 如果服务器有两个线程,分别进行不同的服务, 你或者可以把消息发送给 pid 12345 tid3 就可以
  2. 如果某一服务,由一组线程进行,就没办法了
    为此, QNX6就抽象出了Channel这个概念, 一个Channel,就是一个服务的入口, 至于这个channel到底有多少线程为其服务, 那都是server的事情.

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

API create/attach destory/detach
Server ChannelCreate(flags) ChannelDestroty()
Client ConnectAttach(node, pid, chid, _NTO_SIDE_CHANNEL, flags) ConnectDetach()

QNX IPC机制
QNX IPC机制

与channel有关联的列表:

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

Synchronous message passing

比较传统的IPC方式时基于主从式架构(CS), 并且是双向通信
QNX IPC机制

client线程

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

Message copying

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

Pulses

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

优先级继承与消息

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

Message-passing API

QNX IPC机制

Robust implementations with Send/Receive/Reply

异步系统的一个重要问题是事件通知需要运行信号处理程序。异步IPC难以彻底对系统进行测试,此外也难以确保信号处理程序按预期的运行。基于Send/Receive/Reply构建的同步、非队列系统结构,可以让应用程序的架构更健壮
使用各种IPC机制时,避免死锁是一个难题,在QNX中只需要遵循两个原则,就可以构建无死锁系统:

  • 永远不要两个线程相互发送消息;
  • 将线程组织为层级结构,并只向上发送消息;
    QNX IPC机制
    上层的线程可以通过MsgSendPulse()或MsgDeliverEvent()来传递非阻塞消息或事件:
    QNX IPC机制

Events

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

  • 调用MsgDeliverEvent()接口发送事件
  • 中断处理函数
  • 定时器到期
    事件本身可以有多种类型:Pulse、中断、各种形式的信号、强制解除阻塞的事件等
    考虑到事件本身的多样性,服务器实现所有的异步通知显然不太合适,更好的方式是客户端提供一个数据结构或者cookie,服务器调用MsgDeliverEvent()时将事件类型写进cookie中
    QNX IPC机制
    ionotify()函数是客户端线程请求异步事件通知的一种方式,许多POSIX异步服务都基于这个之上来构建的,比如mq_notify和select等

Signals

QNX IPC机制
QNX Neutrino 拓展了信号传递机制, 允许信号针对特定的线程, 而不是简单的针对包含线程的进程.
由于信号是异步事件, 它们通过事件传递机制实现, 接口如下:
QNX IPC机制
QNX IPC机制
当一个服务器线程想通知一个客户端线程时,有两种合理的事件选择:Pulse或信号

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

POSIX message queues

OSIX通过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机制

Shared Memory

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

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

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

QNX中消息传递通过拷贝完成,当消息较大时,可以通过共享内存来完成,发送消息时不需要发送整个消息内容,只需将消息保存到共享内存中,并将地址传递过去即可
QNX IPC机制
通常会使用mmap来将共享内存区域映射到进程地址空间中来,如下图所示:
QNX IPC机制

Typed memory

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

Pipes and FIFOs

管道是一种非命名IO通道,用于在多个进程之间的通信,一个进程往管道写,其他进程从管道读取。管道一般用于平行的两个进程单向的传递数据,如果要双向通信的话,就应该使用消息传递了。
FIFOs与管道本质是一样的,不同点在于FIFOs会在文件系统中保存为一个永久的命名文件

源参考
知乎参考