一、共享内存简介
1、共享内存是被多个进程共享的一部分物理内存。
2、所有用户空间的进程若要操作共享内存,都要将其映射到自己虚拟内存空间中,通过映射的虚拟内存空间地址去操作共享内存,从而达到进程间的数据通信。
3、共享内存是进程间共享数据的一种最快的方式,一种高效的IPC机制。
二、API
1、创建共享内存
int shmget(key_t key, size_t size , int shmflag);
参数:
key: 共享内存的键值,用户指定。
size: 共享内存的大小。
shmflag: IPC_CREAT、IPC_EXCL、0777等权限的组合
返回: 成功,返回内核中共享内存的标识ID, 失败,返回-1。
2、共享内存控制
int shmctl ( int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid:共享内存ID
cmd:
IPC_STAT 获取共享内存属性
IPC_SET 设置共享内存属性
IPC_RMID 删除共享内存
buf : 共享内存属性指针
返回: 成功返回0,出错返回-1
3、共享内存映射
void * shmat( int shmid ,char * shmaddr, int shmflag);
参数:
shmid : 共享内存ID
shmaddr: 映射到进程虚拟内存空间的地址,一般设置0,由系统分配
shmflag : 若shmaddr 设置0,shmflag也设置为0
返回:成功返回共享内存映射到虚拟内存空间中的地址,失败返回-1.
Note: 子进程不继承父进程创建的共享内存,大家是共享的,但是子进程继承父进程映射的地址。
4、共享内存解除映射
int shmdt(char * shmaddr);
返回: 失败返回-1.
三、代码实践
父进程创建共享内存,父进程向共享内存中写数据,写完后通知子进程,子进程从共享内存中读数据。
细节:1、父进程在未写完共享内存之前,子进程处于等待状态,此时的通知和阻塞暂时用管道的特性来模拟(后面用信号量取代)。
2、子进程继承父进程创建的管道,所以两个进程用完后都有将管道销毁
3、子进程只是继承了父进程共享内存映射到虚拟内存的地址,共享内存就一段,所以两个进程只销毁一次共享内存就够了,但都要解除共享内存映射。
创建tell.c 和 tell.h 用管道的特性类实现两个进程的信息同步。
#include <stdio.h>
#include <unistd.h>
#include "tell.h"
int fd[2];
//管道初始化
void init()
{
if(pipe(fd) < 0)
{
printf("pipe create error\n");
}
}
//利用管道进行等待
void wait_pipe()
{
char c;
//利用管道默认是阻塞的特性
if(read(fd[0],&c,1) < 0)
{
printf("pipe read error\n");
}
}
//利用管道进行通知
void notify_pipe()
{
char c = 'c';
if(write(fd[1],&c,1) < 0)
{
printf("pipe write error\n");
}
}
//销毁管道
void destory_pipe()
{
close(fd[0]);
close(fd[1]);
}
实现共享内存的进程间访问
#include <stdio.h>
#include "tell.h"
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
/* 父进程创建共享内存,父进程向共享内存中写数据,
*子进程从共享内存中读数据.
* */
int main(void)
{
//父进程创建共享内存,大小1024
int shmId = shmget(IPC_PRIVATE,1024,IPC_CREAT|IPC_EXCL|0777);
if(shmId < 0)
{
printf("share mem create error\n");
return -1;
}
//初始化管道
init();
pid_t pid = fork();
if(pid > 0)
{//parent process
//将共享内存映射到父进程虚拟内存空间中
int *p = (int *)shmat(shmId,0,0);
if(p == (int *) (-1))
{
printf("parent process shmat error\n");
return -1;
}
//向共享内存中写入数据
*p = 100;
*(p+1) = 200;
//通知数据已经写好了
notify_pipe();
//父进程共享内存结束映射
shmdt((char *)p);
//父进程创建的pipe,子进程也会继承,所以父子进程都有读端和写段两个管道,
//用完了都需要将其销毁
destory_pipe();
//等待回收子进程
wait(0);
}else if(pid == 0)
{//child parent
//等待共享内存写好数据
wait_pipe();
//子进程将共享内存映射到自己虚拟空间中
int *p = (int *)shmat(shmId,0,0);
if(p == (int *)-1)
{
printf("child process shmat error\n");
return -1;
}
//读共享内存
printf("share mem contex:start:%d,end:%d\n",*p,*(p+1));
//子进程共享内存结束映射
shmdt((char *)p);
//子进程也要销毁管道
destory_pipe();
//共享内存不需要继承,只有一份,所以,在子进程或者父进程删除一次就行
shmctl(shmId,IPC_RMID,NULL);
}
return 0;
}
测试结果: