linux/unix系统编程--进程通信--System V 消息通信

145 阅读7分钟

system V IPC

三种不同的进程间通信机制

消息队列:与管道有点像,但存在两个重大差别。第一消息队列是存在边界的,读写者之间以消息进行通信,而不是通过无分隔符的字节流。每条消息包括一个整型的type字段,并且可以通过类型选择消息儿无需以消息被写入的顺序来读取消息

信号量: 进程同步。一个信号量是由一个内核维护的整数值,它对所有具备相应权限的进程可见

共享内存: 使得多个进程能共享内存(被映射到多个进程的虚拟内存的页帧)的同一块区域(段) 共享内存是一种速度最快的IPC方法:一个进程一旦更新了共享内存,那么这个变更会立即对共享同一个内存段的其他进程可见

接口		消息队列		信号量:	共享内存 
头文件		<sys/msg.h>		<sys/sem.h>	<sys/shm.h>
关联数据结构 msqid_ds		semid_ds	shmid_ds 
创建/打开对象 msgget()		semget()	shmget()+shmat()
关闭对象	   无				无		shmdt()
控制操作	msgctl()		semctl()	shmctl()
执行IPC		msgsnd()写入消息 semop()测试/调整信号量
			msgrcv()接收消息			访问共享区域中的内存

创建和打开一个system V IPC对象

每个system V IPC机制都有一个相关的get系统调用(msgget() semget() shmget()),给定一个证书key,get调用完成以下某个操作:

使用给定的key创建一个新IPC对象并返回一个唯一的标识符来标识该对象

返回一个拥有给定key的既有IPC对象的标识符

文件描述符 VS IPC标识符

文件描述符是一个进程特性,IPC标识符是对象本身的一个属性并且对全局可见。所有访问同一对象的进程使用同样的标识符

创还能对象的进程可能会将标识符写入一个可供其他进程读取的文件

//创建一个system V消息队列
id = msgget(key, IPC_CREAT | S_IRUSR | S_IWUSR);
if(id==-1)
	errExit("msgget")

所有需访问同一个IPC对象的进程在执行get调用时会指定同样的key以获取该对象的同一标识符

一个进程可以通过指定IPC_EXCL标记来确保他是创建IPC对象的进程;如果指定了IPC_EXCL并且与给定key对应的IPC已存在,get调用失败

IPC对象删除和对象持久

IPC_RMID删除一个对象

//删除一个共享内存对象
if(shmctl(ID, IPC_RMID, NULL)==-1)
	errExit("shmctl");

对于消息队列和信息量来讲,IPC对象的删除是立即生效的,对象中包含的所有信息都会被销毁,不管是否有其他进程仍然在使用该对象

共享内存对象的删除操作是不同的,在shmctl(id, IPC_RMID,NULL)调用之后,只有当所有使用该内存段的进程与该内存段分离之后(使用shmdt())才会删除该共享内存段

system V IPC具有内核持久性。一旦被创建之后,一个对象就一直存在直达它被显示删除或系统被关闭

IPC key

system V IPC key是一个整数,数据类型是key_t, IPC get将调用一个key转换成相应的整数IPC标识符 如何产生唯一的key:

随机选取,可能会选取已使用的值

在get调用中将IPC_PRIVATE作为key的值,保证每个对象都拥有一个唯一的key

ftok()生成一个(接近唯一)key

key_t ftok(char*pathname, int proj); ftok()使用i-node号来生成key

关联数据结构和对象权限

内核为System V IPC对象的每个实例都维护着一个关联数据结构

一个IPC对象的关联数据结构会在通过相应的get系统调用创建对象时进程初始化。对象一旦被创建之后,程序就可以通过指定IPC_STAT操作类型使用合适的ctl系统调用来获取这个数据结构的一个副本

这三种IPC机制的关联数据结构都包含一个子结构ipc_perm保存了用于确定对象之上的权限的信息

struct ipc_perm{
	key_t __key;
	uid_t uid;
	gid_t gid;
	uid_t cuid;
	git_t cgid;
	unsigned short mode;
	unsigned short __seq;
};

修改共享内存段的uid字段

struct shmid_ds shmds;
if(shmctl(id, IPC_STAT, &shmds)==-1)
	errExit("shmctl")
shmds.shm_perm.uid = newuid;
if(shmctl(id, IPC_SET, &shmds)==-1)
	errExit("shmctl");

system V消息队列

创建或打开一个消息队列

#include <sys/types.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
//key:get操作生成的键(通常是IPC_PRIVATE或ftok()返回的一个键)msgflg指定施加于消息队列上的权限或检查一个既有队列的权限的位掩码

IPC_CREAT: 如果没有与指定的key对应的消息队列,那么久创建一个新队列

IPC_EXCL: 如果同时还指定了IPC_CREAT并且与指定的key对应的队列已存在,调用会失败

交换消息

msgsnd()和msgrcv()执行消息队列上的IO

struct mymsg{
	long mtype;
	char mtext[];
}
#include <sys/types.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//msqid:消息队列标识符
//msgp:存放接收或发送的消息
//msgflg:位掩码,控制msgsnd()的操作,目前只定义了IPC_NOWAIT:执行一个非阻塞的发送操作

接收消息

msgrcv()从消息队列中读取(以及删除)一条消息并将其内容复制进msgp指向的缓冲区中

#include <sys/types.h>
#include <sys/msg.h>

ssize_t msgrcv(int msqid, void *msgp, size_t maxmsgsz, long msgtyp, int msgflg);

