进程间的通信
进程间通信的四种方式
管道 、 信号量 、共享内存、消息队列
一、管道:
管道分为有名管道和无名管道,
有名管道:可用于任意两个进程间的通信;
可以通过mkfifo命令创建,
也可以通过系统调用创建
#include<stdio.h>
#include<sys/stat.h>
int mkfifo(const char*filename,mode_t mode);
无名管道:用父子进程间的通信
无名管道只能通过系统调用创建:
#include<unistd.h>
/*
pipe()成功返回0,失败返回-1;
fds[0]是管道读端的描述符
fds[1]是管道写端的描述符
*/
int pipe(int fds[2]);
管道的特点
无论是有名还是无名管道,写入的数据都在内存上
管道是一种半双工通信方式
有名和无名的区别是,有名可以在任意两个进程间通信,而无名管道只能在父子进程之间进行通信。
二、信号量:
信号量主要是用来同步进程的。信号量是一个特殊的变量一般只取正值,其代表允许访问资源数目。
分类:
二值信号量:其取值只有0和1;
计数信号量:信号量的值大于1;
当信号量的值为0时,表示没有资源可以使用,P操作会被阻塞。
操作:
P操作:获取资源时,需要对信号量的值减1.
V操作:释放资源时,需要对信号量的值加1.
**相关知识 **:
临界资源:同一时间只能有一个进程进行访问的资源
临界区:访问临界资源的那段代码
信号量由内核提供的接口帮我们进行创建,传建完后也有内核帮我们维护,与使用它的进程的生存周期无关。信号量的销毁需要我们主动手动进行销毁,不然其会一直存在。
信号量函数的定义:头文件“#include<sys/sem.h>”
semget:
//semget()创建或者获取已存在的信号量,成功返回信号量id,失败返回-1
int semget(key_t key, int nsems, int semflg);
//key:两个进程使用相同的 key 值,就可以使用同一个信号量
//nsems:内核维护的是一个信号量集,在新建信号量时,其指定信号量集中信号量的个数
//semflg 可选: IPC_CREAT(创建信号量) IPC_EXCL(判断信号量是否已存在)
semctl:
//semop()对信号量进行改变,做 P 操作或者 V 操作,semop()成功返回 0,失败返回-1
int semop(int semid, struct sembuf *sops, unsigned nsops);
struct sembuf
{
unsigned short sem_num; //指定信号量集中的信号量下标
short sem_op; //其值为-1,代表 P 操作,其值为 1,代表 V 操作
short sem_flg; //SEM_UNDO
};
semctl:
//semctl()控制信号量 semctl()成功返回 0,失败返回-1
int semctl(int semid, int semnum, int cmd, ...);
/*
cmd 选项: SETVAL IPC_RMID
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
};
*/
创建一个信号量实例:
static int semid = -1;
void sem_init()//创建/或者已存在的信号量
{
semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);
if ( semid == -1 )//已存在
{
semid = semget((key_t)1234,1,0600);
}
else//初始化信号量
{
union semun a;
a.val = 1;//信号量的初始值
if ( semctl(semid,0,SETVAL,a) == -1 )
{
perror("semctl error");
}
}
}
void sem_p()//p 减一
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1;//p
buf.sem_flg = SEM_UNDO;
if ( semop(semid,&buf,1) == -1 )
{
perror("semop p error");
}
}
void sem_v()//v 加一
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1;//v
buf.sem_flg = SEM_UNDO;
if ( semop(semid,&buf,1) == -1 )
{
perror("semop v error");
}
}
void sem_destroy()//销毁
{
if ( semctl(semid,0,IPC_RMID) == -1 )
{
perror("semctl del error");
}
}
经典习题:三个进程a,b,c分别输入A,B,C,要求输出ABCABCABC…
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/sem.h>
union semun{
int val;
};
//sem.c
static int semid = 0;
void sem_init()
{
semid=semget((key_t)1234,3,IPC_CRATE|IPC_EXECl|0600);
//如果创建失败看能否直接获取
if(semid==-1){
semid=semget((key_t)1234,3,0600);
}
else{
int arr[]={1,0,0};
for(int i=0;i<3;++i){
union semun a;
a.val=arr[i];
if(semctl(semid,i,SETVAL,a) == -1){
perror("semctl SEtval error\n");
}
}
}
}
void sem_p(int index)
{
assert(index>0&&index<3);
struct sembuf buf[3];
for(int i=0;i<3;++i){
buf[i].sem_num=index;
buf[i].sem_op = -1;
buf[i].sem_flg = SEM_UNDO;
if(semop(semid,&buf[i],1) == -1){
perror("semop p error\n");
}
}
}
void sem_v(int index)
{
assert(index>0&&index<3);
struct sembuf buf[3];
for(int i=0;i<3;++i){
buf[i].sem_num=index;
buf[i].sem_op = 1;
buf[i].sem_flg = SEM_UNDO;
if(semop(semid,&buf[i],1) == -1){
perror("semop v error\n");
}
}
}
void sem_des()
{
if(semctl(semid,0,IPC_RMID) == -1){
perror("semctl destory error\n");
}
}
//a.c
#include"sem.h"
int main()
{
sem_init();
for(int i=0;i<5;++i){
sem_p(0);
printf("A");
fflush(stdout):
sleep(1);
sem_v(1);
}
}
//b.c
#include"sem.h"
int main()
{
sem_init();
for(int i=0;i<5;++i){
sem_p(1);
printf("B");
fflush(stdout):
sleep(1);
sem_v(2);
}
}
//c.c
#include"sem.h"
int main()
{
sem_init();
for(int i=0;i<5;++i){
sem_p(2);
printf("C");
fflush(stdout):
sleep(1);
sem_v(0);
}
}
三、共享内存
共享内存就是,多个进程都可以访问的内存空间。
既然共享内存空间多个进程都可访问,那么我们需要一些方法去控制这些访问内存的进程,是的访问的过程变得有序,不至于出现错误。
共享内存的定义和使用:
头文件#include<sys/shm.h>
shmget:
int shmget(key_t key, size_t size, int shmflg);
shmget();用于创建或者获取共享内存
shmget();成功返回共享内存的 ID, 失败返回-1
key: 不同的进程使用相同的 key 值可以获取到同一个共享内存
size: 创建共享内存时,指定要申请的共享内存空间大小
shmflg: IPC_CREAT IPC_EXCL
shmat:
void* shmat(int shmid, const void *shmaddr, int shmflg);
shmat()将申请的共享内存的物理内存映射到当前进程的虚拟地址空间上
shmat()成功返回返回共享内存的首地址,失败返回 NULL
shmaddr:一般给 NULL,由系统自动选择映射的虚拟地址空间
shmflg: 一般给 0, 可以给 SHM_RDONLY 为只读模式,其他的为读写
shmdt:
int shmdt(const void *shmaddr);
shmdt()断开当前进程的 shmaddr 指向的共享内存映射
shmdt()成功返回 0, 失败返回-1
shmctl:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmctl()控制共享内存
shmctl()成功返回 0,失败返回-1
cmd: IPC_RMID //删除共享内存
四、消息队列
消息队列的原理:
消息队列的定义和使用的:
头文件#include<sys/msg.h>
msgget:
int msgget((key_t) key, int msqflg);
msgget()创建或者获取一个消息队列
msgget()成功返回消息队列 ID,失败返回-1
msqflg: IPC_CREAT
msgsnd:
int msgsnd(int msqid, const void *msqp, size_t msqsz, int msqflg);
msgsnd()发送一条消息,消息结构为:
struct msgbuf
{
long mtype; // 消息类型, 必须大于 0
char mtext[1]; // 消息数据
};
msgsnd()成功返回 0, 失败返回-1
msqsz: 指定 mtext 中有效数据的长度
msqflg:一般设置为 0 可以设置 IPC_NOWAIT
msgrcv:
ssize_t msgrcv(int msqid, void *msgp, size_t msqsz, long msqtyp, int msqflg);
msgrcv()接收一条消息
msgrcv()成功返回 mtext 中接收到的数据长度, 失败返回-1
msqtyp: 指定接收的消息类型,类型可以为 0
msqflg: 一般设置为 0 可以设置 IPC_NOWAIT
msgctl:
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msgctl()控制消息队列
msgctl()成功返回 0,失败返回-1
cmd: IPC_RMID
a.c:
b.c:
总结:
四种进程间通信的方法其实实现的原理都很像,都是申请一个空间将所要传递的信息暂时保存,然后等待另一个进程将信息取出来,实现进程间的通信。
不论是管道,信号量,共享内存,消息队列都是有限的资源,所以用完之后要回收,否则当资源用完就无法再申请了。