进程间通信(Inter Process Communication,IPC)根据通信需求不同提供了不同方式:管道,共享内存,消息队列,信号量。
本篇博客主要是介绍进程间通信的 共享内存 部分内容!
共享内存
用于进程间的数据共享。
通信原理:开辟一块物理内存空间,各个进程将同一块物理空间映射到自己的虚拟地址空间中,通过虚拟地址进行访问,进而实现数据共享。
共享内存是最快的进程间通信方式,因为通过虚拟地址空间映射后,直接通过虚拟地址访问物理内存,相较于其他方式少了两部数据拷贝操作。
操作流程:
1、创建或打开共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
参数:
key: 标识符 -- 通过相同的标识符,多个进程可以打开同一个共享内存(内存空间)
size: 要创建的共享内存大小
shmflg: 打开方式 + 权限; IPC_CREAT | IPC_EXCL | 0664 -- 文件未存在则创建,存在则报错
返回值:成功返回非负整数 ,即 操作句柄;失败返回 -1
2、与进程建立映射关系
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
参数:
shmid: shmget返回的操作句柄
shmaddr: 映射首地址,通常置NULL
shmflg: SHM_RDONLY - 只读;0 - 可读可写
返回值:成功返回映射后的首地址 ; 失败返回(void*)-1 (-1是负1,不是减1)
3、对共享内存进行内存操作
从首地址开始,覆盖式写入
如:memcpy,strcpy,printf...
4、与进程间解除映射关系
int shmdt(const void *shmaddr);
参数:
shmaddr: shmat 返回的映射首地址
返回值:成功返回0,失败返回 -1
5、删除共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid: shmget返回的操作句柄
cmd:要对共享内存进行的操作
IPC_RMID:标记要删除的共享内存,
映射连接数为0时,删除共享内存;禁止新的映射连接产生
buf: 用于获取或设置共享内存属性的,简单使用置NULL即可
返回值:成功返回0,失败返回-1
注:类似函数或指令:
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
用于产生一个 IPC key 标识符
解释:ftok - convert a pathname and a project identifier to a System V IPC key,即此函数可以直接以在程序一开始定义 IPC key 进行代替
system V & POSIX : system V -- 类 unix 的接口标准,POSIX -- 可跨平台运行的接口标准
查看 IPC:
ipcs
:查看系统内核间所有进程间通信资源,输入 ipcs
所打印的内容即为以下三种指令的全部内容
a、ipcs -m
: 查看共享内存
b、ipcs -q
:查看消息队列
c、ipcs -s
: 查看信号量
出现内容名称解释:
owner:所有者 perms:操作权限 nattch:映射连接数
删除共享内存:
ipcrm -m xxxx
: 删除指定的共享内存,xxxx
代表共享内存的 shmid
,即操作句柄。如图,共享内存 0x01234567
的操作句柄为3,当运行 ipcrm -m 3
,则该共享内存会被删除!
例程
(1)创建共享内存
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/shm.h>
#define IPC_KEY 0x01234567
int main()
{
//shmget(标识符,大小,打开方式和权限) 大小为32字节,映射连接数为0
int shmid = shmget(IPC_KEY,32,IPC_CREAT|0664);
if(shmid < 0)
{
perror("shmget error");
return -1;
}
return 0;
}
运行结果如图:
在图中,shm_creat.c
为创建共享内存的代码,且其执行文件为 shmcreat
。而上图所展示的是,执行 shmcreat
前后的共享内存的变化!
(2)共享内存的读取
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/shm.h>
#define IPC_KEY 0x01234567 //定义标识符
int main()
{
//1、创建或打开共享内存。shmget(标识符,大小,打开方式和权限) 成功返回共享内存的操作句柄
int shmid = shmget(IPC_KEY,32,IPC_CREAT|0664);
if(shmid < 0)
{
perror("shmget error");
return -1;
}
// 2、建立映射关系。shmat(句柄,映射首地址,访问方式) 成功返回映射后的首地址
void *shm_start = shmat(shmid,NULL,0);
if(shm_start == (void*)-1) //建立映射关系失败
{
perror("shmat error");
return -1;
}
//访问共享内存
while(1)
{
printf("%s\n",(char *)shm_start);
sleep(1);// 1秒钟打印1次
}
//解除映射关系,shmdt(映射首地址)
int ret = shmdt(shm_start);
if(ret < 0)
{
perror("shmdt error");
return -1;
}
//删除共享内存 ,shmctl(句柄,操作类型,信息结构)
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
(3)共享内存的写入
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/shm.h>
#define IPC_KEY 0x01234567
int main()
{
//创建或打开共享内存
int shmid = shmget(IPC_KEY,32,IPC_CREAT | 0664);
if(shmid < 0)
{
perror("shmget error");
return -1;
}
//建立映射关系
void *shm_start = shmat(shmid,NULL,0);
if(shm_start == (void*)-1) //建立映射关系失败
{
perror("shmat error");
return -1;
}
int i=0;
//对共享内存输入内容
while(1)
{
sprintf(shm_start,"写入次数 + %d",i);
sleep(1);
i++;
}
//解除映射
shmdt(shm_start);
//删除共享内存
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
运行结果:
下图中,左边实现的为共享内存的读取,右边实现的是共享内存的写入。
读取程序中设置的是1秒读一次,而写入程序设置的是1秒写入一次,所以下图中左边的 0~12 便是12秒内读取的数据。而当关闭写入的程序,因共享内存中最后的数据为 ”写入数据 + 12 “,所以在读取数据的部分会一直显示共享内存中最后写入的数据!
最后再附一张总结图
(侵权删~)