文中的内容基本上来自这位大佬,写这篇文章只是为了做笔记,加深印象
# QNX Neutrino 进程间通信编程之Message-passing/Pulse
1.简介
在QNX中,消息可以分为同步消息和异步消息,同步消息需要消息接收者和发送者,异步消息类似于一种通知机制,QNX主要使用的是IPC进行通信,下面是QNX中支持的IPC通信: QNX内核特有的有IPC通信有:
- Message-passing(QNX消息)
- Pules(QNX脉冲)
- Signals(QNX消息)
- Event Delivery(QNX事件)
与UNIX共有的
- POSIX message queues(消息队列)
- 共享内存(share memory)
- 管道(pipe)
- FIFOs
- TCP/IP
本文只讨论QNX特有的IPC通信
2.QNX消息(Message-passing)
在QNX中的Message-passing通信是主从式,双向通信的,在每一个进程中都有一个线程来负责通信,当为收到回信时,线程会block住,直到收到回复信息
- 第一步:客户端使用MsgSend()给服务器端发送消息
- 第二步:服务器端使用MsgReceive接收来自客户端的消息
- 第三步:服务端使用MsgReply来向客户端发送消息
2.1 客户端线程状态
- 客户端线程调用了MsgSend()之后,如果服务器线程没有调用MsgReceive,客户端线程状态则为
SEND blocked
,一旦服务器线程调用了MsgReceive()
,客户端线程状态变为REPLY blocked
,当服务器线程执行MsgReply()
后,客户端线程状态就变成了READY
- 如果客户端线程调用了MsgSend()之后,服务器线程卡在MsgReceive上,则客户端线程直接跳过SEND blocked,直接变为REPLY blocked
- 服务器线程失败,退出或者消失之后,客户端线程会变成READY,此时MsgSend()会发出来一个错误值
2.2 服务器线程状态
- 服务器线程调用MsgReceive时,当没有其他线程发送消息时,他的状态为Reveive blocked,如果有线程给他发送消息则变为READY
- 服务器线程调用MsgReceive()时,如果没有其他线程给他发送消息,则立刻返回,不会阻塞
- 服务器线程调用MsgReply()时,不会阻塞
简单来说如果一个线程(客户端线程)执行了MsgSend()向另一个线程(服务器线程)发送消息,发送消息的线程就会阻塞,直到另一个线程执行了MsgRecevice并且处理了消息,然后执行MsgReply,服务器线程在客户端线程执行MsgSend之前执行了MsgRecevice,他会阻塞到时另一个线程执行MsgSend()才解除阻塞状态
2.3 Channels and connections
在QNX的消息传递是通过Channels和Connections实现的,并不是通过线程间传递,一个server线程要接收消息,必须先生成一个Channels,一个client线程要发送消息,必须要依附于(Attaching) Channel生成一个Connection
一个进程中的多个客户线程可以同时依附于一个服务线程创建的Channel上,这时所有Connection实际上都会映射到同一内核对象
简单来说服务线程会创建一个channel(频道),而客户线程会依附于这个Channel创建一个Connection,从而来与服务线程进行通信
2.4 QNX中的消息传递
在QNX中通信数据总是通过拷贝,而不是通过指针的传递进行的,所以需要定义消息策略
那么如何设计消息传递策略呢?
- 定义公共消息头消息类型结构体
- 所有消息都是同一个消息类型
- 具有匹配每个消息类型的结构
- 如果消息相关或它们使用共同的结构,请考虑使用消息类型和子类型
- 定义匹配的回复结构体。如果合适,避免不同类型服务器的消息类型重叠
复制代码
伪代码实现可帮助理解:
while(1) {
recvid = MsgReceive( chid, &msg, sizeof(msg), NULL );
switch( msg.hdr.type ) {
case MSG_TYPE_1:
handle_msg_type_1(rcvid, &msg);
break;
case MSG_TYPE_2:
…
}
}
复制代码
2.5 QNX消息的编程流程
creates a channel (ChannelCreate())
waits for a message (MsgReceive())
performs processing
sends reply (MsgReply())
goes back for more -> waits for a message (MsgReceive())
– Client:
attaches to channel (ConnectAttach())
sends message (MsgSend())
processes reply
服务端线程:
- 先创建一个channel
- 等待客户端线程发送消息
- 处理消息
- 返回消息给客户端线程
客户端线程:
- 与服务端进程创建的channel进行连接
- 发送消息
- 处理回复的消息
2.5 name_open与name_attach
name_attach():是在服务器端使用,在名称空间中定义一个name(客户端中对应open这个name) ,同时创建了一个channel. 函数原型为:
name_attach_t * name_attach( dispatch_t * dpp,
const char * path, //对应name
unsigned flags );
typedef struct _name_attach {
dispatch_t* dpp;
int chid; //对应 channel id
int mntid;
int zero[2];
} name_attach_t;
复制代码
name_open在客户端使用,通过打开服务端的name来找到服务端进程,
2.6 其他函数原型
int ChannelCreate( unsigned flags );
int ChannelDestroy( int chid );
int name_open( const char * name, int flags );
int name_close( int coid );
int ConnectAttach( uint32_t nd, pid_t pid, int chid, unsigned index, int flags );
int ConnectDetach( int coid );
name_attach_t * name_attach( dispatch_t * dpp, const char * path, unsigned flags );
int name_detach( name_attach_t * attach, unsigned flags );
long MsgSend( int coid, const void* smsg, size_t sbytes, void* rmsg, size_t rbytes );
int MsgReceive( int chid, void * msg, size_t bytes, struct _msg_info * info );
int MsgReply( int rcvid, long status, const void* msg, size_t bytes );
ssize_t MsgWrite( int rcvid, const void* msg, size_t size, size_t offset );
ssize_t MsgRead( int rcvid, void* msg, size_t bytes, size_t offset );
复制代码
详细命令为: 可以查看下面链接查看函数定义: www.qnx.com/developers/…
Function Description
ChannelCreate() Create a channel to receive messages on.
ChannelDestroy() Destroy a channel.
ConnectAttach() Create a connection to send messages on.
ConnectDetach() Detach a connection.
name_open() Open a name to connect to a server
name_close() Close a server connection that was opened by name_open()
name_attach() Register a name in the pathname space and create a channel
name_detach() Remove a name from the namespace and destroy the channel
MsgSend() Send a message and block until reply.
MsgSendv() Send a message to a channel
MsgReceive() Wait for a message.
MsgReceivev() Wait for a message or pulse on a channel
MsgReceivePulse() Wait for a tiny, nonblocking message (pulse).
MsgReply() Reply to a message.
MsgError() Reply only with an error status. No message bytes are transferred.
MsgRead() Read additional data from a received message.
MsgReadv() Read data from a message
MsgWrite() Write additional data to a reply message.
MsgWritev() Write a reply message
MsgInfo() Obtain info on a received message.
MsgSendPulse() Send a tiny, nonblocking message (pulse).
MsgDeliverEvent() Deliver an event to a client.
MsgKeyData() Key a message to allow security checks.
复制代码
重要的函数原型:
2.7 channel与connect的实例代码
服务端:这个服务器,准备好频道后,就从频道上接收信息。如果信息是字符串”Hello“的话,这个服务器应答一个”World“字符串。如果收到的信处是字符串“Ni Hao", 那么它会应答”Zhong Guo",其它任何消息都用MsgError()回答一个错误。
// Simple server
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/neutrino.h>
int main()
{
int chid, rcvid, status;
char buf[128];
if ((chid = ChannelCreate(0)) == -1) {
perror("ChannelCreate");
return -1;
}
printf("Server is ready, pid = %d, chid = %d\n", getpid(), chid);
for (;;) {
if ((rcvid = MsgReceive(chid, buf, sizeof(buf), NULL)) == -1) {
perror("MsgReceive");
return -1;
}
printf("Server: Received '%s'\n", buf);
/* Based on what we receive, return some message */
if (strcmp(buf, "Hello") == 0) {
MsgReply(rcvid, 0, "World", strlen("World") + 1);
} else if (strcmp(buf, "Ni Hao") == 0) {
MsgReply(rcvid, 0, "Zhong Guo", strlen("Zhong Guo") + 1);
} else {
MsgError(rcvid, EINVAL);
}
}
ChannelDestroy(chid);
return 0;
}
复制代码
客户端:这个客户端会向服务端发送Hello,会收到回复world,如果收到字符串Ni Hao,会收到回复Zhong Guo,其他任何消息都会收到错误
//simple client
#include <stdio.h>
#include <string.h>
#include <sys/neutrino.h>
int main(int argc, char **argv)
{
pid_t spid;
int chid, coid, i;
char buf[128];
if (argc < 3) {
fprintf(stderr, "Usage: simple_client <pid> <chid>\n");
return -1;
}
spid = atoi(argv[1]);
chid = atoi(argv[2]);
if ((coid = ConnectAttach(0, spid, chid, 0, 0)) == -1) {
perror("ConnectAttach");
return -1;
}
/* sent 3 pairs of "Hello" and "Ni Hao" */
for (i = 0; i < 3; i++) {
sprintf(buf, "Hello");
printf("client: sent '%s'\n", buf);
if (MsgSend(coid, buf, strlen(buf) + 1, buf, sizeof(buf)) != 0) {
perror("MsgSend");
return -1;
}
printf("client: returned '%s'\n", buf);
sprintf(buf, "Ni Hao");
printf("client: sent '%s'\n", buf);
if (MsgSend(coid, buf, strlen(buf) + 1, buf, sizeof(buf)) != 0) {
perror("MsgSend");
return -1;
}
printf("client: returned '%s'\n", buf);
}
/* sent a bad message, see if we get an error */
sprintf(buf, "Unknown");
printf("client: sent '%s'\n", buf);
if (MsgSend(coid, buf, strlen(buf) + 1, buf, sizeof(buf)) != 0) {
perror("MsgSend");
return -1;
}
ConnectDetach(coid);
return 0;
}
复制代码
结果: 服务器端:
$ ./simple_server
Server is ready, pid = 36409378, chid = 2
Server: Received 'Hello'
Server: Received 'Ni Hao'
Server: Received 'Hello'
Server: Received 'Ni Hao'
Server: Received 'Hello'
Server: Received 'Ni Hao'
Server: Received 'Unknown'
Server: Received ''
复制代码
客户端:
$ ./simple_client 36409378 2
client: sent 'Hello'
client: returned 'World'
client: sent 'Ni Hao'
client: returned 'Zhong Guo'
client: sent 'Hello'
client: returned 'World'
client: sent 'Ni Hao'
client: returned 'Zhong Guo'
client: sent 'Hello'
client: returned 'World'
client: sent 'Ni Hao'
client: returned 'Zhong Guo'
client: sent 'Unknown'
MsgSend: Invalid argument
复制代码
2.7 name_open与name_attach实例
客户端线程:
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/dispatch.h>
#define ATTACH_POINT "myname"//服务器线程通过name_attach创建的名字
/* We specify the header as being at least a pulse */
typedef struct _pulse msg_header_t;、、消息头
/* Our real data comes after the header */
typedef struct _my_data {
msg_header_t hdr;
int data;
} my_data_t;
/*** Client Side of the code ***/
int client()
{
my_data_t msg;
int msg_reply;
int server_coid;
if ((server_coid = name_open(ATTACH_POINT, 0)) == -1) {
printf("client name open failed\n");
return EXIT_FAILURE;
}
/* We would have pre-defined data to stuff here */
msg.hdr.type = 0x00;//消息类型,区别发送了哪种消息
msg.hdr.subtype = 0x00;
msg.data = 1;
/* Do whatever work you wanted with server connection */
printf("client name open success, Client sending msg %d \n", msg.data);
if (MsgSend(server_coid, &msg, sizeof(msg), &msg_reply, sizeof(msg_reply)) == -1) {
printf("client send msg 1 error\n");
}
printf("client receive msg 1 reply: %d \n", msg_reply);
msg.hdr.type = 0x00;
msg.hdr.subtype = 0x01;
msg.data = 2;
printf("client name open success, Client sending msg %d \n", msg.data);
if (MsgSend(server_coid, &msg, sizeof(msg), &msg_reply, sizeof(msg_reply)) == -1) {
printf("client send msg 2 error\n");
}
printf("client receive msg 2 reply: %d \n", msg_reply);
/* Close the connection */
name_close(server_coid);
return EXIT_SUCCESS;
}
int main(int argc, char *argv[]) {
int ret;
if (argc < 2) {
printf("Usage %s -s | -c \n", argv[0]);
ret = EXIT_FAILURE;
}
else if (strcmp(argv[1], "-c") == 0) {
printf("Running client ... \n");
ret = client();
}
else {
printf("Usage %s -s | -c \n", argv[0]);
ret = EXIT_FAILURE;
}
return ret;
}
复制代码
服务器线程:
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/dispatch.h>
#define ATTACH_POINT "myname"
/* We specify the header as being at least a pulse */
typedef struct _pulse msg_header_t;
/* Our real data comes after the header */
typedef struct _my_data {
msg_header_t hdr;
int data;
} my_data_t;
int msg_update_fail =3;
int msg_update_success =4;
/*** Server Side of the code ***/
int server() {
name_attach_t *attach;
my_data_t msg;
my_data_t msg_reply;
msg_reply.hdr.type = 0x00;
msg_reply.hdr.subtype = 0x00;
// my_data_t msg_replaydata;
int rcvid;
/* Create a local name (/dev/name/local/...) */
if ((attach = name_attach(NULL, ATTACH_POINT, 0)) == NULL) {
printf("server name_attach error\n");
return EXIT_FAILURE;
}
printf("server name_attach suceess,wait masg from client\n");
/* Do your MsgReceive's here now with the chid */
while (1) {
rcvid = MsgReceive(attach->chid, &msg, sizeof(msg), NULL);
if (rcvid == -1) {/* Error condition, exit */
break;
}
/* A message (presumable ours) received, handle */
switch(msg.data){
case 1:
printf("Server receive msg data %d \n", msg.data);
MsgReply(rcvid, EOK, &msg_update_fail, sizeof(msg_update_fail));
//MsgReply(UpdateReceiveId, EOK, &msg_update_fail, 0);
break;
case 2:
printf("Server receive msg data %d \n", msg.data);
MsgReply(rcvid, EOK, &msg_update_success, sizeof(msg_update_success));
break;
default:
break;
}
MsgReply(rcvid, EOK, 0, 0);
}
/* Remove the name from the space */
name_detach(attach, 0);
return EXIT_SUCCESS;
}
int main(int argc, char **argv) {
int ret;
if (argc < 2) {
printf("Usage %s -s | -c \n", argv[0]);
ret = EXIT_FAILURE;
}
else if (strcmp(argv[1], "-s") == 0) {
printf("Running Server ... \n");
ret = server();
}
else {
printf("Usage %s -s | -c \n", argv[0]);
ret = EXIT_FAILURE;
}
return ret;
}
复制代码
用name_attach与MsgReceive,MsgReply实现消息传递的服务器线程
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/dispatch.h>
#define ATTACH_POINT "myname"
/* We specify the header as being at least a pulse */
typedef struct _pulse msg_header_t;
/* Our real data comes after the header */
typedef struct _my_data {
msg_header_t hdr;
int data;
} my_data_t;
int msg_update_fail =3;
int msg_update_success =4;
/*** Server Side of the code ***/
int server() {
name_attach_t *attach;
my_data_t msg;
my_data_t msg_reply;
msg_reply.hdr.type = 0x00;
msg_reply.hdr.subtype = 0x00;
// my_data_t msg_replaydata;
int rcvid;
/* Create a local name (/dev/name/local/...) */
if ((attach = name_attach(NULL, ATTACH_POINT, 0)) == NULL) {
printf("server name_attach error\n");
return EXIT_FAILURE;
}
printf("server name_attach suceess,wait masg from client\n");
/* Do your MsgReceive's here now with the chid */
while (1) {
rcvid = MsgReceive(attach->chid, &msg, sizeof(msg), NULL);
if (rcvid == -1) {/* Error condition, exit */
break;
}
/* A message (presumable ours) received, handle */
switch(msg.data){
case 1:
printf("Server receive msg data %d \n", msg.data);
MsgReply(rcvid, EOK, &msg_update_fail, sizeof(msg_update_fail));
//MsgReply(UpdateReceiveId, EOK, &msg_update_fail, 0);
break;
case 2:
printf("Server receive msg data %d \n", msg.data);
MsgReply(rcvid, EOK, &msg_update_success, sizeof(msg_update_success));
break;
default:
break;
}
MsgReply(rcvid, EOK, 0, 0);
}
/* Remove the name from the space */
name_detach(attach, 0);
return EXIT_SUCCESS;
}
int main(int argc, char **argv) {
int ret;
if (argc < 2) {
printf("Usage %s -s | -c \n", argv[0]);
ret = EXIT_FAILURE;
}
else if (strcmp(argv[1], "-s") == 0) {
printf("Running Server ... \n");
ret = server();
}
else {
printf("Usage %s -s | -c \n", argv[0]);
ret = EXIT_FAILURE;
}
return ret;
}
复制代码
3. 如果您想在一条消息中发送以下三个缓冲区怎么办?
你可能通过三次memcpy()
处理一个大的buffer。但是有个更有效的方法通过MsgSendv()
传递指针数组。 使用的函数原型为:
long MsgSendv( int coid, const iov_t* siov, size_t sparts, const iov_t* riov, size_t rparts );
int MsgReceivev( int chid, const iov_t * riov, size_t rparts, struct _msg_info * info );
ssize_t MsgReadv( int rcvid, const iov_t* riov, size_t rparts, size_t offset );
void SETIOV( iov_t *msg, void *addr, size_t len );
复制代码
typedef struct {
void *iov_base;
size_t iov_len;
} iov_t;
iov_t iovs [3];
复制代码
实际IOVs应用实例,客户端需要将一个12KBytes的文件传给服务器端。
客户端伪代码如下:
write (fd, buf, size);
effectively does:
hdr.nbytes = size;
SETIOV (&siov[0], &header, sizeof (header));
SETIOV (&siov[1], buf, size);
MsgSendv (fd, siov, 2, NULL, 0);
复制代码
实际上获取的是连续的字节流:
服务端接收到的内容为:
// assume riov has been setup
MsgReceivev (chid, riov, 4, NULL);
复制代码
实际上我们是不知道接收的数据是多少,直到我们获取到数据流的头。
rcvid = MsgReceive (chid, &header,sizeof (header), NULL);
//获取到数据流的头信息后
SETIOV (iov [0], &cbuf [6], 4096);
SETIOV (iov [1], &cbuf [2], 4096);
SETIOV (iov [2], &cbuf [5], 4096);
//用MsgReadv直接根据sizeof(header)偏移把剩下的数据获取出来。
MsgReadv (rcvid, iov, 3, sizeof(header));
复制代码
整个消息传递从客户端到服务器,你可以理解为下面两个流程
那怎么理解从服务器端拷贝数据给客户端呢?
ssize_t MsgWrite( int rcvid, const void* msg, size_t size, size_t offset );
ssize_t MsgWritev( int rcvid, const iov_t* iov, size_t parts, size_t offset );
复制代码
MsgWrite回传数据实例如下:
4. Pulses脉冲
脉冲其实更像一个短消息,也是在“连接Connection”上发送的。脉冲最大的特点是它是异步的。发送方不必要等接收方应答,直接可以继续执行。
脉冲的通信方式很特别,就像喊命令,不需要回应,执行就好了。便宜还快速,也不会发生blocking的现象。但是,这种异步性也给脉冲带来了限制。脉冲能携带的数据量有限,只有一个8位的"code"域 (1byte)用来区分不同的脉冲,和一个32位的“value"域 (4字节)来携带数据。脉冲最主要的用途就是用来进行“通知”(Notification)。不仅是用户程序,内核也会生成发送特殊的“系统脉冲”到用户程序,以通知某一特殊情况的发生。
int MsgSendPulse ( int coid, int priority, int code, int value );
| |
8bits <---| |
32bits <-------------|
复制代码
-
code 通常用于表示“脉冲类型”的有效范围是 _PULSE_CODE_MINAVAIL 到 _PULSE_CODE_MAXAVAIL。
-
priority 就像发送线程的消息优先级一样,接收线程以该优先级运行
-
发送顺序基于优先级
-
要跨进程边界发送脉冲,发送者必须与接收者具有相同的有效用户 ID 或者是 root 用户
脉冲的接收比较简单,如果你知道频道上不会有别的消息,只有脉冲的话,可以用MsgReceivePulse()来只接收脉冲
; 如果频道既可以接收消息,也可以接收脉冲时,就直接用MsgReceive(),
只要确保接收缓冲(ReveiveBuf)至少可以容下一个脉冲(sizeof struct _pulse)
就可以了。 在后一种情况下,如果MsgReceive()返回的rcvid是0,就代表接收到了一个脉冲,反之,则收到了一个消息
。所以,一个既接收脉冲,又接收消息的服务器,Pulses脉冲实例伪代码如下:
#include <sys/neutrino.h>
struct _pulse {
uint16_t type;
uint16_t subtype;
int8_t code; // <---- 8-bit code
uint8_t zero[3];
union sigval value; // <--- 32-bit value
int32_t scoid;
};
typedef union {
struct _pulse pulse;
// other message types you will receive
} myMessage_t;
…
myMessage_t msg;
while (1) {
rcvid = MsgReceive (chid, &msg, sizeof(msg), NULL);
if (rcvid == 0) {
// it’s a pulse, look in msg.pulse… for data
process_pulse(&msgs, &info);
} else {
// it’s a regular message
process_message(&msgs, &info);
}
}
…
复制代码
展开process_pulse处理实现
...
rcvid = MsgReceive (chid, &msg, sizeof(msg), NULL);
if (rcvid == 0) {
// it’s a pulse, look in msg.pulse… for data
switch (msg.pulse.code) {
case _PULSE_CODE_UNBLOCK:
// a kernel unblock pulse
...
break;
case MY_PULSE_CODE:
// do what's needed
...
break;
} else {
process_message(&msgs, &info);
}
复制代码
脉冲的发送,最直接的就是MsgSendPulse()。不过,这个函数通常只在一个进程中,用在一个线程要通知另一个线程的情形。在跨进程的时候,通常不会用到这个函数,而是用到下面将要提到的 MsgDeliverEvent()。与消息传递相比,消息传递永远是在进程间进行的。也就是说,不会有一个进程向内核发送数据的情形。而脉冲就不一样,除了用户进程间可以发脉冲以外,内核也会向用户进程发送“系统脉冲”来通知某一事件的发生。
如果您有一个频道,您可能会在该频道上接收来自 MsgSend*() 调用和脉冲的消息,但在某个时间点只想接收脉冲,使用MsgReceivePulse()则很有用。
int MsgReceivePulse( int chid, void * pulse, size_t bytes, struct _msg_info * info );
复制代码
如果进程正在接收消息和脉冲:
- 接收顺序仍然基于优先级,使用脉冲的优先级
- 内核将以其接收到的脉冲的优先级运行接收线程
- 脉冲和消息可能会混合在一起
来自线程 1 的Send Pulse比在它Send message之前先到达服务器。因为脉冲的优先级比线程本身的优先级要高。
Pulse API | send | receive |
---|---|---|
MsgSendPulse(coid, priority, code, value) | MsgReceivePulse() | |
MsgDeliverEvent() | MsgReceive() |
- MsgSendPulse() 只在一个进程中的通知,用与同一个进程中一个线程要通知另一个线程的情形, 其中 code 8bits; value 32bits
- MsgDeliverEvent() 在跨进程的时候的通知
- MsgReceivePulse() 用于频道上只有pulse的接收
- MsgReceive() 用于频道上既接收message又接收pulse
———————————————— 版权声明:本文为CSDN博主「背包旅行码农」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/zhuwade/art…