一、共享内存
1、
共享内存为多个进程之间共享和传递数据提供了一种有效的方式。共享内存是先在物理内存上申请一块空间,多个进程可以将其映射到自己的虚拟地址空间中。 所有进程都可以访问共享内存中的地址,就好像它们是由 malloc 分配的一样。如果某个进程向共享内存写入了数据,所做的改动将立刻被可以访问同一段共享内存的任何其他 进程看到。
由于它并未提供同步机制,所以我们通常需要用他的机制来同步对共享内存的访问。
共享内存实际是操作系统在实际物理内存中开辟的一段内存。
共享内存实现进程间通信,是操作系统在实际物理内存开辟一块空间,一个进程在自己的页表中,将该空间和进程地址空间上的共享区的一块地址空间形成映射关系。另外一进程在页表上,将同一块物理空间和该进程地址空间上的共享区的一块地址空间形成映射关系。
当一个进程往该空间写入内容时,另外一进程访问该空间,会得到写入的值,即实现了进程间的通信。
要实现进程间通信需要两个进程能够看到同一块空间,系统开辟的共享内存就是两进程看到的同一资源。
二、共享内存函数的介绍
1、shmget
int shmget(key_t key, size_t size, int shmflg);
shmget()
:用于创建或者获取共享内存
shmget()
:成功返回共享内存的 ID, 失败返回-1
key
:不同的进程使用相同的 key 值可以获取到同一个共享内存,(这里的值和信号量的值一样也没有关系,因为类型不一样;)
size
:创建共享内存时,指定要申请的
2、shmat – 映射
void* shmat(int shmid, const void *shmaddr, int shmflg);
shmat()
:将申请的共享内存的物理内存映射到当前进程的虚拟地址空间上
shmat()
:成功返回返回共享内存的首地址,失败返回 NULL(像malloc一样给共享空间的起始地址),看帮助手册失败返回的是-1;
shmaddr
:一般给 NULL,由系统自动选择映射的虚拟地址空间;
shmflg
: 一般给 0(给0就代表的可读可写), 可以SHM_RDONLY 为只读模式,其他的为读写
3、shmdt – 断开
int shmdt(const void *shmaddr);
shmdt()
:断开当前进程的 shmaddr 指向的共享内存映射;
shmdt()
:成功返回 0, 失败返回-1
4、shmctl – 控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmctl()
:控制共享内存
shmctl()
:成功返回 0,失败返回-1
cmd
: IPC_RMID
三、共享内存使用示例
1、进程a向共享内存写入数据,进程b从共享内存中读取数据并显示;
代码:
main.c :
//main.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.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)
{
exit(1);
}
strcpy(s,"hello");
shmdt(s);
exit(0);
}
test.c :
//test.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.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)
{
exit(1);
}
printf("%s",s);
shmdt(s);
shmctl(shmid,IPC_RMID,NULL);
exit(0);
}
可见,原本运行./test不会打印东西,运行完./main之后,再运行./test就能输出hello。
./main将hello写入到共享内存中后,./test才能将共享内存中的数据打印出来。
2、进程a从键盘循环获取数据并拷贝到共享内存中,进程b从共享内存中获取并打印(不加控制)
代码:
不加控制的使用会出问题:
main.c :
//不加控制的使用,改为循环从键盘获取,循环读取数据
//main.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.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)
{
exit(1);
}
// strcpy(s,"hello");
while(1)
{
printf("input :\n");
char buff[128]={0};
fgets(buff,128,stdin);
strcpy(s,buff);
if(strncmp(buff,"end",3)==0)
{
break;
}
}
shmdt(s);
exit(0);
}
//test.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.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)
{
exit(1);
}
// printf("%s",s);
while(1)
{
if(strncmp(s,"end",3)==0)
{
break;
}
printf("read:%s\n",s);
sleep(1);
}
shmdt(s);
shmctl(shmid,IPC_RMID,NULL);
exit(0);
}
//不加控制的使用会出问题;
那为什么会出现这个问题呢?
没有对两者进行控制。
所以,必须要使用信号量;
一个信号量不可以:
必须需要两个信号量进行控制:
3、进程a从键盘循环获取数据并拷贝到共享内存中,进程b从共享内存中获取并打印(加信号量进行控制)
1)示例代码:
sem.h
#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/sem.h>
union semun
{
int val;
};
void sem_init();//初始化
void sem_p(int index);//p操作,index为下标
void sem_v(int index);//v操作
void sem_destroy();//销毁
sem.c
#include "sem.h"
#define SEM_NUM 2 //宏,创建信号量的数量
static int semid = -1;//信号量的id
void sem_init()//共享内存的创建
{
semid = semget((key_t)1234,SEM_NUM,IPC_CREAT|IPC_EXCL|0600);//全新创建,1234为自己定义,0600为权限
if (semid == -1 )//创建失败,可能已经被创建或是真的创建失败
{
//尝试获取,可能被创建
semid = semget((key_t)1234,SEM_NUM,0600);
if ( semid == -1)
{
printf("semget err\n");
return;
}
}
else//全新创建成功
{
int arr[SEM_NUM] = {1,0};
for( int i = 0; i < SEM_NUM; i++ )//对创建的信号量进行初始值设定
{
union semun a;
a.val = arr[i];
if ( semctl(semid,i,SETVAL,a) == -1 )//全新创建成功,就初始化
{
printf("semctl err\n");
}
}
}
}
void sem_p(int index)//p操作
{
if ( index < 0 || index >= SEM_NUM )
{
return;
}
struct sembuf buf;
buf.sem_num = index;
buf.sem_op = -1;//p
buf.sem_flg = SEM_UNDO;
if ( semop(semid,&buf,1) == -1 )
{
printf("sem p err\n");
}
}
void sem_v(int index)//v操作
{
if ( index < 0 || index >= SEM_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("sem v err\n");
}
}
void sem_destroy()//销毁
{
if ( semctl(semid,0,IPC_RMID) == -1 )
{
printf("semctl del 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,256,0600|IPC_CREAT);//创建/获取共享内存
if ( shmid == -1 )
{
printf("shmget err\n");
exit(0);
}
char* s = (char*)shmat(shmid,NULL,0);
if ( s == (char*)-1 )
{
printf("shmat err\n");
exit(0);
}
sem_init();//2 1,0
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);
}
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,256,IPC_CREAT|0600);
if ( shmid == -1 )
{
printf("shmget err\n");
exit(0);
}
char* s = shmat(shmid,NULL,0);
if ( s == (char*)-1)
{
printf("shmat err\n");
exit(0);
}
sem_init();
while( 1 )
{
sem_p(1);
if ( strncmp(s,"end",3) == 0 )
{
break;
}
printf("read:%s",s);
sem_v(0);
}
shmdt(s);
sem_destroy();
shmctl(shmid,IPC_RMID,NULL);
}
2)代码运行
编译(记得带上sem.c):
记得检查共享内存、信号量是否销毁移除:
ipcs命令查看
ipcs -s :移除信号量
ipcs -m:移除
共享内存