在QNX Neutrino中消息传递(Message passing)是IPC的主要形式,其他的姓氏也是基于消息传递实现的。QNX中提供的IPC形式如何下图所示:
一、Synchronous message passing 同步消息传递
如果一个线程执行了MegSend()方法向另一个线程(可以是不同的进程)发送消息,它会被阻塞,知道目标线程执行了MsgReceive(),并处理消息,然后执行MsgReply()。如果一个线程在其他线程执行了MsgReceive(),它会被阻塞到另一个线程执行MsgSend()。消息查undishi通过直接你存copy来实现的。如果需要大的消息传递时建议通过共享内存或其他方式实现。
1、消息传递中的状态迁移
客户程序的状态迁移
SEND blocked:调用MsgSend()后,服务程序没有调用MsgReceive()的状态。
REPLY blocked:调用MsgSend()后,并且服务程序调用了MsgRecive(),但是没有调用MsgReply()/MsgError()的状态。当服务程序已经调用了MsgReceive(),客户程序一旦调用MsgSend()就直接迁移到此状态。
READY:调用MsgSend()后,并且服务程序地调用了MsgReceive()和MsgReply()的状态。
服务器状态迁移:
RECEIVE blocked:调用MsgRecevie()后,客户程序没有调用MsgSend()时的状态
READY:消息处理时的状态。MsgReply()调用后不会阻塞。当服务器MsgSend()会返回一个错误,不会被阻塞。
2、Channels and connections 通道与连接
在QNX Neutrino 中消息传递时通过Channels和Connections的,而不是直接在线程之间传递。一个要接受消息的线程必须先生成一个通道,而一个要发送消息的线程必须依附通道生成一个连接。一个进程中的多个客户线程可以同时依附一个通道上,这时所有连接实际都会映射到同内核对象。
客户连接直接映射到文件描述符。这样客户线程不需要了解什么地方发送消息,而仅仅需要考虑向文件描述符中发送消息。
一个Channel中包含以下三种列表:
Receive:一个用来保存等待罅隙的线程的后进先出队列(LIFO)
Send:一个用来保存发送但是未被接受的优先级先进先出队列
Reply:一个用来保存已被接受但是还没有返回的不排序列表
所有这些队列中等待的线程都是被阻塞的线程,多个服务线程和多个客户线程可以等待在一个Channel上。
3、Pulses 脉冲
QNX Neutrino同样提供了一个被称为Pulses的非同步的消息机制。Pulses通常用于中断线程的通知机制,或者通过服务线程来唤醒客户而不被阻塞。
Pulses包是相当于最小负载单位,有8 bits的代码和32 bits的数据。
4、Priority inheritance and messages
当服务线程接受消息时会将服务线程的优先级调整为发送者线程中的最高优先级。如果有一下三个线程,服务线程的优先级为22,客户线程1的优先级为13,客户线程2的优先级为10。客户线程2发送一条消息的话,服务线程在接受时会将自身的优先级调整为10。如果此时客户线程1也发送了一条消息的话,服务线程的优先级就会调整为13。
这种优先级继承机制的目的是为了防止一下两种优先级逆转:
当服务线程的优先级较高时,不会因为低优先级的客户线程的消息而抢占比起高优先级线程
当服务线程的优先级较低时,一个较高优先级的线程的消息不会被一个较低优先级线程锁抢占
当然也可以关闭优先级继承功能,在ChannelCreate()是关闭此标志: _NTO_CHF_FIXED_PRIORITY
5、Robust implementations with Send/Receive/Reply
通过Send/Receive/Reply实现的程序能够通过一下两点避免死锁
永远不要有两个线程相互发送消息
将线程组织为等级结构,并且只向上发送消息
二、其他IPC机制
1、Events 事件
QNX Neutrino提供的一种非同步事件发送机制。一下三种情况会发送Events
MsgDeliverEvent()被调用
中断处理
计时器到期
2、I/O notification
最有代表性的就是select()函数,主要当下面条件成立时发送此事件:
3、Signals 信号
信号处理类似于硬件中断,他会使一个进程从当前的执行控制流程跳出,以实现特定的行为,待特定处理完成后,再恢复到中断点继续执行。QNX Neutrino 中的信号时通过Events机制实现的,应该注意,虽然QNX遵循POSIX,但是使用信号的函数有点细微的变化;QNX Neutrino系统中提供了如下64个Signals:
当一个服务线程希望通知一个客户线程一些信息时,可以使用两种手段:
Pulses:客户线程需要生产一个Channel,并调用MsgReceive()
Signals:只需要调用sigwaitinfo(),而不需要生产Channel。
4、POSIX message queues
POSIX通过message queues定义一组非阻塞的消息传递机制。QNX Neutrino的系统核心不支持Message queues,如果想使用它就必须启动对应的服务。QNX Neutrino提供了两种Message queues的实现:
・mqueue:一个传统的使用mqueue资源管理的实现
・mq:一个使用mq服务和非同步消息的替代实现
QNX的Message机制与POSIX的Message queues有一个根本性的区别,QNX的消息机制通过内存拷贝来实现消息的传递,而POSIX的消息队列通过将消息队列的存取来实现消息的传递,POSIX可以维护多了消息队列。QNX的消息机制比POSIX的消息队列更有效率,但是有时为了POSIX的灵活性,这点性能消耗也是值得的。
消息队列的功能有:
5、Shared memory 共享内存
由于Share memory 不支持同步处理,所以通常和Semaphores或Mutexes一起使用。一般来说信号量用于进程同步,而互斥量用于线程同步。QNX 的互斥量也支持进程的同步,相对而言互斥效率更高一些。
使用Share memory时,要创建一个Share memory对象,在使用内存共享时要用到的函数有:
QNX中的消息传递时通过消息的直接拷贝来完成的,当消息较大时可以通过内存共享来提升消息,发送消息时,不是发送整个消息,而是将消息保存于Share memory中,并将地址发送过去,而接受消息时通过Share memory的地址来读取消息。
The mmap() function is defined as follows:
void * mmap( void *where_i_want_it,
size_t length,
int memory_protections,
int mapping_flags,
int fd,
off_t offset_within_shared_memory );
6、Typed memory 类型化内存
POSIX也已经定义,主要是针对实行性的,在这里用到了池(pool)的概念,想使用此功能要包含的头文件是 <sys/mman.h>
Typed memory 增加的功能在 C库里:
posix_typed_mem_open():打开一个Typed memory对象,返回的是文件描述符,可通过mmap()来创建此对象
posix_typed_mem_get_info():得到该对象的信息
实际例子:
int fd = posix_typed_mem_open( "/memory/ram/sysram", O_RDWR,
POSIX_TYPED_MEM_ALLOCATE_CONTIG);
unsigned vaddr = mmap( NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE, fd, 0);
7、Pipes and FIFOs
Pipes
Pipes是两个活着多个协作进程间未命名的IO通道。Pipes通常用于两个平行进程间的单向通信,如果想双向就应该使用消息。
FIFOs FIFOs是文件系统中一个已经命名的永久文件,实现功能与管道出不多