2-2-18-3 QNX 进程间通信(IPC)(一)

阅读前言

本文以QNX系统官方的文档英文原版资料为参考,翻译和逐句校对后,对QNX操作系统的相关概念进行了深度整理,旨在帮助想要了解QNX的读者及开发者可以快速阅读,而不必查看晦涩难懂的英文原文,这些文章将会作为一个或多个系列进行发布,从遵从原文的翻译,到针对某些重要概念的穿插引入,以及再到各个重要专题的梳理,大致分为这三个层次部分,分不同的文章进行发布,依据这样的原则进行组织,读者可以更好的查找和理解。


1. 进程间通信(IPC)

进程间通信在微内核从嵌入式实时内核到全面POSIX操作系统的转换中起到了重要作用。随着各种提供服务的进程被添加到微内核中,IPC是把这些组件连接成一个内聚整体的“粘合剂”。

虽然消息传递是QNX Neutrino RTOS中IPC的主要形式,但也有其他几种形式可用。除非另有说明,否则其他形式的IPC都是建立在本地消息传递之上的。使用的策略是创建一个简单、健壮的IPC服务,可以通过微内核中简化的代码路径来调整性能;更多“feature-cluttered(功能混乱/功能复杂)”的IPC服务都可以基于此进行实现。

高级IPC服务(如通过我们的消息传递实现的管道【pipes】和先进先出队列【fifo】)与单片整体式内核中相应服务,就基准测试相比,表现出的性能差不多。

QNX Neutrino至少提供以下形式的IPC:

Service:

Implemented in:

Message-passing

Kernel

Signals

Kernel

POSIX message queues

External process

Shared memory

Process manager

Pipes

External process

FIFOs

External process

设计人员可以根据带宽需求、排队需求、网络透明性等方面的考量来选择这些服务。这种权衡可能会很复杂,但是带来的灵活性会非常有用。

作为要定义微内核工程方面的工作的一部分,将消息传递作为基本的IPC原语是经过深思熟虑的。作为IPC的一种形式,消息传递(在MsgSend()MsgReceive(),和MsgReply()中实现)就是同步数据和拷贝数据。让我们更详细地研究这两个属性(指的是同步数据和拷贝数据这两个方面)。

1.1. 同步的消息传递

同步消息传递是QNX Neutrino RTOS中IPC的主要形式。

对另一个线程(该线程可能在另一个进程中)执行MsgSend(),则该线程将会被阻塞,直到对端线程执行了MsgReceive()、处理消息并执行MsgReply()。如果一个线程执行了MsgReceive(),而在此之前没有消息被发送和挂起,则该线程将会发生阻塞,直到有另一个线程执行了MsgSend()

在QNX Neutrino中,服务器线程通常会循环,等待从客户端线程接收消息。如前所述,如果一个线程可以使用CPU,那么它就处于READY状态。由于受到自身和其他线程的优先级和调度策略的影响,当前线程在并不是阻塞状态的情况下,也有可能实际上并没有获得任何CPU时间。

让我们先看看客户端线程:

在“发送-接收-应答”事务中,客户端线程的状态变化
在“发送-接收-应答”事务中,客户端线程的状态变化
  • 如果客户端线程调用了MsgSend(),而服务器线程还没有调用MsgReceive(),那么客户端线程就会被SEND阻塞。一旦服务器线程调用MsgReceive(),内核将客户端线程的状态更改为reply -blocked,这意味着服务器线程已经接收到了消息,现在正在准备进行回复。当服务器线程调用MsgReply()时,客户端线程变为READY。
  • 如果客户端线程调用MsgSend()时,而服务器线程已经在MsgReceive()上被阻塞,那么客户端线程立即变成REPLY阻塞,会完全跳过SEND阻塞状态。
  • 如果服务器线程失败、退出或消失,客户端线程变为READY, 而MsgSend()将会指示一个错误。

接下来,让我们考虑一下服务器线程:

在“发送-接收-应答”事务中,服务器线程的状态变化
在“发送-接收-应答”事务中,服务器线程的状态变化
  • 如果服务器线程调用MsgReceive()时,并且没有其他线程MsgSend()给它,那么服务器线程就会被RECEIVE阻塞。当另一个线程向它MsgSend()时,服务器线程变为READY。
  • 如果服务器线程调用MsgReceive(),而另一个线程已经向它发送了消息,那么MsgReceive()将立即返回消息。在这种情况下,服务器线程不会被阻塞。
  • 服务器线程调用MsgReply()时,服务器线程不存在阻塞的情况。

这种固有的阻塞会同步发送线程的执行,因为请求发送数据的行为也会导致发送线程被阻塞,而接收线程被安排执行。这种同步并不需要内核显式地决定下一个运行哪个线程(就像大多数其他形式的IPC一样)。执行流和数据会直接从一个上下文移动到另一个上下文。

这些消息传递原语省略了数据排队功能,因为排队可以在接收线程中进行实现(如果有需要的话)。发送线程通常会立刻准备等待响应;而排队会造成不必要的开销和更高的复杂性(即,它降低了非排队情况的速度)。因此,发送线程不需要进行单独的显式阻塞调用来等待响应(如果使用了其他IPC形式,则需要这样做)。

虽然发送和接收操作是阻塞和同步的,但MsgReply()(或MsgError())不会导致阻塞。由于客户端已经被阻塞等待应答,因此不需要额外的同步(阻塞),因此不需要阻塞MsgReply()。这允许服务器先回复客户端,然后继续处理其他事务,而内核或网络代码异步地将回复数据传递给发送线程,并将发送线程标记为准备执行状态。由于大多数服务器倾向于做一些处理,来准备接收下一个请求(此时它们会再次阻塞),因此这种方法效果很好。

在网络中,回复可能不像在本地消息传递中那样“立即”完成。有关网络消息传递的更多信息,请参阅本书中 Qnet networking 章节中的内容。

MsgReply() vs MsgError()

MsgReply()函数用于向客户端返回一个状态和零个或多个字节。另一方面,MsgError()仅用于向客户端返回一个状态。这两个函数都可以导致客户端从MsgSend()中解除阻塞。

1.2. 消息拷贝

由于我们的消息传递服务直接将消息从一个线程的地址空间复制到另一个线程的地址空间,没有中间缓冲区,因此消息传递的性能接近于底层硬件的内存带宽。

内核没有给消息的内容附加任何特殊的含义,消息中的数据只有在发送方和接收方相互定义时才具有意义。但是与此同时,内核还提供了一些“定义良好的”消息类型,以便用户编写的进程或线程可以扩展或替代系统提供的服务。【内核提供的“定义良好的”消息类型,指的是脉冲、事件、信号等】

消息传递原语支持多部件传输,因此从一个线程的地址空间传递到另一个线程的消息,不需要预先存在于单个的连续的缓冲区中。相反,发送和接收线程都可以指定一个向量表,使用该向量表指示发送和接收的消息片段在内存中的位置。请注意,发送方和接收方的各个内存部件的大小可能会不同。

多部件传输允许发送具有 header block 的消息,header block 与 data block 是分开的,不需要因创建连续消息而复制数据,从而削弱性能。此外,如果底层数据结构是一个环形缓冲区,那么可以指定一个由三个部件组成的消息,允许在环形缓冲区内把一个 header 和两个范围不连续的data block作为单个原子消息进行发送。这个概念可以等效为一种名为分散/收集DMA设施【scatter/gather DMA facility】的硬件。

多部件传输
多部件传输

每个IOV最多支持524288个部件。零件尺寸的总和不能超过SSIZE_MAX。

文件系统也广泛地使用多部件传输。在读取时,使用包含n个部件的消息,将数据直接从文件系统缓存复制到应用程序中。每个部件都指向一个缓存块;这弥补了缓存块在内存中不是连续的这种事实所带来的不便。

