linux下进程间的通信方法:半双工管道、FIFO(命名管道)、消息队列、信号量、共享内存等。
1.半双工管道
进程创建管道,每次创建两个文件描述符来操作管道。其中一个对管道进行写操作,另一个描述符进行读操作。
创建管道的原型为:
#include<unistd.h>
int pipe(int filedes[2]);
filedes数组是一个文件描述符的数组,用于保存管道返回的两个文件描述符。filedes[0]是为了读操作而创建和打开的,filedes[1]是为了写操作而打开的。函数执行成功返回0,失败则返回1。半双工管道需要在一个进程中关闭读端,另一个进程中关闭写端。
管道的具体实例代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
int main(void)
{
int result = -1; /*创建管道结果*/
int fd[2],nbytes; /*文件描述符,字符个数*/
pid_t pid; /*PID值*/
char string[] = "你好,管道";
char readbuffer[80];
/* 文件描述符1用于写,文件描述符0用于读 */
int *write_fd = &fd[1]; /*写文件描述符*/
int *read_fd = &fd[0]; /*读文件描述符*/
result = pipe(fd); /*建立管道*/
if( -1 == result) /*建立管道失败*/
{
printf("建立管道失败\n"); /*打印信息*/
return -1; /*返回错误结果*/
}
pid = fork(); /*分叉程序*/
if( -1 == pid) /*fork失败*/
{
printf("fork 进程失败\n"); /*打印信息*/
return -1; /*返回错误结果*/
}
if( 0 == pid) /*子进程*/
{
close(*read_fd); /*关闭读端*/
result = write(*write_fd,string,strlen(string)); /*向管道端写入字符*/
return 0;
}
else /*父进程*/
{
close(*write_fd); /*关闭写端*/
nbytes = read(*read_fd, readbuffer,sizeof(readbuffer)); /*从管道读取数值*/
printf("接收到%d个数据,内容为:”%s“\n",nbytes,readbuffer); /*打印结果*/
}
return 0;
}
2.命名管道
命名管道的工作方式与普通管道非常相似,但也存在一些明显的区别。
- 在文件系统中命名管道是以设备特殊文件的形式存在的。
- 不同的进程可以通过命名管道共享数据。
创建命名管道:
可以用shell来完成,例如在目录/tmp下建立一个名字为namedfifo的命名管道。
$mkfifo /ipc/namedfifo
在代码中也可以使用mkfifo()函数。
#include <sys/type.h>
#include <sys/stat.h>
int mkfifo(const char* pathname,mode_t mode);
FIFO的IO操作与普通的管道的IO操作基本上是一样的。一个主要的区别在于,在FIFO中必须使用一个open()函数来显示的建立链接到管道的通道。
3.消息队列
消息队列是内核地址空间中的内部链表,通过linux内核在各进程之间传递内容。不同的消息队列之间是相互独立的。每个消息队列中的消息,又构成一个独立的链表。
1. 消息缓冲区结构
#include <linux/msg.h>
struct msgmBuf
{
long mtype;//消息类型,以正数来表示。
char mtext[1];//消息数据,在构建自己的消息结构时,这个域不一定要设为char或者长度为1.
};
可以以这个结构为模板定义自己的消息结构。例如:
struct msgmBuf
{
long mtype;
char mtext[10];
long length;
};
2. 结构msgid_ds
对于消息队列而言,它的内部数据结构是msgid_ds结构。对于系统上创建的每个消息队列,内核均为其创建、存储和维护该结构的一个实例。
#include <linux/msg.h>
struct msqid_ds
{
struct ipc_perm msg_perm;//ipc_perm结构在linux/ipc.h中定义。用于存放队列的许可权限信息。
time_t msg_stime;//发送到队列的最后一个消息的时间戳
time_t msg_rtime;//从队列中获取的最后一个消息的时间戳
time_t msg_ctime;//队列进行最后一次变动的时间戳
unsigned long __msg_cbytes;//在队列上所驻留的字节总数
msgqnum_t msgqnum;//当前处于队列中的消息数目
msglen_t msgqbytes;//队列中能容纳的字节最大数目
pit_t msg_lspid;//发送最后一个消息进程的pid
pit_t msg_lrpid;//接收最后一个消息进程的pid
};
3. 结构ipc_perm
内核把IPC对象的许可权限信息存放在ipc_perm类型结构中。
#include <linux/msg.h>
struct ipc_perm
{
key_t key;//函数msgget()使用的键值
uid_t uid;//用户的uid
gid_t gid;//用户的gid
uid_t cuid;//建立者的uid
gid_t cgid;//建立者的gid
unsigned short mode;//权限,用户控制读写,例如0666,可以对消息进行读写操作
unsigned short seq;//序列号
};
4. 函数
- 键值构建函数ftok()
ftok()将路径名和项目的表示符转变为一个系统V的IPC键值。
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char*pathname,int proj_id);
//pathname必须是已经存在的目录,proj_id则是一个8位的值常用a、b等表示
- 获得消息函数msgget()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key,int msgflg);
//key是键值,由ftok()生成
//msgflg参数
// IPC_CREAT:如果内核中不存在该队列,则创建
// IPC_EXCL:与IPC_CREAT一起使用,如果队列早已存在则出错
- 消息发送函数msgsend()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsend(int msqid,const void*msgp,size_t msgsz,int msgflg);
- msqid是队列标识符,由msgget()返回
- msgp,是void类型指针,指向消息缓冲区
- msgsz包含着消息大小,单位字节,其中不包括消息类型的长度
- msgflg可以设置为0(表示忽略),也可以设置为IPC_NOWAIT,则进程将被阻塞,直到可以写消息为止。
- 消息接收函数msgrcv()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msqid,void *msgq,size_t msgsz,long msgtype,int msgflg);
- msqid是队列标识符,由msgget()返回
- msgp,是void类型指针,指向消息缓冲区,获取的消息将存放在这里
- msgsz包含着消息大小,单位字节,其中不包括消息类型的长度
- msgtype指要从队列中获取的消息类型,如果参数为0将返回队列中最老的消息。
- 消息控制函数msgctl()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid,int cmd,struct msqid_ds *buf);
msgctl()向内核发送一个cmd命令,内核根据此来判断进行何种操作,buf为应用层和内核空间进行数据交换的指针。cmd的值可以如下:
- IPC_STAT:获取队列的msqid_ds结构,并把它存放在buf变量所指定的地址中。
- IPC_SET:设置队列的msqid_ds结构的ipc_perm成员值,它从buf中取得该值。
- IPC_RMID:内核删除队列。
5.示例
#include<sys/msg.h>
#include <time.h>
#include <sys/ipc.h>
struct msgmBuf
{
long mtype;
char mtext[10];
};
void msgShowAttr(int &msgId,struct msqid_ds & MsgInfo)
{
sleep(1);
int ret = msgctl(msgId, IPC_STAT ,&MsgInfo);//获取消息
if(-1 == ret)
{
printf("get msg err\n");
return ;
}
printf("\n"); /*以下打印消息的信息*/
printf("现在队列中的字节数:%ld\n",MsgInfo.msg_cbytes); /*消息队列中的字节数*/
printf("队列中消息数:%d\n",(int)MsgInfo.msg_qnum); /*消息队列中的消息数*/
printf("队列中最大字节数:%d\n",(int)MsgInfo.msg_qbytes); /*消息队列中的最大字节数*/
printf("最后发送消息的进程pid:%d\n",MsgInfo.msg_lspid); /*最后发送消息的进程*/
printf("最后接收消息的进程pid:%d\n",MsgInfo.msg_lrpid); /*最后接收消息的进程*/
printf("最后发送消息的时间:%s",ctime(&(MsgInfo.msg_stime))); /*最后发送消息的时间*/
printf("最后接收消息的时间:%s",ctime(&(MsgInfo.msg_rtime))); /*最后接收消息的时间*/
printf("最后变化时间:%s",ctime(&(MsgInfo.msg_ctime))); /*消息的最后变化时间*/
printf("消息UID是:%d\n",MsgInfo.msg_perm.uid); /*消息的UID*/
printf("消息GID是:%d\n",MsgInfo.msg_perm.gid); /*消息的GID*/
return;
}
int MsgQueueTest()
{//消息队列
msgmBuf MsgBuf;
struct msqid_ds MsgInfo;
char msgPath[] = "/ipc/msg/";//消息key产生所用的路径
key_t key = ftok(msgPath,'b');//产生key
if(-1 == key)
{
printf("get key err\n");
return -1;
}
int msgId = msgget(key,IPC_CREAT|IPC_EXCL|0x0666);//建立消息
if(-1 == msgId)
{
printf("errno = %s\n", strerror(errno));
printf("setup msg err\n");
return -1;
}
msgShowAttr(msgId,MsgInfo);
//发送消息
MsgBuf.mtype = 10;
memcpy(MsgBuf.mtext,"test msg",sizeof("test msg"));
int ret = msgsnd(msgId,&MsgBuf,sizeof("test msg"),IPC_NOWAIT);
if(-1 == ret)
printf("send msg err\n");
msgShowAttr(msgId,MsgInfo);
/接收消息
ret = msgrcv(msgId,&MsgBuf,10,10,IPC_NOWAIT|MSG_NOERROR);
if(-1 == ret)
{
printf("rcv msg err\n");
}
else
printf("msg len:%d\n",ret);
msgShowAttr(msgId,MsgInfo);//
///设置消息属性
ret = msgctl(msgId,IPC_SET ,& MsgInfo);
if(-1 == ret)
{
printf("set msg arr err \n");
return -1;
}
msgShowAttr(msgId,MsgInfo);
//删除消息队列
ret = msgctl(msgId,IPC_RMID,NULL);
if(-1 == ret)
{
printf("del err\n");
return -1;
}
return 0;
}
4.信号量
信号量是一种计数器,用来控制对多个进程共享的资源所进行的访问。常常被用来做一个锁机制。典型例子:生产者和消费者的模型。
- 信号量数据结构
union semun
{//信号量操作的联合结构
int val; //整型变量
struct semid_ds *buf; //semid_ds结构指针
unsigned short *array; //数组类型
} ;
- 函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key,int nsems,int semflg);//新建信号量
int semop(int semid,struct sembuf* sops,unsigned nspos);//信号量操作函数(P、V操作)
int semctl(int semid,int semnum,int cmd,...);//控制信号量参数
semget()
int semget(key_t key,int nsems,int semflg);//新建信号量
- key是ftok()生成的键值
- nsems指定在新的集合中创建的信号量数目
- semflg打开信号的方式
- IPC_CREAT:如果内核不存在这样的信号量集合,则把它创建出来
- IPC_EXCL:与IPC_CREAT一起使用时,若已存在,则操作失败
semop()
int semop(int semid,struct sembuf* sops,unsigned nspos);
#include <linux/sem.h>
struct sembuf
{
ushort sem_num;//信号量的编号
short sem_op;//信号量的操作,正、负,或者零
short sem_flg;//信号量的操作标志
}
- semid由semget()生成
- spos是一个指针,指向将要在信号量集合上执行操作的一个数组
- nspos是数组中操作的个数
semctl()
int semctl(int semid,int semnum,int cmd,...);
- semid是关键字的值,由semget()返回
- semun要执行操作的信号量的编号,对于第一个信号量,它的值为0
- cmd表示要在集合上执行的命令。
- IPC_STAT:获取某个集合的semid_ds结构,并把它存储在semun联合体的buf所指定的地址中。
- IPC_SET:设置某个集合的semid_ds结构的ipc_perm成员的值。从semun联合体的buf参数中取值。
- IPC_RMID:从内核中删除集合
- GETALL:用于获取集合中所有信号量的值。存放在数组中,由联合体的buf参数中取到。
- GETNCNT:返回当前正在等待资源的进程数目
- GETPID:最后一次执行semop()的进程的PID
- GETVAL:返回集合中某个信号量的值
- GETZCNT:返回正在等待资源利用率达到百分之百的进程数目
- SETALL:把集合中所有的信号量的值设置为联合体array成员所包含的对应值
- SETVAL:把集合中单个信号量的值设为联合体的val成员的值
- 示例
//信号量
#include <sys/sem.h>
union semun { //信号量操作的联合结构
int val; //整型变量
struct semid_ds *buf; //semid_ds结构指针
unsigned short *array; //数组类型
} ;
int CreateSem(key_t& key,int value)//创建信号量
{
union semun sem;
sem.val = value;
int semId = semget(key,0,IPC_CREAT|0666);
if(-1 == semId)
{
printf("creat sem err\n");
printf("errno = %s\n", strerror(errno));
return -1;
}
printf("semId:%d\n",semId);
semctl(semId,0,SETVAL,sem);
return semId;
}
///PV操作
int semP(int &semId)///P操作 增加信号量
{
sembuf sops {0,+1,IPC_NOWAIT};
return semop(semId,&sops,1);
}
int semV(int &semId)//V操作 减小信号量
{
sembuf sops {0,-1,IPC_NOWAIT};
return semop(semId,&sops,1);
}
void SetValSem(int& semId, int val)
{
union semun sem;
sem.val = val;
semctl(semId,0,SETVAL,sem);//设置信号量的值
return;
}
int GetValSem(int &semId)//获得信号量的值
{
union semun sem;
return semctl(semId,0,GETVAL,sem);
}
void DestroySem(int &semId)//销毁信号量
{
union semun sem;
sem.val = 0;
semctl(semId,0,IPC_RMID,sem);
}
int SemunTest()
{//信号量
key_t key= ftok("./test/ipc/sem",'b');
if(-1 == key)
{
printf("creat key err\n");
return -1;
}
printf("key:%d\n",key);
int semId = CreateSem(key,100);
if(-1 == semId)
return -1;
for(int i=0;i<3;i++)
{
semP(semId);
semV(semId);
}
int value = GetValSem(semId);
printf("sem val:%d\n",value);
DestroySem(semId);
return 0;
}
5.共享内存
共享内存是在多个进程之间共享内存区域的一种进程间通信方式,它是在多个进程之间对内存段进行映射的方式实现的。
- 函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key,size_t size,int shmflg);//创建共享内存
void *shmat(int shmid,consst void* shmaddr,int shmflg);//获得共享内存地址
int shmdt(const void * shamaddr);//删除
int shmctl(int shmid,int cmd,shmid_ds *buf);//控制
shmget()
int shmget(key_t key,size_t size,int shmflg);
- key关键字的值
- size共享内存大小
- shmflg打开方式
- IPC_CREAT:如果内核不存在这样的内存段,则把它创建出来
- IPC_EXCL:与IPC_CREAT一起使用时,若已存在,则操作失败
shmat()
void *shmat(int shmid,consst void* shmaddr,int shmflg);
- shmid通过shmget()获得的值
- shamaddr为0自动查找未映射地址,也可以指定某个地址
- shmflg标志参数,SHM_RDONLY标志与标志参数进行or操作,这样共享内存段只能标记为只读
shmdt()
int shmdt(const void * shamaddr);
当某进程不在需要一个共享内存段时,它必须调用这个函数来断开与该内存端的连接。在成功断开连接后,shmid_ds结构的shm_nattch成员的值将减1,当这个值为0时,内核才会真正删除该内存段。
shmctl()
int shmctl(int shmid,int cmd,shmid_ds *buf);
struct shmid_ds
{
struct ipc_perm shm_perm;//所有者权限
size_t shm_segsz;//段大小
time_t shm_atime;//最后挂接的时间
time_t shm_dtime;//最后去除的时间
time_t shm_ctime;//最后修改的时间
pid_t shm_cpid;//建立者的id
pit_t shm_lpid;//最后调用函数shmat()/shmdt() 的PID
shmatt_t shm_nattch;//现在挂接的数量
...
};
与msgctl()类似,IPC_SET、IPC_RMID.
- 示例
#include <sys/shm.h>
int shareMemTest()
{
static char msg[] = "hello sharemem\n";
key_t key = ftok("./test/ipc/sem",'a'); /*生成键值*/
int shmid = shmget(key,1024,IPC_CREAT|0604); //获得共享内存,大小为1024个
int semid = CreateSem(key,0); /*建立信号量*/
pid_t p = fork();//
if(p > 0) /*父进程*/
{
char* shms = (char *)shmat(shmid,0,0); /*挂接共享内存*/
memcpy(shms, msg, strlen(msg)+1); /*复制内容*/
sleep(10); /*等待10s,另一个进程将数据读 出*/
semP(semid); /*获得共享内存的信号量*/
shmdt(shms); /*摘除共享内存*/
DestroySem(semid); /*销毁信号量*/
}
else if(p == 0) /*子进程*/
{
char* shmc = (char *)shmat(shmid,0,0); /*挂接共享内存*/
semV(semid); /*减小信号量*/
printf("共享内存的值为:%s\n",shmc); /*打印信息*/
shmdt(shmc); /*摘除共享内存*/
}
return 0;
}
6.信号
信号机制是unix系统中最古老的进程之间的通信机制。用于在一个或多个进程之间传递异步信号。