十、进程间通信-共享内存
一、概述
-
共享内存用于用于进程间的数据共享,
-
开辟一块物理内存空间, 各个进程将同一块物理内存空间映射到自己的虚拟地址空间中, 通过虚拟地址进行访问, 进而实现数据共享
-
共享内存是最快的进程间通信方式, 因为通过虚拟地址空间映射后, 直接通过虚拟地址访问物理内存, 相较于其他方式少了两步数据拷贝的操作.
-
用于高效率传输大量数据
二、共享内存的用法
1、定义一个唯一key(ftok)
key_t ftok(const char *pathname, int proj_id);
2、构造一个共享内存对象(shmget)
(1)头文件
#include <sys/ipc.h>
#include <sys/shm.h>
(2)函数原型
int shmget(key_t key,int size,int shmflg)
(3)参数
-
key: 键值;
-
size: 共享内存的大小。
-
shmflg:
-
IPC_CREATE:共享内存不存在则创建
-
mode:共享内存的权限
-
(4)返回值
-
成功:共享内存ID;
-
失败:-1
3、共享内存映射(shmat)
int shmat(int shmid,const void *shmaddr,int shmflg)
(1)参数
-
shmid:共享内存ID
-
shmaddr:映射地址,NULL为自动分配
-
shmflg:
-
SHM_RDONLY:只读方式映射
-
0:可读可写
-
(2)返回值
- 成功:共享内存首地址
- 失败:-1
4、解除共享内存映射(shmdt)
int shmdt(const void *shmaddr)
(1)参数
- shmaddr:映射的地址
(2)返回值
- 成功:0
- 失败:-1
5、删除共享内存(shmctl RMID)
int shmctl(int shmid,int cmd,struct shmid_ds *buf) //获取或设置共享内存的相关属性
(1)参数:
-
shmid:共享内存ID
-
cmd:
-
- IPC_STAT:获取共享内存的属性信息
-
- IPC_SET:设置共享内存的属性
-
- IPC_RMID:删除共享内存
- buf:属性缓冲区
(2)返回值:
- 成功:由cmd类型决定
- 失败:-1
三、实例
使用共享内存,子进程拷贝数据到共享内存,父进程读取共享内存的数据。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include <sys/shm.h>
#define DELAY_TIME 3
union semun{
int val;
struct seimd_ds *buf;
};
/*初始化信号量*/
int init_sem(int sem_id,int init_value)
{
union semun sem_union;
sem_union.val = init_value;
/*设置信号量集中的一个单独的信号量的值*/
if(semctl(sem_id,0,SETVAL,sem_union) == -1)
{
printf("Initialize semaphore failed!\n");
return -1;
}
return 0;
}
/*删除信号量*/
int del_sem(int sem_id)
{
union semun sem_union;
if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
{
perror("Delete semaohore fail!\n");
return -1;
}
return 0;
}
/*P 操作:执行P操作时,信号量-1*/
int sem_p(int sem_id)
{
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = -1;
sops.sem_flg = SEM_UNDO;/*系统自动释放将会在系统中残留的信号量*/
if(semop(sem_id,&sops,1) == -1)
{
perror("P operation failed\n");
return -1;
}
return 0;
}
/*V 操作:执行P操作时,信号量+1*/
int sem_v(int sem_id)
{
struct sembuf sops;
sops.sem_num = 0; /*单个信号量的编号应该为 0*/
sops.sem_op = 1; /*表示V操作*/
sops.sem_flg = SEM_UNDO; /*系统自动释放将会在系统中残留的信号量*/
if(semop(sem_id,&sops,1) == -1)
{
perror("V operation failed\n");
return -1;
}
return 0;
}
void main(void)
{
pid_t result;
int sem_id;
int shm_id;
char* addr;
//这里6666的key也可以用ftok去生成
sem_id = semget((key_t)6666,1,0666|IPC_CREAT);/*创建一个信号量*/
shm_id = shmget((key_t)7777,1024,0666|IPC_CREAT); /*创建一个共享内存对象*/
init_sem(sem_id,0); //初始化信号量的值为0,只能先执行V操作+1,然后才能执行P操作-1
/*调用fork()函数*/
result = fork();
if(result == -1)
{
perror("Fork\n");
}
else if(result == 0)
{
printf("Child proess will wait for some seconds...\n");
sleep(DELAY_TIME);
/*映射共享内存*/
addr = shmat(shm_id,NULL,0);
if(addr == (void*)-1)
{
printf("shmat111 error!");
exit(-1);
}
/*设置共享内存的内容*/
memcpy(addr,"helloworld",11);
printf("the child process is running...\r\n");
sem_v(sem_id); //如果有其他进程因等待信号量而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.
}
else /*返回值大于0代表父进程*/
{
sem_p(sem_id); //如果信号量等于0,则进程挂起,直到信号量大于0,执行V操作
printf("the father process is running...\r\n");
/*映射共享内存地址*/
addr = shmat(shm_id,NULL,0);
if(addr == (void*)-1)
{
printf("shmat222 error!");
exit(-1);
}
printf("shared memory string:%s\r\n",addr);
/*解除共享内存映射*/
shmdt(addr);
/*删除共享内存映射*/
shmctl(shm_id,IPC_RMID,NULL);
/*删除信号量*/
del_sem(sem_id);
}
exit(0);
}
执行结果:
父进程读出了共享内存地址的数据helloworld,代码中的信号量用于共享资源的互斥。