进程间通信和同步

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.命名管道

命名管道的工作方式与普通管道非常相似,但也存在一些明显的区别。

  1. 在文件系统中命名管道是以设备特殊文件的形式存在的。
  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. 函数

  1. 键值构建函数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等表示
  1. 获得消息函数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一起使用,如果队列早已存在则出错
  1. 消息发送函数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,则进程将被阻塞,直到可以写消息为止。
  1. 消息接收函数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将返回队列中最老的消息。
  1. 消息控制函数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.信号量

信号量是一种计数器,用来控制对多个进程共享的资源所进行的访问。常常被用来做一个锁机制。典型例子:生产者和消费者的模型。

  1. 信号量数据结构
union semun 
{//信号量操作的联合结构 
        int 				 val;			//整型变量
        struct semid_ds 	*buf;			//semid_ds结构指针 
        unsigned short 		*array;			//数组类型
    } ;
  1. 函数
#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成员的值
  1. 示例
//信号量
#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.共享内存

共享内存是在多个进程之间共享内存区域的一种进程间通信方式,它是在多个进程之间对内存段进行映射的方式实现的。

  1. 函数
#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.

  1. 示例
#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系统中最古老的进程之间的通信机制。用于在一个或多个进程之间传递异步信号。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值