一、概念与特点
- 共享存储允许两个或多个进程共享一个给定的存储区
- 共享存储不属于某一特定进程,申请时是由系统提供的,大家都可以使用
- 因为数据不需要在进程之间复制,所以这是最快的一种IPC结构
- 重点:父进程fork子进程或者exec执行一个新的程序。在子进程和新程序里面不会继承父进程之前使用的共享存储
二、共享存储的互斥问题
- 因为共享存储大家都可以使用,所以当有多个进程对共享存储进行操作时,需要控制好读写操作(也就是互斥操作)
- 通常,信号量用于同步共享存储的访问(不过前一篇文章最后提到的记录锁或互斥量也可以使用)
三、共享存储与内存映射的不同
- 共享存储在多个进程将同一个文件映射到它们的地址空间的时候,XSI共享存储和内存映射的文件的不同之处在于,共享存储没有相关的文件
- XSI共享存储段是内存的匿名段
四、共享存储结构体(struct shmid_ds)
- 内核为每个共享存储维护着一个结构,该结构只要要为每个共享存储段包含以下成员:
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
- shm_perm成员:见==>https://blog.csdn.net/qq_41453285/article/details/90552522
- shmatt_t数据类型:无符号整型,至少与unsigned short一样大
- shm_nattch:表示有多少个进程正在使用这个共享存储
五、共享存储的系统限制
六、共享存储的创建/引用(shmget函数)
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int flag);
//返回值:成功返回共享存储ID;出错返回-1
- 功能:用来创建或者引用一个共享存储
- 共享存储ID:每个共享存储对应一个ID,此函数的返回值就是共享存储ID
参数:
- key、flag:如何设置见文章==>https://blog.csdn.net/qq_41453285/article/details/90552522
- size:如果此函数用来创建一个共享存储,那么此参数指定共享存储的长度,单位为“字节”,段内的内容全部初始化为0。如果此函数用来引用一个已存在的共享存储,那么此参数指定为0
size参数的注意事项:
- size通常将其向上取为系统页长的整倍数。如果指定的size值并非系统页长的整数倍,那么最后一页的余下部分是不可使用的
如果此函数用来创建一个新的共享存储,那么内核会自动初始化共享存储struct shmid_ds结构体,初始化结果如下:
七、共享存储操作函数(shmctl函数)
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//返回值:成功返回0;失败返回-1
- 功能:此函数可以对参数1指定的共享存储进行多种操作(删除、取信息、加锁、解锁等)
cmd参数:
- 此参数对应如下,此参数对shmid指向的共享存储进行多种不同操作
八、共享存储的连接使用(shmat函数)
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *addr, int flag);
//返回值:成功,返回指向共享存储段的指针;出错返回(void*)-1
- 功能:一旦创建/引用了一个共享存储段,那么进程就可调用shmat函数将其连接到它的地址空间中
- 返回值:成功返回的是该段所连接的十几地址
- 如果shmat成功执行,那么内核将使与该共享存储相关的shmid_ds结构中的shm_nattch计数器值加1
共享存储段连接到调用进程的哪个地址上与addr参数以及flag中是否指定SHM_RND有关:
addr参数:
- 如果addr为0:则此函数连接到由内核选择的共享存储段的第一个可用地址上。这是推荐的使用方法
- 如果addr非0:并且没有指定SHM_RND,则此段连接到参数addr所指定的地址上
- 如果addr非0:并且指定了SHM_RND,则此段连接到(addr-(addr mod SHMLBA))所表示的地址上,该算式是将地址向下去最近1个SHMLBA的倍数
flag参数:
- SHM_RND:意思是“取整”
- SHMLBA:意思是“地边界地址倍数”,它总是2的乘方
- SHM_RDONLY:则以只读方式连接此段,否则以读写方式连接此段
九、 共享存储的分离(shmdt函数)
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
//返回值:成功,返回0;出错返回-1
- 功能:当对共享存储的操作已经结束时,则调用shmdt与该存储段分离
- 参数:shmaddr是上一次调用shmat函数的返回值
- 如果shmat成功执行,那么内核将使与该共享存储相关的shmid_ds结构中的shm_nattch计数器值减1
- 注意:这并不从系统中删除共享存储的标识符以及其相关的数据结构。共享存储的仍然存在,直至某个进程带IPC_RMID命令的调用shmctl特地删除共享存储为止
十、演示案例
- 查看程序的内存模型,以及查看共享存储的内存处于哪一位置
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#define ARRAY_SIZE 40000
#define MALLOC_SIZE 100000
#define SHM_SIZE 100000
#define SHM_MODE 0600
char array[ARRAY_SIZE];
int main()
{
int shmid;
char *ptr,*shmptr;
if((ptr=malloc(MALLOC_SIZE))==NULL){
perror("malloc");
exit(EXIT_FAILURE);
}
if((shmid=shmget(IPC_PRIVATE,SHM_SIZE,SHM_MODE))==-1){
perror("shmget");
exit(EXIT_FAILURE);
}
if((shmptr=shmat(shmid,0,0))==(void*)-1){
perror("shmat");
exit(EXIT_FAILURE);
}
printf("arrar[] from %p to %p\n",(void*)&array[0],(void*)&array[ARRAY_SIZE]);
printf("stack around %p\n",(void*)&shmid);
printf("malloc from %p to %p\n",(void*)ptr,(void*)(ptr+MALLOC_SIZE));
printf("share memory attached from %p to %p\n",(void*)shmptr,(void*)(shmptr+SHM_SIZE));
if(shmctl(shmid,IPC_RMID,0)==-1){
perror("shmctl");
exit(EXIT_FAILURE);
}
exit(0);
}
十一、演示案例
- A_3_shm_A.c创建一个共享存储,然后A_3_shm_B.c引用A_3_shm_A.c创建的共享存储并写入一些内容,写入内容之后A_3_shm_A.c去读取。因为共享存储的操作需要解决互斥的问题,因此程序中用到了信号量的操作解决互斥的问题
- A_3_shm_A.c运行之后阻塞等待信号量变为0,变为0之后读取共享存储的内容。A_3_shm_B.c向共享存储写入内容,之后将信号量变为0。信号量变为0之后A_3_shm_A.c阻塞消失,继续运行,读取A_3_shm_B.c向共享存储读取的内容
//A_3_shm_A.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#define SHM_SIZE 512
#define FLAG IPC_CREAT|0777
#define SHMID 100
#define SEMID 100
#define SHMFILENAME "./shm.txt"
#define SEMFILENAME "./sem.txt"
key_t createKey(char *name,int id);
void create_sem_and_wait();
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
int main()
{
int shmId;
char *shmptr;
key_t key;
key=createKey(SHMFILENAME,SHMID);
//创建共享存储
if((shmId=shmget(key,SHM_SIZE,FLAG))==-1){
printf("A shmget error\n");
exit(EXIT_FAILURE);
}
//以只读的方式连接到共享存储,并且shmptr引用到共享存储的地址上
if((shmptr=shmat(shmId,0,SHM_RDONLY))==(void*)-1){
printf("A shmat error\n");
exit(EXIT_FAILURE);
}
printf("wait...\n");
//创建信号量并且等待信号量的值变为0
create_sem_and_wait();
printf("The content of the Shared memory is:%s\n",shmptr);
//分离共享存储
if(shmdt(shmptr)==-1){
printf("A shmdt error\n");
exit(EXIT_FAILURE);
}
//消除共享内存
if(shmctl(shmId,IPC_RMID,NULL)==-1){
printf("A shmctl error\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
key_t createKey(char *name,int id)
{
key_t key;
if((key=ftok(name,id))==-1){
printf("A ftok error\n");
exit(EXIT_FAILURE);
}
return key;
}
void create_sem_and_wait()
{
key_t key;
int semId;
key=createKey(SEMFILENAME,SEMID);
//创建信号量集合,集合中只有1个信号量(ID为0)
if((semId=semget(key,1,IPC_CREAT|0777))==-1){
printf("A semget error\n");
exit(EXIT_FAILURE);
}
//设置信号量集合中ID为0的信号量的值为1
union semun arg;
arg.val=1;
if(semctl(semId,0,SETVAL,arg)==-1){
printf("A semctl error\n");
exit(EXIT_FAILURE);
}
//获得当前信号量集合中ID为0的信号量值
/*int semval;
if((semval=semctl(semId,0,GETVAL,NULL))==-1){
printf("A semctl error\n");
exit(EXIT_FAILURE);
}
printf("current semval:%d\n",semval);*/
//等待信号量集合中ID为0的信号量值变为0
struct sembuf buff;
buff.sem_num=0;
buff.sem_op=0;
buff.sem_flg=0;
if(semop(semId,&buff,1)==-1){
printf("A semop error\n");
exit(EXIT_FAILURE);
}
//销毁信号量集合
if(semctl(semId,0,IPC_RMID,NULL)==-1){
printf("A semctl error\n");
exit(EXIT_FAILURE);
}
}
////A_3_shm_B.c
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#define SHM_SIZE 512
#define FLAG 0666
#define SHMFILENAME "./shm.txt"
#define SEMFILENAME "./sem.txt"
#define SHMID 100
#define SEMID 100
key_t createKey(char* filename,int id);
void signal_sem();
int main()
{
int shmId;
char *shmptr;
char buff[]="B writes something to the shared memory";
key_t key;
key=createKey(SHMFILENAME,SHMID);
//引用共享存储
if((shmId=shmget(key,0,FLAG))==-1){
printf("B shmget error\n");
exit(EXIT_FAILURE);
}
//获取共享存储首地址
if((shmptr=shmat(shmId,(void*)0,SHM_RND))==(void*)-1){
printf("B shmat error\n");
exit(EXIT_FAILURE);
}
//向共享存储写入内容
bcopy(buff,shmptr,sizeof(buff));
//分离共享内存
if(shmdt(shmptr)==-1){
printf("B shmdt error\n");
exit(EXIT_FAILURE);
}
signal_sem();
exit(EXIT_SUCCESS);
}
key_t createKey(char* filename,int id)
{
key_t key;
if((key=ftok(filename,id))==-1){
printf("B ftok error\n");
exit(EXIT_FAILURE);
}
return key;
}
void signal_sem()
{
int semId;
key_t key;
key=createKey(SEMFILENAME,SEMID);
//引用信号量集合
if((semId=semget(key,0,0777))==-1){
printf("B semget error\n");
exit(EXIT_FAILURE);
}
//获得当前信号量集合中ID为0的信号量值
/*int semval;
if((semval=semctl(semId,0,GETVAL,NULL))==-1){
printf("A semctl error\n");
exit(EXIT_FAILURE);
}
printf("current semval:%d\n",semval);*/
//将信号量集合中信号量ID为0的信号量值变为0
struct sembuf buff;
buff.sem_num=0;
buff.sem_op=-1;
buff.sem_flg=0;
if(semop(semId,&buff,1)==-1){
printf("B semop error\n");
exit(EXIT_FAILURE);
}
}