导航

进程间通信

Posted on 2023-06-16 20:47  koodu  阅读(19)  评论(0编辑  收藏  举报

进程间通信概述

进程间通信主要有:管道通信,消息队列,共享内存,信号量

现代进程间通信方式:

管道通信

管道是单工的

命名管道mkfifo在文件系统中以管道文件存在

管道通信:管道创建

通过pipe(int fd[2])创建管道通信,其中fd[0]为读端,fd[1]为写端

在父子进程间利用管道进行通信时,由于父进程fork()子进程,子进程会复制父进程的管道描述数组,所以只需在父进程fork()之前创建管道,后面再关闭父子进程中相应的读端或写端,即可进行通信

在父进程创建管道后,再fork()子进程,接着

重定向管道的输入输出

将原先用命令输出到标准输出(屏幕上的)信息重定向输出到管道
grep筛选标准输入流字符重定向到筛选读管道数据

主要操作想法

调用exec函数执行cat命令,会将文件信息输出到屏幕
前面用dup2()函数将输出到屏幕的流STDOUT_FILENO重定向输入到fd[1],cat函数内容到输出流时会输出到fd[1]管道输入里

管道读写特性

写一个读端被关闭的管道,产生SIGPIPE信号,同时errno被设置为EPIPE

标准库的管道popen

示例:

命名管道fifo

通过 mkfifo s.pipe创建一个命名管道文件,两个进程通过打开open()的mkfifo创建的管道文件,通过该文件通信

也可以在读端进程创建管道文件myfifo

 int main(int argc,char* argv[])
 {
     unlink("myfifo");               
     mkfifo("myfifo",0777);//创建管道通信的管道文件
     int fd=open("myfifo",O_RDONLY|O_NONBLOCK);//打开管道文件,读端
     if(fd==-1)
     {
        perror("open error");
        exit(1);
     }
     ...

匿名管道和命名管道

相同点:

不同点:

  1. 打开方式不一致
  2. pipe通过fcntl系统调用来设置O_NONBLOCK来设置非阻塞性读写
  3. FIFO通过fcntl系统调用或open函数来设置非阻塞性读写

System V IPC

消息队列

消息队列的属性

消息队列的创建

若是消息队列已存在,则根据用户传入的key值获得消息队列的id

消息队列控制

往消息队列中发送消息

从消息队列获取消息

示例
//发送消息
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/msg.h>

typedef struct
{
	long type;//消息类型
	int start;//消息数据(start end)
	int end;
}MSG;
//往消息队列发送消息
int main(int argc,char *argv[])
{
	if(argc<2)
	{
		printf("usage: %s key\n",argv[0]);
		exit(1);
	}
	key_t key=atoi(argv[1]);//获取用户传入的key
	printf("key:%d\n",key);

	//创建消息队列
	int msq_id;//创建成功返回队列id
	if((msq_id=msgget(key,IPC_CREAT|IPC_EXCL|0777))<0)
       perror("msgget error");
	printf("消息队列id:%d\n",msq_id);

	//定义要发送的消息
	MSG m1={4,4,400};
	MSG m2={5,5,500};
	MSG m3={3,4,200};
	MSG m4={4,5,400};
	MSG m5={5,4,600};
	//发送消息
	if(msgsnd(msq_id,&m1,sizeof(MSG)-sizeof(long),IPC_NOWAIT)<0)
		perror("msgsnd error");
	if(msgsnd(msq_id,&m2,sizeof(MSG)-sizeof(long),IPC_NOWAIT)<0)
		perror("msgsnd error");
	if(msgsnd(msq_id,&m3,sizeof(MSG)-sizeof(long),IPC_NOWAIT)<0)
		perror("msgsnd error");
	if(msgsnd(msq_id,&m4,sizeof(MSG)-sizeof(long),IPC_NOWAIT)<0)
		perror("msgsnd error");
	if(msgsnd(msq_id,&m5,sizeof(MSG)-sizeof(long),IPC_NOWAIT)<0)
		perror("msgsnd error");
	//通过消息队列控制获得消息队列属性,如数量,全部属性存在消息队列结构体中
	struct msqid_ds ds;
	if(msgctl(msq_id,IPC_STAT,&ds)<0)
		perror("msgctl error");
	printf("消息队列中消息数量为:%ld\n",ds.msg_qnum);
	exit(0);
}
//接收消息
#include<sys/msg.h>
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
typedef struct
{
	long type;
	int start;
	int end;
}MSG;
int main(int argc,char *argv[])
{
	if(argc<3)
	{
		perror("first error");
		exit(0);
	}
	//获取传入的key值
	key_t key=atoi(argv[1]);//根据用户传入的key值创建消息队列;若已存在,则根据用户传入的key获得消息队列的id
	//要从队列中取走的消息的type值类型
	long type = atoi(argv[2]);

	int msq_id;
	if((msq_id=msgget(key,0777))<0)//获取消息队列id,传入的权限为0777
		perror("get msq_rcv error");
	printf("根据传入的key获得消息队列的id为:%d\n",msq_id);
	//接收消息
	MSG m;
	if(msgrcv(msq_id,&m,sizeof(MSG)-sizeof(long),type,IPC_NOWAIT)<0)
		perror("messeg recive error");
	else
	{
		printf("type:%ld start:%d end:%d\n",m.type,m.start,m.end);
	}
	exit(0);
}

可以在系统中通过命令查看其在系统中的信息:

ipcs -q

ipcrm -q x  # 删除消息队列为x的消息队列

共享内存

概述:

共享内存属性

步骤

创建共享内存

共享内存控制

共享内存映射

对于共享内存的操作,利用管道阻塞进行父子进程对共享内存的同步

示例
#ifndef __TELL_H__
#define __TELL_H__
//管道初始化
extern void init();
//利用管道进行等待
extern void wait_pipe();
//利用管道进行通知
extern void notify_pipe();
//销毁管道
extern void destroy_pipe();
#endif
#include<sys/shm.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/wait.h>
#include"share_memory_tell.h"
static int fd[2];

//管道初始化
void init()
{
	if(pipe(fd)<0)
		perror("create pipe error");
}
//利用管道进行等待
void wait_pipe()
{
	char c;
	//管道读写默认是阻塞性的
	if(read(fd[0],&c,1)<0)
	{
		perror("pipe wait...");
	}
}
//利用管道进行通知
void notify_pipe()
{
    //随便发送一个字符
	char c='c';
	if(write(fd[1],&c,1)!=1)
		perror("pipe send error");
}
//销毁管道
void destroy_pipe()
{
	close(fd[0]);
	close(fd[1]);
}
#include<sys/shm.h>
#include"share_memory_tell.h"
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/wait.h>
int main(void)
{
	//父创建共享内存
	int shmid;
	if((shmid=shmget(IPC_PRIVATE,1024,IPC_CREAT|IPC_EXCL|0777))<0)//返回共享内存id
	{
		perror("shmget error");
		exit(1);
	}
	pid_t pid;
	init();//初始化管道,父进程创建管道
	if((pid=fork())<0)
		perror("create fork error");
	else if(pid==0)//子进程
	{
		//子进程从管道中读数据,父进程不往管道写入数据,子进程阻塞;要等待父进程往共享内存写入,再往管道发送数据
		wait_pipe();
		//子进程从共享内存读数据
		//子进程也要映射到虚拟内存,因为父进程没有在fork()前映射
		int* pi=(int*)shmat(shmid,0,0);
		if(pi==(int*)-1)
		{
			perror("shmat error");
			exit(1);
		}
		printf("start:%d end:%d\n",*pi,*(pi+1));
		//解除映射
		shmdt(pi);
		//删除共享内存
		shmctl(shmid,IPC_RMID,NULL);
		destroy_pipe();//删除管道
	}else
	{
		//父进程进行共享内存映射
		int* pi=(int*)shmat(shmid,0,0);//映射成功返回虚拟内存地址
		if(pi==(int*)-1)//映射失败返回-1地址
		{
			perror("shnat error");
			exit(1);
		}
		//往共享内存读写
		*pi=100;*(pi+1)=20;
		//操作完毕解除共享内存映射
		shmdt(pi);
		notify_pipe();//通知子进程可以读了,往管道发送一个数据通知
		destroy_pipe();//删除管道文件描述符
		wait(0);
	}
	exit(0);
}
考虑将银行账户这类临界资源放到共享内存

在创建共享内存后,进行共享内存映射

映射后返回地址空间(void*)转为(Account*)类型

信号量集

概述:

信号量集属性

创建信号量集

信号量集的控制

信号量集操作

信号量集pv操作
 //pv.h
//封装pv操作的函数原型
#ifndef __PV_H__
#define __PV_H__

//初始化semnums信号集信号灯的值
extern int I(int sumnums,int value);

//对信号量集(semid)中的信号灯(semnum)做P(value)操作
extern void P(int semid,int semnum,int value);

//对信号量集(semid)中的信号灯(semnum)做V(value)操作
extern void V(int semid,int semnum,int value);

//销毁信号量集
extern void D(int semid);

#endif
//pc.c
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "pv.h"

union semun//对信号集控制的联合
{
	int val;
	struct semid_ds* buf;
	unsigned short * array;
};
//初始化信号集 信号灯个数(sumnums) 每个灯的值value
int I(int sumnums,int value)//sumnums信号集中信号的数量
{
	//创建信号量集,semid 信号集id
	int semid;
	semid=semget(IPC_PRIVATE,sumnums,IPC_CREAT|IPC_EXCL|0777);
	if(semid<0) return -1;//出错
	union semun un;
	//存放信号灯的值                              个数     单位
	unsigned short* array=(unsigned short*)calloc(sumnums,sizeof(unsigned short));
	int i;
	for(i=0;i<sumnums;i++) 
	array[i]=value;

	un.array=array;
	//初始化信号集中所有信号灯的初值
	//0:初始化所有信号灯,SETALL使用un里的array,传入un即可
	if(semctl(semid,0,SETALL,un)<0)
	{
		perror("semctl error");
		return -1;
	}
	free(array);
	return semid;
}

//对信号量集(semid)中的信号灯(semnum)做P(value)操作
void P(int semid,int semnum,int value)
{
	assert(value>=0);
    /*
	 *定义sembuf类型的结构体数组,放置若干个结构体sembuf变量
	 *sembuf结构体对应操作的信号量,P/V操作
	 */
	struct sembuf ops[]={{semnum,-value,SEM_UNDO}};
	//对信号量做PV操作
	if(semop(semid,ops,sizeof(ops)/sizeof(struct sembuf))<0)
		perror("semop error");
}
/*
semop函数对信号集中信号灯为semnum的信号灯作P操作(对该信号灯的value  -value)
由于该例中初值为1,减value即减1  最后为0

在进行P操作后,若信号集中该信号的值小于0,则P操作会阻塞
等待,值大于等于0,在进行下去
*/
//对信号量集(semid)中的信号灯(semnum)做V(value)操作
void V(int semid,int semnum,int value)
{

	 //assert(value>=0);
    /*
	 *定义sembuf类型的结构体数组,放置若干个结构体sembuf变量
	 *sembuf结构体对应操作的信号量,P/V操作
	 */
	struct sembuf ops[]={{semnum,value,SEM_UNDO}};
	//对信号量做PV操作
	if(semop(semid,ops,sizeof(ops)/sizeof(struct sembuf))<0)
		perror("semop error");
}

//销毁信号量集
void D(int semid)
{
	if(semctl(semid,0,IPC_RMID,NULL)<0)
		perror("semctl error");
}
银行账户

在共享资源账户上绑定一个信号量集 int semid;

信号量集初始为1个信号量,初值为1

存取前,pv操作,对信号集上的信号量操作

gcc的一些编译

在银行账户的源程序中,要用到pv.h

pv.o二进制文件

编译:

gcc -o bin/account_test -Iinclude obj/pv.o src/account.c src/account_test.c
读者写者问题,使用信号集,共享内存

#include<sys/shm.h>
#include<sys/sem.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<sys/wait.h>
#include<unistd.h>
#include<sys/types.h>
typedef struct
{
	int val;
	int semid;//信号集编号
}Storage;
void init(Storage* s)//初始化资源
{
	assert(s!=NULL);
	//创建信号量集(包含2个信号量)
	if((s->semid=semget(IPC_PRIVATE,2,IPC_CREAT|IPC_EXCL|0777))<0)
	{
		perror("semget error");
		exit(1);
	}
	//对信号集中所有信号量初始化
	union semun
	{
		int val;
		struct semid_ds* ds;
		unsigned short* array;
	};
	union semun un;
	//2个信号量的初值设置为0
	unsigned short array[2]={0,0};
	un.array=array;

	if(semctl(s->semid,0,SETALL,un)<0)
	{
		perror("semctl error");
		exit(1);
	}
}
void destroy(Storage* s)//销毁信号量集资源
{
	assert(s!=NULL);
	if(semctl(s->semid,0,IPC_RMID,NULL)<0)
	{
		perror("semctl error");
		exit(1);
	}
}
void write_fun(Storage*s,int val)//写者
{
	assert(s!=NULL);
	//写入数据到Storage
	s->val=val;
	printf("%d write %d\n",getpid(),val);

	//设置0号信号量(s1)作V(1)操作
    struct sembuf ops_v[1]={{0,1,SEM_UNDO}};
	//设置1号信号量(s2)作P(1)操作
    struct sembuf ops_p[1]={{1,-1,SEM_UNDO}};

	//V(s1)
	if(semop(s->semid,ops_v,1)<0)
	{
		perror("semop s1 error");
		exit(1);
	}
	//P(s2)
	if(semop(s->semid,ops_p,1)<0)
	{
		perror("semop s2 error");
		exit(1);
	}
}
void read_fun(Storage*s)//读者
{
    assert(s!=NULL);
	//设置0号信号量(s1)作P(1)操作
    struct sembuf ops_p[1]={{0,-1,SEM_UNDO}};
	//设置1号信号量(s2)作V(1)操作
    struct sembuf ops_v[1]={{1,1,SEM_UNDO}};
	//P(s1)
	if(semop(s->semid,ops_p,1)<0)
	{
		perror("semop s2 error");
		exit(1);
	}
	//从Storage读出数据
    printf("%d read %d\n",getpid(),s->val);
	//V(s2)
	if(semop(s->semid,ops_v,1)<0)
	{
		perror("semop s1 error");
		exit(1);
	}
}
int main(void)
{
	//将共享资源Storage创建在共享内存中
	int shmid;
	if((shmid=shmget(IPC_PRIVATE,sizeof(Storage),IPC_CREAT|IPC_EXCL|0777))<0)
	{
		perror("shmget error");
		exit(1);
	}
	//父进程进程共享内存映射
	Storage*s=(Storage*)shmat(shmid,0,0);
	if(s==(Storage*)-1)
	{
		perror("shmat error");
		exit(1);
	}
	//创建信号量集并初始化
	init(s);
	pid_t pid=fork();
	if(pid<0)
	{
		perror("fork error");
		exit(1);
	}else if(pid>0)//父进程
	{
		int i=1;
		for(;i<=100;i++)
		{
			write_fun(s,i);
		}
		wait(0);
		destroy(s);//删除信号集资源
		shmdt(s);//解除映射
		shmctl(shmid,IPC_RMID,NULL);//销毁共享内存
	}else//子进程
	{
		int i=1;
		for(;i<=100;i++)
		{
			read_fun(s);
		}
		shmdt(s);
	}
	exit(1);
}