目录
共享内存,顾名思义就是申请一段公共的内存空间,供多个进程共同访问;各个进程分工对这段内存进行读写,达到通信的目的。
各个进程从自己的逻辑空间到这段物理内存位置,建立一个映射。
共享内存接口未提供对共享内存访问的同步控制机制,这需要我们自己完成。就可以使用信号量机制来实现。
共享内存机制接口都在 头文件<sys/shm.h> 中声明。
1、shmget
用于申请一段新的共享内存段,或通过key获取一个已有的共享内存段的标识符。
函数原型:
int shmget(key_t key, size_t size, int shmflg);
key:一个键值 ,可以是任意一个合法范围内的数值。各个进程通过同一个键值,来获取一段相同的共享内存的标识符。
size:以字节为单位指定共享内存段的大小。
shmflg:包含9个bit的权限标志,作用与创建文件时使用的mode标志一样。权限标志用来指定第一个申请该共享内存的进程 、同组的进程和其他进程对这段共享内存的访问权限。由一个 特殊标志IPC_CREAT 与 权限标志 按位或才能申请一个新的共享内存段。
成功返回共享内存标识符;失败返回-1。
2、shmat
用于建立本进程的逻辑地址与共享内存的映射。
函数原型:
void *shmat(int shm_id, const void *shm_addr, int shmflg);
shm_id:shmget()获取到的共享内存标识符。
shm_addr:指定将共享内存连接到当前进程中的地址位置。可以为NULL,由系统来选择与共享内存建立映射的地址。
shmflg:有两个可能取值是SHM_RND(这个标志与shm_addr联用,用来控制共享内存连接的地址)和SHM_RDONLY(对连接的共享内存只读)。SHM_RDONLY被设置后,即使在shmget()时设置有读外的其他权限,其他权限也会失效。
成功返回将共享内存连接到本进程的内存中的地址;失败返回NULL。
3、shmdt
用于释放本进程与共享内存的连接。而不是释放共享内存段。
函数原型:
int shmdt(const void* shmaddr);
shmaddr:shmat()返回的将共享内存连接到本进程的内存中的地址。
成功返回0;失败返回-1。
4、shmctl
用于控制共享内存段。设置或获取共享内存的访问权限,或释放共享内存段。
函数原型:
int shmctl(int shm_id, int command, struct shmid_ds *buf);
shmid:共享内存标识符。
buf:结构体shmid_ds的指针。
struct shmid_ds{
uid_t shm_perm.uid;//进程所属用户的id
uid_t shm_perm.gid;//进程所属用户的组id
mode_t shm_perm.mode;//访问权限
}
command:要采取的动作:
命令 | 说明 |
IPC_STAT | 将共享内存当前的权限值保存到shmid_ds结构中 |
IPC_SET | 把共享内存当前的权限值设置为shmid_ds结构中保存的 |
IPC_RMID | 删除共享内存段 |
5、实操
Reader进程读取共享内存内容,Writer进程向共享内存中写入数据。使用信号量控制同步。
信号量使用Linux信号量中封装的信号量接口。
//sem.h
#include<stdio.h>
#include<unistd.h>
#include<sys/sem.h>
#include<stdlib.h>
union semun{
int value;
};
void sem_init();//信号量初始值
void sem_p();
void sem_v();
void sem_destroy();
//sem.c
#include"sem.h"
static int semid;//静态全局变量 .data区 本文件有效
void sem_init(){
semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);
//创建失败(已存在,直接获取)
if(semid==-1){
semid=semget((key_t)1234,1,0600);//已存在,获取
if(semid==-1) exit(1);
}
else{
union semun a;
a.value=1;
if(semctl(semid,0,SETVAL,a)==-1){//第二个参数 下标
perror("semctl err");
exit(1);
}
}
}
void sem_p(){
struct sembuf buf;
buf.sem_num=0;
buf.sem_op=-1;
buf.sem_flg=SEM_UNDO;
if(semop(semid,&buf,1)==-1){
perror("semop err");
exit(1);
}
}
void sem_v(){
struct sembuf buf;
buf.sem_num=0;
buf.sem_op=1;
buf.sem_flg=SEM_UNDO;
if(semop(semid,&buf,1)==-1){
perror("semop err");
exit(1);
}
}
void sem_destroy(){
if(semctl(semid,0,IPC_RMID)==-1){
perror("semctl err");
exit(1);
}
}
//Reader.c
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/shm.h>
#include"../信号量/sem.h"
int main(){
sem_init();
int shmid = shmget((key_t)1234,1024, 0666 | IPC_CREAT);
if(shmid==-1){
fprintf(stderr,"shmget failed\n");
exit(1);
}
char *m=shmat(shmid,NULL,0);
if(m==NULL){
fprintf(stderr,"shmat failed\n");
exit(1);
}
while(1){
printf("Waiting for writer...\n");
sem_p();
printf("read: %s\n",m);
sleep(rand()%4);
sem_v();
if(strncmp(m,"end",3)==0){
break;
}
}
if(shmdt(m)==-1){//删除连接
fprintf(stderr,"shmdt failed\n");
exit(1);
}
//再由读进程负责释放共享内存段
if(shmctl(shmid,IPC_RMID,NULL)==-1){
fprintf(stderr,"shmctl(IPC_RMID) failed\n");
exit(1);
}
sem_destroy();
exit(0);
}
//Writer.c
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/shm.h>
#include"../信号量/sem.h"
int main(){
sem_init();
int shmid = shmget((key_t)1234, 1024, 0666 | IPC_CREAT);
if(shmid==-1){
fprintf(stderr,"shmget failed\n");
exit(1);
}
char *m=shmat(shmid,NULL,0);
if(m==NULL){
fprintf(stderr,"shmat failed\n");
exit(1);
}
printf("Memory attached at %p\n",m);
while(1){
sem_p();
char buff[1024]={0};
//从键盘输入数据
printf("Enter some text: ");
fgets(buff,1024,stdin);
strcpy(m,buff);
sem_v();
if(strncmp(buff,"end",3)==0){
break;
}
}
if(shmdt(m)==-1){//写进程只删除连接
fprintf(stderr,"shmdt failed\n");
exit(1);
}
exit(0);
}
先执行写进程,申请到共享内存段,进行p操作,获取到对共享内存的操作权限,等待输入。读进程根据相同的键值,获取到共享内存的标识符,建立映射,执行p操作阻塞。
从键盘输入 hello ,写进程将hello写入共享内存段,执行v操作。此时,读进程p操作成功,获取到对共享内存操作的权限,并从共享内存段中读到 hello 。再随机等待几秒后执行v操作,再使写进程能p操作成功。
输入end后结束。