进程间通信 IPC :管道、信号量、共享内存、消息队列、套接字
进程间通信,两个进程间传递信息
除了套接字,前面几个主要是在同一台主机上两个进程间通信
进程间通信(IPC):信号量_♚陌上花开的博客-CSDN博客_ipc 信号量
进程间通信( IPC):消息队列_♚陌上花开的博客-CSDN博客
共享内存
共享内存为多个进程之间共享和传递数据提供了一种有效的方式。共享内存是先在物理内存上申请一块空间,多个进程可以将其映射到自己的虚拟地址空间中。所有进程都可以访问共享内存中的地址,就好像它们是由 malloc 分配的一样。如果某个进程向共享内存写入了 数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。由于它并未提供同步机制,所以我们通常需要用其他的机制来同步对共享内存的访问。
接口
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
int shmget(key_t key, size_t size, int shmflg); //创建共享内存
shmget() 用于创建或者获取共享内存
shmget() 成功返回共享内存的 ID, 失败返回-1
key: 不同的进程使用相同的 key 值可以获取到同一个共享内存
size: 创建共享内存时,指定要申请的共享内存空间大小,以字节为单位
shmflg: 包含9个比特的权限标志
IPC_CREAT IPC_EXCL
IPC_CREAT:存在则打开,不存在则创建
IPC_EXCL和IPC_CREAT同时使用存在则会报错,不存在创建;
(权限直接加在后面进行赋值)
void* shmat(int shmid, const void *shmaddr, int shmflg); //映射,将共享内存映射到进程的地址空间
shmat() 将申请的共享内存的物理内存映射到当前进程的虚拟地址空间上
shmat() 成功返回返回共享内存的首地址,失败返回 NULL
shmaddr:一般给NULL,由系统自动选择映射的虚拟地址空间
shmflg: 一组标志位,SHM_RND(这个标志与shm_addr联合使用,用来控制共享内存链接共享内存链接的地址) SHM_RDONLY(它使得连接的内存只读),不指定时给0就可以
int shmdt(const void *shmaddr); //断开映射
shmdt() 断开当前进程的 shmaddr 指向的共享内存映射
shmdt() 成功返回 0, 失败返回-1
shmaddr 返回的地址指针
int shmctl(int shmid, int cmd, struct shmid_ds *buf); //控制共享内存,eg:删除
shmctl() 控制共享内存
shmctl() 成功返回 0,失败返回-1
cmd: 要采取的动作
IPC_STAT 把shmid__ds结构中的数据设置为共亨内存的当前关联值
IPC_SET 如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID 删除共享内存段buf: 是一个指针,它指向包含共享内存模式和访问权限的结构。
代码实现
进程 a向共享内存中写入数据,进程 b 从共享内存中读取数据并显示 shma.c 的代码:
a文件输入,b文件输出
a.c文件
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/shm.h>
int main()
{
//创建共享内存
int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);//key值为1234,共享内存大小为128,IPC_CREAT 存在则打开,不存在创建,权限0600
if(shmid==-1)
{
printf("shmget err\n");//创建失败
exit(1);
}
//映射,将共享内存映射到进程的地址空间
//映射成功后就可以通过指针s访问此空间
char*s=(char*)shmat(shmid,NULL,0);//shmat本身返回值void*类型,强转为char*,shmid 共享内存id,给NULL。系统自动选择映射的虚拟地址空间标志位不指定为0
if(s==(char *)-1)
{
printf("shmat err\n");//映射失败
exit(1);
}
//向共享内存写入数据
strcpy(s,"hello");
shmdt(s);//断开映射
exit(0);
}
b.c文件
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/shm.h>
int main()
{
//创建共享内存,存在就获取共享内存
int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);//key值为1234,共享内存大小为128,IPC_CREAT 存在则打开,不存在创建,权限0600
if(shmid==-1)
{
printf("shmget err\n");//创建失败
exit(1);
}
//映射,将共享内存映射到进程的地址空间
//映射成功后就可以通过指针s访问此空间
char*s=(char*)shmat(shmid,NULL,0);//shmat本身返回值void*类型,强转为char*,shmid 共享内存id,给NULL。系统自动选择映射的虚拟地址空间 标
志位不指定为0
if(s==(char *)-1)
{
printf("shmat err\n");//映射失败
exit(1);
}
//用test文件读取共享内存空间中的值
printf("read:%s\n",s);
shmdt(s);//断开映射
exit(0);
}
共享内存创建成功后,会一直存在,即使程序运行结束,也还会存在,除非删除此共享内存或者重启
a.c文件持续写入,b.c文件持续读取
a.c文件
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/shm.h>
int main()
{
//创建共享内存
int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);//key值为1234,共享内存大小为128,IPC_CREAT 存在则打开,不存在创建,权限0600
if(shmid==-1)
{
printf("shmget err\n");//创建失败
exit(1);
}
//映射,将共享内存映射到进程的地址空间
//映射成功后就可以通过指针s访问此空间
char*s=(char*)shmat(shmid,NULL,0);//shmat本身返回值void*类型,强转为char*,shmid 共享内存id,给NULL。系统自动选择映射的虚拟地址空间标志位不指定为0
if(s==(char *)-1)
{
printf("shmat err\n");//映射失败
exit(1);
}
while(1)
{
printf("input\n");
char buff[128]={0};//将输入的的数据放在buff中
fgets(buff,128,stdin);
strcpy(s,buff);//先将数据写入共享内存是为了也可以让读取数据时以end为结束标志
if(strncmp(buff,"end",3)==0)
{
break;//以end为输入结束的标志
}
}
shmdt(s);//断开映射
exit(0);
}
b.c文件
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/shm.h>
int main()
{
//创建共享内存,存在就获取共享内存
int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);//key值为1234,共享内存大小为128,IPC_CREAT 存在则打开,不存在创建,权限0600
if(shmid==-1)
{
printf("shmget err\n");//创建失败
exit(1);
}
//映射,将共享内存映射到进程的地址空间
//映射成功后就可以通过指针s访问此空间
char*s=(char*)shmat(shmid,NULL,0);//shmat本身返回值void*类型,强转为char*,shmid 共享内存id,给NULL。系统自动选择映射的虚拟地址空间 标
志位不指定为0
if(s==(char *)-1)
{
printf("shmat err\n");//映射失败
exit(1);
}
while(1)
{
if(strncmp(s,"end",3)==0)
{
break;//先判断是不是结束标志,不是再输出
}
sleep(1);
printf("resf:%s\n",s);//以此读取数据
}
shmdt(s);//断开映射
exit(0);
}
运行结果如下:
我们可以看到,b是可以读到共享内存的,a输入数据b可以读取,但是只要我们不输入新的数据,b就一直读取上一个数据,
我们想要的结果是写一次,读一次,不写就不读,我们可以通过信号量解决这个问题
使用信号量控制共享内存
此时我们需要两个信号量,s1,s2
sem.h
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/sem.h>
#define SEM1 0 //第一个信号量
#define SEM2 1 //第二个信号量
#define NUM 2 //信号量总个数
union semun
{
int val;//初始值
};//自己定义
void sem_init();//信号量初始化
void sem_p(int index);//P操作,创建两个信号量,用下标区分
void sem_v(int index);//V操作
void sem_destroy();//销毁
sem.c
#include"sem.h"
static int semid = -1;//信号量id,只在本文件有效
void sem_init()//信号量初始化
{
semid = semget((key_t)1234,NUM,IPC_CREAT|IPC_EXCL|0600);//key值为1234,SEM个信号量,全新创建一个信号量,如果已存在就报错,存取权限0600
if(semid == -1)//全新创建失败,说明已存在或者创建失败
{
semid=semget((key_t)1234,NUM,0600);
//semid = semget((key_t)1234,SEM,0600);//当信号已存在,获取信号量
if(semid == -1)
{
printf("semget err\n");//前面获取失败说明是创建的时候就失败了
}
}
else//因为要创建NUM个信号量,循环初始化
{
union semun a;
int arr[NUM]={1,0};//信号量初始值数组
for(int i=0;i<NUM;i++)
{
a.val = arr[i];//信号量的初始值
if(semctl(semid,i,SETVAL,a)==-1)//i,信号量个数。SETVAL,a,赋初值
{
printf("semctl init err\n");//初始化失败打印此语句
}
}
}
}
void sem_p(int index)//P操作
{
if(index<0 || index>=NUM)
{
return;
}
struct sembuf buf;//对信号量的改变
buf.sem_num=index;//信号量的下标
buf.sem_op=-1;//对信号量执行的是p操作
buf.sem_flg = SEM_UNDO;//标志位,如果程序异常可以帮助进行v操作
if(semop(semid,&buf,1)==-1)
{
printf("semop p err\n");
}
}
void sem_v(int index)//V操作
{
if(index<0 || index>=NUM)
{
return;
}
struct sembuf buf;//对信号量的改变
buf.sem_num=index;//信号量的下标
buf.sem_op=1;//对信号量执行的是v操作
buf.sem_flg = SEM_UNDO;
if(semop(semid,&buf,1)==-1)
{
printf("semop v err\n");
}
}
void sem_destroy()//销毁
{
if(semctl(semid,0,IPC_RMID)==-1)//semid 要删除的信号,0 占位,根据semid删除创建的所有信号。IPC_RMID 删除信号量
{
printf("semctl destroy err\n");
}
}
a.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/shm.h>
#include"sem.h"
int main()
{
//创建共享内存
int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);//key值为1234,共享内存大小为128,IPC_CREAT 存在则打开,不存在创建,权限0600
if(shmid==-1)
{
printf("shmget err\n");//创建失败
exit(1);
}
//映射,将共享内存映射到进程的地址空间
//映射成功后就可以通过指针s访问此空间
char*s=(char*)shmat(shmid,NULL,0);//shmat本身返回值void*类型,强转为char*,shmid 共享内存id,给NULL。系统自动选择映射的虚拟地址空间 标>
志位不指定为0
if(s==(char *)-1)
{
printf("shmat err\n");//映射失败
exit(1);
}
sem_init();//信号量初始化
while(1)
{
printf("input\n");
char buff[128]={0};//将输入的的数据放在buff中
fgets(buff,128,stdin);
sem_p(SEM1);//对a文件先P操作
strcpy(s,buff);//先将数据写入共享内存是为了也可以让读取数据时以end为结束标志
sem_v(SEM2);//对b文件进行v操作,不能将v操作放在下面的判读之后,因为如果最后输出end结束后,b文件不能进行v操作获得权限就无法打印最后
一个
if(strncmp(buff,"end",3)==0)
{
break;//以end为输入结束的标志
}
}
shmdt(s);//断开映射
exit(0);
}
b.c文件
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/shm.h>
#include"sem.h"
int main()
{
//创建共享内存,存在就获取共享内存
int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);//key值为1234,共享内存大小为128,IPC_CREAT 存在则打开,不存在创建,权限0600
if(shmid==-1)
{
printf("shmget err\n");//创建失败
exit(1);
}
//映射,将共享内存映射到进程的地址空间
//映射成功后就可以通过指针s访问此空间
char*s=(char*)shmat(shmid,NULL,0);//shmat本身返回值void*类型,强转为char*,shmid 共享内存id,给NULL。系统自动选择映射的虚拟地址空间 标>
志位不指定为0
if(s==(char *)-1)
{
printf("shmat err\n");//映射失败
exit(1);
}
sem_init();//初始化信号量
while(1)
{
sem_p(SEM2);
if(strncmp(s,"end",3)==0)
{
break;//先判断是不是结束标志,不是再输出
}
printf("resf:%s\n",s);//以此读取数据
sem_v(SEM1);
}
shmdt(s);//断开映射
sem_destroy();//销毁信号量
exit(0);
}
运行程序
运行结果