创建消息队列

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
static void usageError(const char* progName, const char* msg){
        if(msg !=NULL)
                fprintf(stderr, "Usage: %s [-cx] {-f pathname | -k key | -p} [octal-perms]\n", progName );
                fprintf(stderr, "       -c IPC_CREATE flag\n");
                fprintf(stderr, "       -x      IPC_CREATE\n");
                fprintf(stderr, "       -f pathname \n");
                return;
}

int main(int argc, char*argv[]){
        int numKeyFlags;
        int flags, msqid, opt;
        unsigned int perms;
        long lkey;
        key_t key;
        numKeyFlags = 0;
        flags = 0;
        while((opt=getopt(argc,  argv, "cf:k:px"))!=-1){
                switch(opt){
                case 'c':
                        flags |= IPC_CREAT;
                        break;
                case 'f':
                        key = ftok(optarg, 1);
                        if(key==-1){
                                printf("ftok err\n");
                                return 0;
                        }
                        numKeyFlags++;
                        break;
                case 'k':
                        if(sscanf(optarg, "%li", &lkey)!=1){
                                printf("err \n");
                                return 0;
                        }
                        key = lkey;
                        numKeyFlags++;
                        break;
                case 'p':
                        key = IPC_PRIVATE;
                        numKeyFlags++;
                        break;
                case 'x':
                        flags |= IPC_EXCL;   
                        break;
                default:
                        usageError(argv[0], "Bad option\n");
                }
        }

        if(numKeyFlags!=1){
                usageError(argv[0], "ERR");
        }
        perms = (optind==argc)?(S_IRUSR | S_IWUSR):atoi(argv[optind]);
        msqid = msgget(key, flags|perms);	//创建一个消息队列
        if(msqid==-1){
                printf("err...\n");
                return 0;
        }
        printf("%d \n", msqid);
        return 0;
}

消息队列中写入消息

#include <sys/types.h>
#include <sys/msg.h>
#include <stdio.h>
#include <unistd.h>
#define MAX_MTEXT 1024
struct mbuf{
        long mtype;
        char mtext[MAX_MTEXT];
};
static void usageError(const char *progName, const char * msg){
        if(msg!=NULL)
                fprintf(stderr, "%s", msg);
        fprintf(stderr, "Usage: %s [-n] msqid...", progName);
        return;
}
int main(int argc, char*argv[]){
        int msqid, flags, msgLen;
        struct mbuf msg;
        int opt;
        flags = 0;
        while((opt=getopt(argc, argv, "n"))!=-1){
                if(opt=='n')
                        flags |= IPC_NOWAIT;
                else
                        usageError(argv[0], NULL);
        }
        if(argc<optind+2 || argc>optind+3)
                usageError(argv[0], "Wrong num....");
        msqid = atoi(argv[optind]);
        msg.mtype = atoi(argv[optind+1]);
        if(argc > optind+2){
                msgLen = strlen(argv[optind+2])+1;
                if(msgLen>MAX_MTEXT){
                        printf("too long ...\n");
                        return 0;
                }
                memcpy(msg.mtext, argv[optind+2], msgLen);
        }
        else{
                msgLen = 0;
        }
        if(msgsnd(msqid, &msg, msgLen, flags)==-1){	//发送消息
                printf("msg err\n");
                return 0;
        }
        return 0;
}

接收消息

#define _GUN_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/msg.h>
#define MAX_MTEXT 1024
struct mbuf{
        long mtype;
        char mtext[MAX_MTEXT];
};
int main(int argc, char*argv[]){
        int msqid, flags, type;
        ssize_t msgLen;
        size_t maxBytes;
        struct mbuf msg;
        int opt;
        flags = 0;
        type = 0;
        while((opt=getopt(argc, argv, "ent:x"))!=-1){
                switch(opt){
                        case 'e':       flags |= MSG_NOERROR;   break;
                        case 'n':       flags |= IPC_NOWAIT;    break;
                        case 't':       type = atoi(optarg);    break;
                        defaule:        printf("arg err\n"); return 0;
                }
        }
        if(argc < optind+1 || argc>optind+2){
                printf("arg err\n");
                return 0;
        }
        msqid = atoi(argv[optind]);
        maxBytes = (argc>optind+1)?atoi(argv[optind+1]):MAX_MTEXT;

        msgLen = msgrcv(msqid, &msg, maxBytes, type, flags);	//接收消息
        if(msgLen==-1){
                printf("msgrcv err\n");
                return 0;
        }
        printf("Received : type=%ld length=%ld \n", msg.mtype, (long)msgLen);
        if(msgLen>0)
                printf("; body=%s", msg.mtext);
        return 0;
}    
//创建消息队列
$./create -p 
322
//向消息队列中写入消息
$./server 322 20 "xxxxxxx"
//从队列中读取类型小于或等于20的消息
$./recv -t 20 322 

消息队列控制操作

msgctl()系统调用在标识符为msgid的消息队列上执行控制操作

#include <sys/types.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid__ds *buf);

//cmd参数指定了在队列上执行的操作,取值如下:
  • IPC_RMID: 立即删除消息队列对象及其关联的msqid_ds数据结构,队列中所有剩余的消息都会都是,所有被阻塞的读者和写者进程会立即醒来

  • IPC_STAT: 将消息队列关联的msqid_ds数据结构的副本放到buf指向的缓冲区中

  • IPC_SET: 使用buf指向的缓冲区提供的值更新与这个消息队列关联的msqid_ds数据结构中被选中的字段