QNX下进程间通信
QNX是一个微内核的操作系统,所谓的微内核是指内核进程仅提供最基本的服务如内存管理,进程调度,信号,时钟,中断处理等,而其他的服务如文件系统,网络协议栈都是独立于内核以单独的进程来运行,它们与内核进程和其它进程之间通过内核提供的消息传递机制来进行通信。消息传递机制是QNX微内核提供的一种最基本的进程间通信服务,其它复杂的进程间通信机制大多基于消息传递来实现。
本文要讨论的话题是如何在两个独立的互不相识的两个进程间传递数据,例如进程A要给进程B传递一个版本号“V1.2.0”。分别采用了基本的同步消息传递和共享内存两种方式来实现。
同步消息传递
采用同步消息传递机制进行进程间通信的步骤如下:
1 server调用MsgReceive等待接收client发送的消息,若client没有发送消息则server阻塞;
2 client调用MsgSend发送消息到server,若server没有回复则client阻塞;
3 server接收到client发送的消息后处理消息,并调用MsgReply回复client;
4 client收到server的回复后进行处理,进程间通信结束。
以上接口函数由QNX内核提供,原型如下:
#include <sys/neutrino.h>
int MsgReceive( int chid,
void * msg,
int bytes,
struct _msg_info * info );
int MsgSend( int coid,
const void* smsg,
int sbytes,
void* rmsg,
int rbytes );
int MsgReply( int rcvid,
int status,
const void* msg,
int size );
进程间消息的传递通过通道(channel)连接,对于server(进程A)而言,它首先要调用ChannelCreate创建通道,然后调用MsgReceive接收消息,调用MsgReply回复消息。对于client(进程B)而言,它首先要调用ConnetAttach与server创建的channel建立连接,然后调用MsgSend发送消息。
include <sys/neutrino.h>
#include <sys/netmgr.h>
int ConnectAttach( uint32_t nd,
pid_t pid,
int chid,
unsigned index,
int flags );
int ChannelCreate( unsigned flags );
以上就是基于同步消息传递的进程间通信方法,但是这种方法有一定的局限性就是client(进程B)调用ConnectAttach时必须知道server(进程A)的nd, pid和chid,由于是不同的地址空间,进程B通常是无法获取这几个参数的(当然也不是完全没有办法)。所以我们采用另外一种更为常见合理的方法。
命名空间
server(进程A)调用name_attach创建一个通道并在命名空间为这个通道创建一个名字,client(进程B)调用name_open在命名空间打开server创建的通道与之建立连接。
#include <sys/iofunc.h>
#include <sys/dispatch.h>
name_attach_t * name_attach( dispatch_t * dpp,
const char * path,
unsigned flags );
int name_open( const char * name,
int flags );
由于命名空间对于进程A和进程B来说都是可见的,因此避免了进程B与进程A建立连接前需要知道nd,pid和chid等参数的问题。最终的参考代码如下:
server(进程A)
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/neutrino.h>
#include <sys/dispatch.h>
#define ATTACH_POINT "version"
#define VERSION "V1.2.0"
int rcvid = 0;
struct version_message
{
int type;
char data[100];
};
int main(int argc, char *argv[]) {
name_attach_t *attach;
struct version_message rmsg;
struct version_message smsg;
int rcvid;
/* Create a local name (/dev/name/local/...) */
if ((attach = name_attach(NULL, ATTACH_POINT, 0)) == NULL) {
printf("name_attach error!\n");
return EXIT_FAILURE;
}
while(1)
{
rcvid = MsgReceive(attach->chid, &rmsg, sizeof(rmsg), NULL);
if(rcvid > 0)
{
/* name_open() sends a connect message, must EOK this */
if (rmsg.type == _IO_CONNECT ) {
printf("connect received!\n");
MsgReply( rcvid, EOK, NULL, 0 );
continue;
}
/* Some other QNX IO message was received; reject it */
if (rmsg.type > _IO_BASE && rmsg.type <= _IO_MAX ) {
printf("wrong msg type received!\n");
MsgError( rcvid, ENOSYS );
continue;
}
/* reply the bsp version */
if(0x1 == rmsg.type)
{
printf("version request received!\n");
memcpy(smsg.data, BSP_VERSION, strlen(BSP_VERSION)+1);
MsgReply(rcvid, EOK, &smsg, sizeof(smsg));
}
}
else if(0 == rcvid)
{
printf("pulse msg received!\n");
}
}
/* Remove the name from the space */
name_detach(attach, 0);
return EXIT_SUCCESS;
}
client(进程B)
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <sys/mman.h>
#define ATTACH_POINT "version"
struct version_message
{
int type;
char data[100];
};
int main(int argc, char *argv[]) {
struct version_message smsg;
struct version_message rmsg;
int server_coid;
int rcvid;
if ((server_coid = name_open(ATTACH_POINT, 0)) == -1) {
printf("name_open error!");
return EXIT_FAILURE;
}
/* We would have pre-defined data to stuff here */
smsg.type = 0x01;
/* Do whatever work you wanted with server connection */
printf("Client sending %d \n", smsg.type);
if (MsgSend(server_coid, &smsg, sizeof(smsg), &rmsg, sizeof(rmsg)) == -1) {
printf("MsgSend error!");
return EXIT_FAILURE;
}
printf("version:%c%c%c%c%c%c\n", rmsg.data[0], rmsg.data[1], \
rmsg.data[2], rmsg.data[3], rmsg.data[4], rmsg.data[5]);
/* Close the connection */
name_close(server_coid);
}
提供命名空间服务的进程
需要注意的是,以上命名为version的通道被创建于/dev/name/local/version路径下,必须将named服务添加到qnx镜像中,通道才能被创建成功,否则/dev/name路径不存在,无法使用命名空间服务。
共享内存
另外一种比较高效的进程间通信方式是共享内存,对于效率要求较高,数据量较大的场合通常采用这种方式。采用共享内存进行进程间通信的步骤如下:
1 进程A调用shm_open创建共享内存对象;
2 进程A调用ftruncate设置共享内存大小;
3 进程A调用mmap将共享内存对象映射到自己的进程地址空间;
4 进程A访问进程地址空间来修改共享内存中的内容;
5 进程B调用shm_open打开共享内存对象;
6 进程B调用mmap将共享内存对象映射到自己的进程地址空间;
7 进程B访问进程地址空间来获取共享内存中的内容。
进程A:
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <sys/mman.h>
#define VERSION "V1.2.0"
int main( int argc, char** argv )
{
int fd;
unsigned char * addr;
/*
* In case the unlink code isn't executed at the end
*/
if( argc != 1 ) {
shm_unlink( "/version" );
return EXIT_SUCCESS;
}
/* Create a new memory object */
fd = shm_open( "/version", O_RDWR | O_CREAT, 0777 );
if( fd == -1 ) {
printf("Open failed:%s\n","/version");
return EXIT_FAILURE;
}
/* Set the memory object's size */
if( ftruncate( fd, 10) == -1 ) {
printf("ftruncate error\n");
return EXIT_FAILURE;
}
/* Map the memory object */
addr = mmap( 0, 10,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0 );
if( addr == MAP_FAILED ) {
printf("mmap failed\n");
return EXIT_FAILURE;
}
printf( "Map addr is 0x%08x\n", addr);
/* Write to shared memory */
memcpy(addr, VERSION, 10);
/*
* The memory object remains in
* the system after the close
*/
close( fd );
/*
* To remove a memory object
* you must unlink it like a file.
*
* This may be done by another process.
*/
/*shm_unlink( "/version" );*/
return EXIT_SUCCESS;
}
进程B:
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <sys/mman.h>
int main(int argc, char *argv[]) {
int fd;
unsigned char version[10];
unsigned char * addr;
/* Create a new memory object */
fd = shm_open( "/version", O_RDONLY, 0777 );
if( fd == -1 ) {
printf("Open failed:%s\n","/version");
return EXIT_FAILURE;
}
/* Map the memory object */
addr = mmap( 0, 10,
PROT_READ,
MAP_SHARED, fd, 0 );
if( addr == MAP_FAILED ) {
printf("mmap failed\n");
return EXIT_FAILURE;
}
printf( "Map addr is 0x%08x\n", addr);
/* READ shared memory */
memcpy(version, addr, 10);
printf("version: %c%c%c%c%c%c\n", version[0], version[1], \
version[2], version[3], version[4], version[5]);
/*
* The memory object remains in
* the system after the close
*/
close( fd );
return EXIT_SUCCESS;
}
需要注意的是,由于共享内存对象可能同时被进程A和进程B访问,属于临界区,因此需要考虑进程A和进程B之间的同步。本文例子中进程A对共享内存只写一次,进程B对共享内存只读,因此未考虑同步,实际应用场景比较复杂,是必须要考虑同步的。