例如,缓存块大小为512字节,对于读取1454字节,可以使用由五个部件组成的消息来满足读取的需求:

对1454字节的读取进行分散/收集
对1454字节的读取进行分散/收集

由于消息数据在地址空间之间进行显式地复制(而不是通过页表操作),因此可以很容易地在堆栈空间上分配消息内存空间,而不是从用于MMU“page flipping”的page-aligned的特殊内存池中分配消息内存空间。因此,许多library程序可以轻而易举地编写出来,这些library程序会在客户端和服务器进程之间实现API,这种实现不需要复杂的特定于IPC的内存分配调用。

例如,客户端线程请求文件系统资源管理器代表它执行lseek的代码实现如下所示:

#include <unistd.h>
#include <errno.h>
#include <sys/iomsg.h>

off64_t lseek64(int fd, off64_t offset, int whence) {
    io_lseek_t                            msg;
    off64_t                               off;

    msg.i.type = _IO_LSEEK;
    msg.i.combine_len = sizeof msg.i;
    msg.i.offset = offset;
    msg.i.whence = whence;
    msg.i.zero = 0;
    if(MsgSend(fd, &msg.i, sizeof msg.i, &off, sizeof off) == -1) {
        return -1;
    }
    return off;
}

off_t lseek(int fd, off_t offset, int whence) {
    return lseek64(fd, offset, whence);
}

这段代码本质上是在堆栈上构建一个消息结构体,MsgSend()线程传递的各种常量和参数填充该消息结构体,然后将消息结构体发送到文件系统资源管理器(由fd标识)。对该消息的应答会指示操作的成功或失败。

由于传递的许多消息都很小,因此通过中间缓冲区将这些消息内容复制两次要比操作页表【manipulating page tables】进行直接的process-to-process拷贝的速度快得多,因此内核使用这种方式作为对传递短消息的优化。

1.3. 简单消息

对于简单的单部件消息,操作系统提供了一些函数,采用指针直接指向缓冲区的方式,而不需要IOV(输入/输出向量)。在这种情况下,部件数量被消息大小所替代。【意思是使用IOV要关注部件数量,使用单部件消息要关注消息大小】

以message send原语的为例(send采用一个发送缓冲区和一个回复缓冲区),为message send引入了四种变体:

Function

Send message

Reply message

MsgSend()

Simple

Simple

MsgSendsv()

Simple

IOV

MsgSendvs()

IOV

Simple

MsgSendv()

IOV

IOV

采用直接消息的其他消息原语,只是在其名称中去掉末尾的“v”【“直接消息”指的就是“单部件消息”】:

IOV

Simple direct

MsgReceivev()

MsgReceive()

MsgReceivePulsev()

MsgReceivePulse()

MsgReplyv()

MsgReply()

MsgReadv()

MsgRead()

MsgWritev()

MsgWrite()

1.4. 通道和连接【channels and connections】

在QNX Neutrino RTOS中,消息传递直接指向的是“通道【channel】”和“连接【connection】”,而不是直接从“线程”传递到“线程”。希望接收消息的线程,首先会创建一个通道;希望向该线程发送消息的另一个线程,必须先通过“附加【attaching】”到该“通道”建立起“连接”。

消息相关的内核调用需要有“通道”,服务器使用“通道”来接收MsgReceive()消息。“连接”由客户端的线程进行创建,以便“连接”到服务器所提供的可用“通道”上。连接一旦建立,客户端就可以通过它们并使用MsgSend()发送消息。如果一个进程中的多个线程都连接到同一个通道,那么为了提高效率,这些 “连接” 都会被映射到同一个内核对象。“通道”和“连接”在进程中由一个短小的整数标识符命名。客户端“连接”会被直接映射到文件描述符(fd)。

从架构上来说,这一点很关键。通过将客户端连接直接映射到文件描述符fd,我们又消除了另外的一层转换。我们不需要再根据文件描述符去“弄清楚”要在哪里发送消息(例如,通过read(fd)调用)。相反,我们可以直接向“文件描述符”(即连接ID)发送消息,这样会更简单。

