进程间通信详解(管道、共享内存、消息队列、信号量)

进程间通信

​ 进程之间是相互独立的,多个进程之间如果需要协同工作(多进程),那么进程之间就需要通信(数据交换)。

进程间通信的分类

1、简单数据通信

​ 命令行参数、信号、文件

2、传统的进程间通信

​ 管道文件、匿名管道

3、XSI通信间通信

​ 共享内存、消息队列、信号量

4、网络进程间通信

​ 本地网络:根据网络协议的通信格式,同一个系统内的进程进行通信。

​ 网络通信:根据网络协议的通信格式,不同计算机之间的进程通信。

管道通信

​ 管道是UNIX系统最古老的通信方式,早期管道是半双工的工作模式,只允许数据单项流动,现在的系统大多是全双工的,数据可以双向流动。

管道文件

​ 是一种特殊的文件,采用流式的工作模式。

与普通文件的区别

​ 1、数据读取完之后就消失

​ 2、读取数据时,如果数据不存在则阻塞

编程模型

​ 进程A 函数 进程B

​ 创建管道 mkfifo

​ 打开管道 open 打开管道

​ 写入数据 write/read 读取数据

​ 关闭管道 close 关闭管道

​ 删除管道 unlink

int mkfifo(const char *pathname, mode_t mode);
功能:创建一个管道文件
pathname:管道文件的路径
mode:文件的权限
返回值:成功返回0,失败返回-1

匿名管道

​ 由内核维护一个内核对象,没有文件名,但能以文件的方式操作,只适合以fork创建的父子进程之间使用。

int pipe(int pipefd[2]);
功能:创建一个匿名的管道,并以参数形式返回管道的文件描述符。
返回值:成功返回0,失败返回-1。
    pipefd[0]	读
    pipefd[1]

编程模型

​ 进程A 函数 进程B

​ 创建匿名管道 pipe

​ 创建子进程 fork 复制父进程的管道

​ 关闭读通道 close 关闭写通道

​ 写数据 write/read 读数据

​ 关闭写通道 close 关闭读通道

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>

int main()
{
	int fd[2] = {};
	char buf[4096] = {};

	//创建管道
	if(pipe(fd))
	{
		perror("pipe");
		return -1;
	}

	//创建子进程
	pid_t pid = fork();

	//父进程
	if(pid)
	{
		//关闭读通道
		close(fd[0]);
		
		//写数据
		for(;;)
		{
			printf(">>");
			gets(buf);
			int ret = write(fd[1],buf,strlen(buf)+1);
			
			if(0 >= ret)
			{
				perror("write");
				return -1;
			}

			if(0 == strcmp("quit",buf))
			{
				printf("通信结束\n");
				break;
			}
			usleep(1000);
		}

		//关闭写通道
		close(fd[1]);
		while(-1 != wait(NULL));
	}

	//子进程
	else
	{
		//关闭写通道
		close(fd[1]);

		//读数据
		for(;;)
		{
			int ret = read(fd[0],buf,sizeof(buf));
			if(0 >= ret)
			{
				perror("read");
				return -1;
			}
			
			if(0 == strcmp("quit",buf))
			{
				printf("通信结束");
				break;
			}
			printf("read:%s\n",buf);

		}
		//关闭读通道
		close(fd[0]);
	}
}

XSI通信间通信

​ 由X/Open组织设计一套用于进程间通信的接口,简称XSI。

IPC对象

​ 1、XSI的通信方式是由内核维护一个用于进程通信的对象,简称IPC对象。

​ 2、这种对象有三种,分别是共享内存、消息队列、信号量。

​ 3、该类对象与文件对象一样,只给应用层暴露一个整数,叫IPC标识。

IPC键值

​ 1、相当于文件的名字,用来创建IPC对象/打开IPC对象。

​ 2、如果应用层提供的IPC键值相同,就会获取同一个IPC对象的标识符。

​ 3、IPC键值是一个无符号长整型,可以自动生成,也可以由程序员自定义(可能会冲突)。

key_t ftok(const char *pathname, int proj_id);
功能:生成一个IPC键值
pathname:当前项目的路径
    注意:生成键值时依赖的是真实的路径,而不是字符串
