进程间通信的通道与连接
在UNIX系统中,消息传递是沿着通道与连接传送的,而不是直接的从线程到线程。一个线程要接收消息就先要创建一个通道,另外一个线程如果想要向这个线程传递消息就需要创建一个连接附加到该通道上。
通道是消息内核调用所必须的,服务器线程在调用MsgReceive()函数就是使用这个通道来接收消息的。连接由客户线程创建并用于“连接”服务器线程创建的可用通道。一旦连接确定,客户线程就可以使用MsgSend()函数发送消息了。如果一个进程中有多个线程连接到同一个通道,为了效率所有的连接就被映射到同一个内核对象。在一个进程中的通道与连接是使用一个小整数进行命名的。客户连接直接映射为文件描述符。
从架构上来说,这是一个关键点。通过将客户连接直接映射为文件描述符,就消除了使用另一层变换的必要。我们不必“指出”根据文件描述符向哪里发送消息。相反,我们可以很简单的将消息直接发送给“文件描述符”(也就是连接ID)。
这里可以使用的函数如下:
ChannelCreate() 创建一个通道来接收消息;
ChannelDestroy() 清除一个通道;
ConnectAttach() 创建一个连接来发送消息;
ConnectDetach() 释放一个连接。
作为服务器端的进程可以使用事件循环来接收与处理消息,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 | 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 ); } |
在循环中,线程可以接受连接到通道的所有线程的消息。
通道有几个与其关联的消息列表:
- 接收:一个LIFO的等待消息的线程队列;
- 接收:一个有优先权的线程FIFO队列,这些线程发送了消息当还未被接收;
- 回复:一个无序的线程列表,这些线程发送的消息已被接受但还未被回复。
在上面的任何一个列表中的等待队列都是阻塞状态(RECEIVE-、SEND-或REPLY-阻塞)。多个线程或客户端可能在等待同一个通道。
脉冲
除了同步的发送/接收/回复服务外,操作系统也提供了规定大小、非阻塞的消息。这种消息一般被称为脉冲并有小的体积(四个字节的数据和一个单字节的代码)。
脉冲的载荷相对来说很小——八位的代码以及32位的数据。脉冲常被用于中断处理器中的通知机制。也可以用于服务器端通知客户端而不阻塞它们。
一个脉冲的示意图如下:
优先级继承与消息
服务器进程按照优先级来接收消息与脉冲。当服务器进程中的线程接收到请求之后,它们就继承了发送消息的线程的优先级,而不是调度算法的优先级。这样,请求服务器端操作的线程的相对优先级就被保存了,服务器端的操作就会按照合适的优先级运行。这种消息驱动的优先级继承避免了优先级反转的问题。
例如,假如系统包含了如下线程:
- 一个服务器线程,优先级为22;
- 一个客户线程,T1,优先级为13;
- 一个客户线程,T2,优先级为10.
如果没有优先级继承,如果T2向服务器线程发送了一个消息,它所请求的动作就会在优先级22上执行,这样的话T2的优先级就被反转了。
实际上发生的是,当服务器端接收到一个消息,它的有效优先级就变为最高优先级发送者的优先级。在这里,T2的优先级要低于服务器端的优先级,这样当服务器端接收到消息之后其有效优先级就会发生变化。
接着,假设T1在服务器端的优先级还在10的时候发送了一个消息。由于T1的优先级要比服务器端的当前优先级高,当T1发送消息的时候服务器端的优先级就发生改变。
在服务器端接收到消息之前就改变优先级是为了防止又一次优先级反转的情况。如果服务器端的优先级保持低于10,而另一个线程T3开始以优先级11运行,服务器就要等待T3直到其能够有CPU时间来使其最终能够接收到T1的消息。这样的话,T1就会被低优先级的T3线程所延迟。
在调用ChannelCreate()函数的时候,可以通过设定——NTO_CHF_FIXED_PRIORITY标志来关闭优先级继承。如果使用自适应分块,这个标志将导致接收线程不在发送线程的分块中运行。
Related posts:
最近评论