Function

Description

ChannelCreate()

创建一个通道,用于接收消息。

ChannelDestroy()

销毁一个通道。

ConnectAttach()

创建一个连接,用于发送消息。

ConnectDetach()

分离指定连接。

连接被优雅地映射到了文件描述符
连接被优雅地映射到了文件描述符

作为服务器的进程通过实现事件循环来接收和处理消息,如下所示:

chid = ChannelCreate(flags);
SETIOV(&iov, &msg, sizeof(msg));
for(;;) {
    rcv_id = MsgReceivev( chid, &iov, parts, &info );

    switch( msg.type ) {
        /* Perform message processing here */
    }

    MsgReplyv( rcv_id, &iov, rparts );
}

此循环允许服务器线程接收来自与该通道有连接的任何线程的消息。

服务器还可以使用name_attach()创建通道并将名称与其关联。然后,发送方进程可以使用name_open()来定位该名称并创建到它的连接。

通道有几个与其相关的消息列表:

  • Receive

后进先出队列,由等待消息的线程组成。

  • Send

带有优先级的FIFO队列,由发送了消息但消息尚未被接收的发送线程组成。

  • Reply

无序列表,由已发送消息且消息已被接收但尚未收到回复的发送线程组成。

在这些列表中,等待在这些队列中的线程会被阻塞(即,RECEIVE-blocked, SEND-blocked或REPLY-blocked)。多个线程和多个客户端可能在同一个通道上进行等待。

1.5. 脉冲【Pulses】

除了 Send/Receive/Reply 这些同步服务之外,操作系统还支持固定大小的非阻塞消息。这种非阻塞消息被称为脉冲,携带有一个小的有效载荷(四个字节的数据加上一个单字节的代码)。

脉冲的有效载荷相对较小,只有8位代码和32位数据。脉冲通常用作中断处理程序中的通知机制。它们还允许服务器在无需阻塞客户端的情况下向客户端发送信号。

脉冲的有效载荷很小(只有8位代码和32位数据)
脉冲的有效载荷很小(只有8位代码和32位数据)

1.6. 优先级继承和消息

服务器进程按优先级顺序接收消息和脉冲。当服务器内的线程接收请求时,它们将继承发送线程的优先级(但不会继承调度策略)。因此,请求服务器执行某些工作的客户端线程的相对优先级会被服务器继续使用,从而使服务器能够以适当的优先级执行这些工作。这种消息驱动的优先级继承避免了优先级反转的问题。

例如,假设系统包含以下内容:

  • 一个服务器线程,优先级为22
  • 一个客户端线程,T1,优先级为13
  • 一个客户端线程,T2,优先级为10

在没有优先级继承的情况下,如果T2向服务器发送消息,那么服务器实际上是以优先级22为T2完成工作的,因此T2的优先级被颠倒了。

如果存在优先级继承,则实际发生的情况是,当服务器接收到消息时,其有效优先级更改为最高的发送方的优先级(如下所述的限制)。在这种情况下,T2的优先级低于服务器的优先级,因此在服务器接收到消息时,服务器的有效优先级的更改为T2的优先级。

接下来,假设T1在服务器优先级仍然为10时,向服务器发送一条消息。由于T1的优先级高于服务器当前的有效优先级,所以当T1发送消息时,服务器的优先级再次发生变化(服务器的有效优先级的更改为T1的优先级)。

优先级的更改发生在服务器接收消息之前,以避免另一种优先级反转的情况出现。如果服务器的优先级保持在10不变(也就是优先级的更改不是发生在服务器接收消息之前),并且另一个线程T3开始以优先级11运行,那么服务器线程就必须等待,直到T3允许服务器有一些CPU时间,以便最终可以接收T1的消息。因此,T1会被优先级较低的线程T3延迟。