proj_id:当前项目版本号

共享内存

​ 在内核中开辟一块内存,供多个进程进行映射,共享使用该内存,达到通信的效果。

​ 特点:不需要复制,是最快的IPC机制

​ 缺点:需要解决同步访问的问题

int shmget(key_t key, size_t size, int shmflg);
功能:在内核创建一块共享内存
key:IPC键值
size:共享内存的大小
shmflg:
    IPC_CREAT	创建共享内存
    IPC_EXCL	如果已经存在则创建失败
    0			获取共享内存
    mode_flags	共享内存的权限,与文件权限设置文件一致,0mmm
返回值:
    成功,则返回共享内存的标识符,相当于文件描述符
    失败,返回-1
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:映射内核中的共享内存
shmid:IPC标识符,shmget的合法返回值
shmaddr:要映射的虚拟地址,如果为NULL,则系统自动选择要映射的虚拟地址
shmflg:
    0	以读写方式映射
 	SHM_RDONLY	以只读方式映射
    SHM_RND		当shmaddr不为空时,对shmaddr的值向内存赠的整数倍进行向下取整作为映射地址
返回值:成功返回映射后的地址,失败返回0xffffffff
    
int shmdt(const void *shmaddr);
功能:取消映射
shmaddr:映射成功的虚拟地址
返回值:成功返回0,失败返回-1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:管理共享内存
shmid:IPC标识符,shmget的合法返回值
cmd:
    IPC_STAT	获取共享内存的属性
    IPC_SET		设置共享内存的属性,只能修改大小和权限
    IPC_RMID	删除共享内存的属性
    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
        pid_t           shm_lpid;    //最后一次映射或者取消映射的进程ID
        shmatt_t        shm_nattch;  //被映射的次数
        ...
    };
	struct ipc_perm {
        key_t          __key;    //IPC键值
        uid_t          uid;      //拥有者的用户ID
        gid_t          gid;      //拥有者的组ID
        uid_t          cuid;     //创建者的用户ID
        gid_t          cgid;     //创建者的组ID
        unsigned short mode;     //共享内存的权限
        unsigned short __seq;    //共享内存的序列号
    };

编程模型

​ 进程A 进程B 函数

​ 创建共享内存 获取 shmget

​ 映射共享内存 映射 shmat

​ 使用共享内存 使用 …(signal)

​ 取消映射 取消 shmdt

​ 删除共享内存 shmctl

消息队列

小项目:多进程编程之银行系统(消息队列):传送门

​ 基本特点:是由内核维护的数据链接表,进程之间可以通过它按类型、顺序收发数据。

消息格式:
    struct msgbuf{
        long mtype	//消息类型,由程序员自己决定
		char mtext[1];    //消息内容
    };

int msgget(key_t key, int msgflg);
功能:在内核中创建一块消息队列
key:IPC键值
shmflg:
    IPC_CREAT	创建消息队列
    IPC_EXCL	如果已经存在则创建失败
    0			获取消息队列
    mode_flags	消息队列的权限,与文件权限设置文件一致,0mmm
返回值:
    成功,则返回消息队列的标识符,相当于文件描述符
    失败,返回-1
    
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:发消息
msgp:消息结构体变量地址
msgsz:消息内容的长度,不包括消息类型
msgflg:
    0			当消息队列满时,阻塞
    IPC_NOWAIT	当消息队列满时不阻塞
    MSG_NOERROR	当消息内容超出消息队列限制时,自动截断消息内容,不产生错误
返回值:
    成功,返回 0
    失败,返回-1

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:接收消息
msqid:消息队列标识符,msgget的合法返回值
msgp:存储消息的结构体变量地址
msgsz:存储消息内容的容量
msgtyp:消息类型
msgflg:
    0	当要接收的消息不存在时等待
    IPC_NOWAIT	当要接收的消息不存在时不等待
    MSG_EXCEPT	接收消息队列第一个不是msgtyp的消息
