QNX IPC机制

作者: Loyen | 来源:发表于2019-01-03 14:28 被阅读5次

    QNX相关历史文章:

    介绍

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


    IPC

    Synchronous message passing

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

    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机制类似。

    multipart transfer

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


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


    Channels and connections

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


    Connections map elegantly into file descriptors

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

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

    Pulses

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

    Pulses pack a small payload

    优先级继承与消息

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

    Message-passing API

    Robust implementations with Send/Receive/Reply

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

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

    • 永远不要两个线程相互发送消息;
    • 将线程组织为层级结构,并只向上发送消息;
      Threads should always send up to higher-level threads
      上层的线程可以通过MsgSendPulse()MsgDeliverEvent()来传递非阻塞消息或事件:
      A higher-level thread can "send" a pulse event

    Events

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

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

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

    Signals

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


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


    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的灵活,需要适当的牺牲一点效率。

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


    Shared memory

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

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

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

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


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


    Arguments to mmap

    Typed memory

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

    Pipes and FIFOs

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

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

    相关文章

      网友评论

        本文标题:QNX IPC机制

        本文链接:https://www.haomeiwen.com/subject/eonalqtx.html