如果客户端线程中最高的优先级是特权优先级,并且服务器没有启用PROCMGR_AID_PRIORITY功能,那么服务器线程将提升到最高的非特权优先级。有关更多信息,请参阅“QNX Neutrino Microkernel”章节中的 “Scheduling priority” 和 C Library Reference 中的 procmgr_ability()的内容。

在调用ChannelCreate()时,可以通过指定_NTO_CHF_FIXED_PRIORITY标志来关闭优先级继承。如果你使用的是自适应分区,使用这个标志还会导致接收线程不会在发送线程的分区中运行。

1.6.1. 服务器提升

在服务器上如果一个高优先级线程被SEND-blocked,内核会尝试找到一个或多个可能在给定通道上接收的线程,然后提高它们的优先级。

当一个通道上不存在正被RECEIVE-blocked的服务器线程时,并且客户端发送了消息或脉冲,则内核会提高最后在该通道上进行接收的所有服务器线程的优先级,希望其中某个线程可以很快完成现有工作并能够进行消息接收。

出现这种情况,可以认为是服务器设计不良的症状,内核的响应策略只是试图解决它。服务器或资源管理器始终都应该至少有一个处于RECEIVE-blocked状态的线程。

当接收线程第一次被提升时,它的原始优先级会被记录下来。如果多个线程被SEND阻塞,接收线程可能会被提升多次,但是内核只记录初始提升之前的优先级。

当下一次接收到消息时,内核会重新评估情况。如果线程仍然有脉冲或者被SEND-blocked,那么一些提升的线程可能会保持提升(尽管可能处于较低的优先级),而其他线程可能会下降回到原来的优先级。

如果没有被SEND-blocked阻塞的线程,那么所有最初提升的线程都会返回到它们原来的优先级。

当线程对通道执行MsgReceive()操作时,它会与通道建立起关联关系。在线程中有多个通道的情况下,线程会与它接收到消息的最后一个通道相关联。在接收到消息后,线程可以通过以-1表示通道ID调用MsgReceive()将自己与通道进行分离。

1.7. 消息传递API

消息传递API由以下函数组成:

Function

Description

MsgSend()

发送消息并阻塞,直到回复。

MsgReceive()

等待一个消息。

MsgReceivePulse()

从收到的消息中读取附加数据。

MsgReply()

获取收到的消息的信息。

MsgError()

将附加数据写入到回复消息。

MsgRead()

回复一个消息。

MsgWrite()

只回复错误状态。不传输消息字节。

MsgInfo()

发送一个微小的、非阻塞的消息(脉冲)。

MsgSendPulse()

等待一个微小的、非阻塞的消息(脉冲)。

MsgDeliverEvent()

向客户端交付一个事件。

MsgKeyData()

允许两个特权进程通过公共的客户端传递数据,同时验证客户端没有修改数据。

从编程角度了解消息的相关信息,请参阅QNX Neutrino入门中的“Message Passing”一章。

1.8. Send/Receive/Reply的健壮实现

将 QNX Neutrino 应用程序架构设计为一组通过Send/Receive/Reply进行协作的线程和进程,从而形成一个使用同步通知机制的系统。因此,在系统内进行指定转换时进行IPC通信,而不是异步进行IPC通信的。

异步系统的一个重要问题是,事件通知需要运行信号处理程序。异步IPC通信,可能彻底会让系统的测试操作变得很困难,并且难以确保无论信号处理程序何时运行,处理都将按照预期继续进行。应用程序通常会试图通过依赖“窗口”的显式打开和关闭,来避免这种情况,在此期间(窗口期间)的信号,会被容忍。

使用围绕Send/Receive/Reply构建的同步的、非排队的系统架构,可以非常容易地实现和交付健壮的应用程序架构。

在使用排队IPC、共享内存和其他同步原语的各种组合,进行构建应用程序时,避免死锁是另一个难题。例如,假设线程A直到线程B释放互斥锁2才释放互斥锁1。不幸的是,如果线程B一直处于直到线程A释放互斥锁1才释放互斥锁2的状态,就会导致僵局。通过经常调用仿真工具,确保在系统运行时不会发生死锁。