返回值:
    成功,返回实际接收到的字节数
    失败,返回-1
    
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:管理消息队列
msqid:消息队列标识符,msgget的合法返回值
cmd:
    IPC_STAT	获取消息队列的属性
    IPC_SET		设置消息队列的属性,只能修改大小和权限
    IPC_RMID	删除消息队列的属性

    struct msqid_ds {
        struct ipc_perm msg_perm;     //消息队列的拥有者以及权限
        time_t          msg_stime;    //最后发送消息的时间
        time_t          msg_rtime;    //最后接收消息的时间
        time_t          msg_ctime;    //最后修改消息的时间
        unsigned long   __msg_cbytes; //当前消息队列的总字节数
        msgqnum_t       msg_qnum;     //当前消息队列中消息的数量
        msglen_t        msg_qbytes;   //一条消息内容的最大字节数
        pid_t           msg_lspid;    //最后发送消息的进程ID
        pid_t           msg_lrpid;    //最后接收消息的进程ID
    };

编程模型

​ 进程A 进程B 函数

​ 创建消息队列 获取消息队列 msgget

​ 发送消息 接收消息 msgsnd/msgrcv

​ 接收消息 发送消息

​ 删除消息队列 msgctl

信号量

信号量详细介绍+例子:传送门

​ 在内核中被若干个进程共享的变量,用于计数,解决多进程之间对有限的共享资源进行访问。

​ 当进程需要获取一份资源时,就应该先对计数器减一,如果不够减则阻塞,当资源使用完后并归还,应该对计数器加一。

​ 进程间共享的资源不多,信号量在进程使用不多

相关函数

int semget(key_t key, int nsems, int semflg);
功能:创建或获取信号量
key:IPC键值
nsems:信号量的个数
semflg:
    0	获取信号量
    O_CREAT	创建信号量
    O_EXCL	如果已经存在则出错
返回值:
    成功返回信号标识符
    失败返回-1

int semctl(int semid, int semnum, int cmd, ...);
功能:管理信号量
    1、设置信号量属性
    2、获取信号量属性
    3、删除信号量
    4、设置/获取信号量的值
semid:信号量标识符,semget合法的返回值
semnum:信号量的数量
cmd:
    IPC_STAT	获取信号量的属性
    	第四个参数	struct semid_ds *buf	输出型参数
    IPC_SET		设置信号量的属性
        第四个参数	struct semid_ds *buf	输入型参数
    IPC_RMID	删除信号量
		没有第四个参数
    GETALL		获取所有信号量的值
		第四个参数	unsigned short  *array	该数组中用于存储每个信号量的值
    GETVAL		获取一个信号量的值
		没有第四个参数,semnum相当于下标,信号量的值通过返回值返回。
    SETALL		设置所有信号量的值
		第四个参数	unsigned short  *array	该数组中存储这每个信号量要设置的值
    SETVAL		设置一个信号量的值
		第四个参数	int	val	设置信号量的值,semnum相当于下标
返回值:因cmd的不同而不同,失败返回-1
        
int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:对信号量加1或减1
semid:信号量标识符,semget的合法返回值
sops:用于操作信号量的结构数组
nsops:结构数组的长度
返回值:成功返回0,失败返回-1
    
    struct sembuf{
        unsigned short sem_num;  //信号量下标
        short          sem_op;   //操作数(+1 或者 -1)
        short          sem_flg;  
        	IPC_NOWAIT:当信号量不够sem_op减时,进程不阻塞
			SEM_UNDO:当进程结束时,本次操作将会取消
    }
    
int semtimedop(int semid, struct sembuf *sops, unsigned nsops, struct timespec *timeout);
功能:多了个倒计时的功能
timeout:用于阻塞的时间,

编程模型

​ 进程A 进程B 函数

​ 创建信号量 获取信号量 semget

​ 初始化信号量 semctl

​ 减信号量 减信号量 semop

​ 使用资源 使用资源 …

​ 加信号量 加信号量 semop

​ 删除信号量 semctl

查看XSI对象

​ ipcs -m 显示共享内存

​ ipcs -q 显示消息队列

​ ipcs -s 显示信号量

​ ipcs -a 显示所有IPC对量

删除XSI对象

​ ipcrm -m shmid 删除共享内存

​ ipcrm -q msgid 删除消息队列

​ ipcrm -s semid 删除信号量

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值