进程间通信概述
进程间通信主要有:管道通信,消息队列,共享内存,信号量
现代进程间通信方式:
管道通信
管道是单工的
命名管道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);
}
...
匿名管道和命名管道
相同点:
不同点:
- 打开方式不一致
- pipe通过fcntl系统调用来设置O_NONBLOCK来设置非阻塞性读写
- 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);
}