Send/Receive/Reply这几个IPC原语,允许构建无死锁的系统,只需要注意以下这些简单的规则:

  • 永远不要让两个线程相互发送。
  • 总是把你的线程安排在一个层次结构【hierarchy】中,所有的发送沿着树向上进行。

第一条规则显然是为了要避免僵局的出现,但第二条规则需要进一步解释。假设协同工作的线程和进程组如下:

线程应该总是发送到更高级别【higher-level】的线程
线程应该总是发送到更高级别【higher-level】的线程

在这里,层次结构中任何给定级别的线程之间都不会相互发送,而只向上发送。

这方面的一个例子可能是客户端应用程序向数据库服务器进程发送数据,而数据库服务器进程又向文件系统进程发送数据。由于发送线程阻塞并等待目标线程回复,并且由于目标线程没有SEND阻塞在在发送线程上,因此不会出现死锁。

但是,高层级的线程如何通知低级线程它拥有先前请求的操作的结果呢?(假设低层级的线程在最后一次发送时不想等待返回的结果。)

QNX Neutrino RTOS提供了一个非常灵活的架构,通过MsgDeliverEvent()内核调用来传递非阻塞事件。所有常见的异步服务都可以用它来实现。例如,服务器端的poll()调用是一个API,应用程序可以使用它来允许线程等待一组文件描述符上的I/O事件完成。除了需要异步通知机制作为从高层级线程到低层级线程的通知的“回传通道”之外,我们还可以为计时器、硬件中断和其他事件源构建可靠的通知机制。

高等级的线程可以“发送”脉冲事件
高等级的线程可以“发送”脉冲事件

还有一个相关的问题是,高等级的线程如何能够在不用冒着死锁的风险向低等级的线程发送消息的情况下,请求低等级的线程进行相关工作。有一种做法是:低等级的线程,仅作为高等级线程的“worker thread”而存在,低等级的线程主动请求执行工作。低等级线程会发送消息以“报告工作”,但是高等级线程不会立刻回复。它将延迟应答,直到更高级别的线程有工作要做,并且它将会使用能够描述要做工作的数据进行应答(这是一个非阻塞操作)。实际上,回复是用来启动工作的,而并非由发送来启动工作,这种做法巧妙地避开了第一条规则。

1.9. 事件【Events】

QNX Neutrino内核设计中有一个重要的特性,那就是事件处理子系统。POSIX及其实时扩展标准,定义了许多异步通知方法(例如,UNIX信号不用排队或传递数据,POSIX实时信号可以排队和传递数据,等等)。

QNX内核还定义了额外的、特定于QNX Neutrino的通知技术,比如脉冲。

实现所有这些事件机制(或异步通知方法)可能会消耗大量的代码空间,因此我们的实现策略是在单个的功能丰富的事件子系统上去构建所有这些通知方法。

这种实现策略的一个好处是,一种通知技术所独有的功能可以为其他通知技术所用。例如,应用程序可以将这种相同的排队服务(实现了POSIX实时信号的排队服务)应用于UNIX信号。这可以简化应用程序中信号处理程序的健壮实现。

执行线程遇到的事件可以来自以下三种来源:

  • 来自于线程所调用的MsgDeliverEvent()
  • 来自于中断处理程序;
  • 计时器的过期;

事件本身可以是许多不同类型中的任何一种:QNX的脉冲【pulses】、中断【interrupts】、各种形式的信号【signals】和强制“解除阻塞”的事件【unblock events】。“解除阻塞”是一种方法,通过这种方法可以将线程从故意阻塞的状态中释放出来,而无需实际交付任何的显式事件。

考虑到事件类型的多样性,以及应用程序所需的能力(请求最适合其需求的异步通知技术的能力),要求服务器进程(前一节中的高等级的线程)携带支持所有这些选项的代码是很尴尬的一件事。

