目录
一、概念
- 共享内存为多个进程之间共享和传递数据提供了一种有效地方式。共享内存实现在物理内存上申请一块空间,多个进程可以将其映射到自己的虚拟地址空间中。所有进程都可以访问共享内存中的地址,就好像它们是由malloc分配的一样。如果某个进程向共享内存写入数据,所做的改动将立刻被可以访问同一段共享内存的其它进程看到。由于它并未提供同步机制所以我们通常需要其他的机制来同步对共享内存的访问。
- 共享内存允许两个不相关的进程访问同一个逻辑内存。
- 下图显示出进程的逻辑地址空间到可用物理内存的映射关系。
- 如果A,B共享内存:A进程写入数据,B进程就可直接使用
- 操作系统保证进程是相互独立的,但是共享内存打破这种规则,使之可共享使用——共享内存为多个进程共享和传递数据。
二、共享内存函数
头文件:
#include<sys/shm.h>
1.shmget
int shmget(key_t key,size_t size,int shmflg);
- 作用:创建或获取共享内存
- 参数:
- key:不同进程使用相同key值可以获取到同一个共享内存(这里的值和信号量的值一样也没关系,因为类型不一样),一般为1234。
- size:创建共享内存时,指定要申请的内存容量
- shmflg:包含9个比特的权限标志,他们的作用与创建文件时使用的mode标志一样。
- 返回值:成功返回共享内存ID,失败返回-1
2.shmat
void* shmat(int shmid, const void *shmaddr, int shmflg);
- 作用:将申请的共享内存的物理内存映射到当前进程的虚拟地址空间上
- 返回值:成功返回共享内存的首地址,失败返回NULL,或者1(返回1居多)
3.shmaddr
一般传NULL,由系统自动选择映射的虚拟地址空间
4.shmflg
一般给0代表可读可写,可以给SHM_RDONLY 为只读模式,其他为读写
5.shmdt
int shmdt(const void *shmaddr);
- 作用:断开当前进程的shmaddr指向的共享内存映射
- 参数:shmat返回的地址指针
- 返回值:成功返回 0, 失败返回-1
6.shmctl
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 功能:控制共享内存,设置信号量
- 参数:
- shmid是shmget返回的共享内存标识符,也就是信号量的ID
- cmd是要采取的动作,可以取三个值:
- IPC_STAT 把shmid_ds结构中的数据设置为共享内存的当前关联值
- IPC_SET 如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
- IPC_RMID 删除共享内存段
- buf是一个指针,指向包含共享内存模式和访问权限的结构。
- 返回值:成功返回 0,失败返回-1
7.删除共享内存
操作:ipcrm -m id
三、共享内存的使用
例1:进程A向共享内存写入数据,进程B从共享内存中读取数据并显示
- a.c:写入hello
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<assert.h> #include<unistd.h> #include<sys/shm.h> //一个写入数据,一个读数据 int main() { int shmid=shmget((key_t)1234,256,IPC_CREAT|0600); assert(shmid!=-1); char* s=(char* )shmat(shmid,NULL,0); if(s==(char*)-1)//man shmat查看帮助手册,失败返回-1 { exit(1); } strcpy(s,"hello"); shmdt(s); exit(0); }
- test.c 取数据,可以读出"hello",虽然test内没有写入,但是共享内存,可以读取main写入的数据
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<assert.h> #include<unistd.h> #include<sys/shm.h> int main() { int shmid=shmget((key_t)1234,256,IPC_CREAT|0600); assert(shmid!=-1); char* s=(char* )shmat(shmid,NULL,0); if(s==(char*)-1)//man shmat查看帮助手册,失败返回-1 { exit(1); } printf("%s",s); shmdt(s); shmctl(shmid,IPC_RMID,NULL);//IPC_RMID:删除,返回NULL即可 exit(0); }
例2:进程A从键盘循环获取数据并拷贝到共享内存中,进程b从共享内存中获取并打印数据。要求进程a输入一次,进程b输出一次,a不输入,b就不输出。
解析:fgets()可以控制输入
而输出的时候一直输出S内的数据,没有控制,会一直循环输出
做法:信号量控制
信号量一般辅助共享内存使用
正确流程是写入一次读取一次,不写入不读取
1.封装信号量
2.A B调用信号量
代码如下:
sem.h
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<sys/sem.h>
#define SEM1 0
#define SEM2 1
#define SEM_NUM 2
union semun
{
int val;
};
void sem_init();
void sem_p(int index);//两个信号两,需要传参,定义传给哪个信号量
void sem_v(int index);
void sem_destory();
sem.c
#include"sem.h"
static int semid=-1;
void sem_init()
{
semid=semget((key_t)1234,SEM_NUM,IPC_CREAT|IPC_EXCL|0600);
if(semid==-1)
{
semid=semget((key_t)1234,SEM_NUM,IPC_EXCL|0600);
if(semid==-1)
perror("create sem error\n");
}
else
{
union semun a;
int arr[SEM_NUM]={1,0};
for(int i=0;i<SEM_NUM;i++)
{
a.val=arr[i];
if(semctl(semid,i,SETVAL,a)==-1)
{
perror("semctl setval error!\n");
}
}
}
}
void sem_p(int index)
{
if(index<0||index>=SEM_NUM)
{
return;
}
struct sembuf buf;
buf.sem_num=index;
buf.sem_op=-1;
buf.sem_flg=SEM_UNDO;
if(semop(semid,&buf,1)==-1)
{
printf("sem p err\n");
}
}
void sem_v(int index)
{
if(index<0||index>=SEM_NUM)
{
return;
}
struct sembuf buf;
buf.sem_num=index;
buf.sem_op=1;
buf.sem_flg=SEM_UNDO;
if(semop(semid,&buf,1)==-1)
{
printf("sem v err\n");
}
}
void sem_destory()
{
if(semctl(semid,0,IPC_RMID)==-1)
{
printf("semctl del err\n");
}
}
main.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<sys/shm.h>
#include"sem.h"
//一个写入数据,一个读数据
int main()
{
int shmid=shmget((key_t)1234,256,IPC_CREAT|0600);
assert(shmid!=-1);
char* s=(char* )shmat(shmid,NULL,0);
if(s==(char*)-1)//man shmat查看帮助手册,失败返回-1
{
exit(1);
}
sem_init();
while(1)
{
printf("input:\n");
char buff[128]={0};
fgets(buff,128,stdin);
sem_p(0);
strcpy(s,buff);
sem_v(1);
if(strncmp(buff,"end",3)==0)
{
break;
}
}
shmdt(s);
exit(0);
}
test.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<sys/shm.h>
#include"sem.h"
//一个写入数据,一个读数据
int main()
{
int shmid=shmget((key_t)1234,256,IPC_CREAT|0600);
assert(shmid!=-1);
char* s=(char* )shmat(shmid,NULL,0);
if(s==(char*)-1)//man shmat查看帮助手册,失败返回-1
{
exit(1);
}
sem_init();
while(1)
{
sem_p(1);
if(strncmp(s,"end",3)==0)
{
break;
}
printf("read:%s\n",s);
sem_v(0);
}
shmdt(s);
sem_destory();
exit(0);
}
执行结果:
A(./main)未输入时,B(./test)不输出(阻塞着)
A输入一个,B读取一个
A输入end,A,B退出