相反,客户端线程可以向服务器提供一个被称为sigevent的数据结构或“cookie”,以便稍后再进行挂起。当服务器需要通知客户端线程时,服务器线程调用MsgDeliverEvent(),然后微内核根据客户端cookie中所编码的内容设置事件类型。

客户端向服务器发送一个信号事件
客户端向服务器发送一个信号事件

有关sigevent结构的详细信息,请参阅 C Library Reference中的sigevent的内容;有关如何使用它的描述,请参阅 Getting Started with QNX Neutrino 中的“Clocks, Timers, and Getting a Kick Every So Often”章节中的“Notification schemes”。

在完全由可信程序组成的深度嵌入式系统中,不需要对事件进行安全保护,但在更开放的系统中则不是这样。sigevents的一个问题是,服务器可以将任何事件传递给客户端,即使客户端从未表示过有兴趣接收该事件。

在QNX Neutrino 7.0.1或更高版本中,客户端可以注册事件从而使其更加安全,如下所示:

  1. 客户端设置sigevent来指示希望如何得到通知。
  2. 如果客户端希望允许服务器更新与事件相关的值,客户端会在事件中设置SIGEV_FLAG_UPDATEABLE标志。
  3. 客户端通过调用MsgRegisterEvent()来注册事件。这个函数会向内核注册一个sigevent,内核在内部存储事件,并为事件提供句柄。
  4. 为了防止传递未经注册的事件,客户端在调用ConnectAttach()连接到服务器时,会设置_NTO_COF_REG_EVENTS标志。
  5. 客户端会向服务器提供句柄而不是实际的事件。
  6. 如果客户端在事件上设置SIGEV_FLAG_UPDATEABLE标志,则允许服务器更新注册事件中所包含的值。
  7. 当服务器使用句柄调用MsgDeliverEvent()时,内核查找该句柄。如果句柄存在并且客户端已经允许服务器交付它,内核会把相应的注册事件交付给该客户端。
  8. 当客户端不再需要这个安全事件时,可以通过调用MsgUnregisterEvent()来删除它并注销句柄。

因此,客户端可以确保它只得到它想要的事件,并且未经篡改。有关示例程序,请参阅 C Library Reference 中的MsgRegisterEvent()项的内容。

默认情况下,在QNX Neutrino 7.1或更高版本中,你只能使用已注册的事件,但是你也可以为procnto使用-U选项,允许使用未经注册的事件。这个选项是一个临时的解决方案,你可以暂时使用,直到你已经转换你的程序使用注册事件;对于-U选项,将在未来的版本中删除。

你可以使用secpolgenerate(通过文件/dev/secpolgenerate/errors)或secpolmonitor(指定-u时)检测进程何时使用了未经注册的事件。

1.9.1. I/O通知

ionotify()函数是客户端线程请求异步事件传递的一种方式。

许多POSIX异步服务(例如mq_notify()以及poll()select()的客户端)都是构建在ionotify()之上的。当对文件描述符(fd)执行I/O操作时,线程可以选择等待I/O事件完成(对于write()情况),或者等待数据到达(对于read()情况)。ionotify()可以允许客户端线程在指定的I/O条件发生时向资源管理器发送一个客户端线程希望接收的事件,而不是让线程阻塞在为读/写请求提供服务的资源管理器进程上。以这种方式等待允许线程继续执行和响应事件源,而不仅仅是单个I/O请求。

select()调用就是使用I/O通知实现的,允许线程阻塞并等待多个fd上的一堆混合的I/O事件,同时可以继续响应其他形式的IPC。

以下是可以交付所请求事件的条件:

  • _NOTIFY_COND_OUTPUT,在输出缓冲区中有容纳更多数据的空间时。
  • _NOTIFY_COND_INPUT,资源管理器定义的数量的数据可供读取时。
  • _NOTIFY_COND_OBAND,资源管理器定义的“out of band”数据可用时。

未完,请继续看下一篇:《 2-2-18-3 进程间通信(IPC)(二)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星原飞火

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值