Linux共享内存
1.什么是共享内存
在前面讲虚拟内存机制时,有讲到Linux的内存映射机制:
初始化虚拟内存区域时,会把虚拟内存和磁盘文件对象对应起来。
由于内存映射机制,一个磁盘文件对象可被多个进程共享访问,也可被多个进程私有访问。
当共享访问时,一个进程的对该对象的修改会显示到其他进程。
当私有访问时,修改时会产生保护故障,内核会拷贝这个私有对象,修改的是这个新对象,其他进程指向的是原来的对象。
所以,共享内存是指不同进程访问同一个逻辑内存。
2.共享内存的使用
Linux提供了一组共享内存API,声明在头文件sys/shm.h中。
1)shmget函数:新建共享内存
int shmget(key_t key,size_t size,int shmflg);
key:共享内存键值,可以理解为共享内存的唯一性标记。
size:共享内存大小
shmflag:创建进程和其他进程的读写权限标识。
返回值:相应的共享内存标识符,失败返回-1
2)shmat函数:连接共享内存到当前进程的地址空间
void *shmat(int shm_id,const void *shm_addr,int shmflg);
shm_id:共享内存标识符
shm_addr:指定共享内存连接到当前进程的地址,通常为0,表示由系统来选择。
shmflg:通常为0
返回值:指向共享内存第一个字节的指针,失败返回-1
3)shmdt函数:当前进程分离共享内存
int shmdt(const void *shmaddr);
4)shmctl函数
和信号量的semctl函数类似,控制共享内存
int shmctl(int shm_id,int command,struct shmid_ds *buf);
shm_id:共享内存标识符
command: 有三个值
IPC_STAT:获取共享内存的状态,把共享内存的shmid_ds结构复制到buf中。
IPC_SET:设置共享内存的状态,把buf复制到共享内存的shmid_ds结构。
IPC_RMID:删除共享内存
buf:共享内存管理结构体。具体结构可参考定义。
3.共享内存需要注意的问题
共享内存没有同步机制,当多个进程同时向共享内存读写数据时,我们需要使用互斥锁,读写锁,信号量,条件变量等来确保数据的一致性。
4.共享内存使用示例
我们编写了两个程序shmread.c,shmwrite.c分别对共享内存读和写。
需要做到可写,然后可读,然后可写,然后可读,循环下去,直到写入的是"end",结束写读进程。
我们使用了前面讲的信号量来处理读写同步的问题。
示例代码如下:
shm_data.h
#pragma once #define TEXT_SZ 2048 struct shared_use_st { char text[TEXT_SZ]; };
shmread.c
#include<stdio.h> #include<stdlib.h> #include<sys/shm.h> #include<sys/sem.h> #include"shmdata.h" union semun { int val; struct semid_ds *buf; unsigned short *arry; }; int sem_id; int set_semvalue() { //用于初始化信号量,在使用信号量前必须这样做 union semun sem_union; sem_union.val = 1; if(semctl(sem_id, 0, SETVAL, sem_union) == -1) return 0; return 1; } void del_semvalue() { //删除信号量 union semun sem_union; if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1) fprintf(stderr, "Failed to delete semaphore\n"); } int semaphore_p() { struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_op = -1;//P() sem_b.sem_flg = SEM_UNDO; if(semop(sem_id, &sem_b, 1) == -1) { fprintf(stderr, "semaphore_p failed\n"); return 0; } return 1; } int semaphore_v() { struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_op = 1;//V() sem_b.sem_flg = SEM_UNDO; if(semop(sem_id, &sem_b, 1) == -1) { fprintf(stderr, "semaphore_v failed\n"); return 0; } return 1; } int main() { void *shm=NULL; struct shared_use_st *shared; int shmid;//共享内存标识符 //创建共享内存 shmid = shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT); if(shmid==-1) { fprintf(stderr,"shmget failed\n"); exit(EXIT_FAILURE); } //将共享内存连接到当前进程的地址空间 shm = shmat(shmid,0,0); if(shm==(void*)-1) { fprintf(stderr,"shmat failed\n"); exit(EXIT_FAILURE); } printf("memory attached at %x\n",(int)shm); //设置共享内存 shared = (struct shared_use_st*)shm; //新建信号量 sem_id = semget((key_t)1234,1,0666|IPC_CREAT); //信号量初始化 if(!set_semvalue()) { fprintf(stderr,"init failed.\n"); exit(EXIT_FAILURE); } while(1) { if(!semaphore_p()) exit(EXIT_FAILURE); fflush(stdout); printf("you wrote:%s",shared->text); if(!semaphore_v()) exit(EXIT_FAILURE); if(strncmp(shared->text,"end",3)==0) break; sleep(1); } //删除信号量 del_semvalue(); //把共享内存从当前进程中分离 if(shmdt(shm)==-1) { fprintf(stderr,"shmdt failed\n"); exit(EXIT_FAILURE); } //删除共享内存 if(shmctl(shmid,IPC_RMID,0)==-1) { fprintf(stderr,"shmctl(IPC_RMID) failed"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); }
shmwrite.c
#include<stdio.h> #include<stdlib.h> #include<sys/shm.h> #include<sys/sem.h> #include<string.h> #include"shmdata.h" int sem_id; int semaphore_p() { //对信号量做减1操作,即等待P(sv) struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_op = -1;//P() sem_b.sem_flg = SEM_UNDO; if(semop(sem_id, &sem_b, 1) == -1) { fprintf(stderr, "semaphore_p failed\n"); return 0; } return 1; } int semaphore_v() { //这是一个释放操作,它使信号量变为可用,即发送信号V(sv) struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_op = 1;//V() sem_b.sem_flg = SEM_UNDO; if(semop(sem_id, &sem_b, 1) == -1) { fprintf(stderr, "semaphore_v failed\n"); return 0; } return 1; } int main() { sem_id = semget((key_t)1234,1,0666|IPC_CREAT); void *shm = NULL; struct shared_use_st *shared = NULL; char buffer[200]; int shmid; //创建共享内存 shmid = shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT); if(shm==-1) { fprintf(stderr,"shmget failed\n"); exit(EXIT_FAILURE); } //将共享内存连接到当前进程的地址空间 shm = shmat(shmid,(void*)0,0); if(shm == (void*)-1) { fprintf(stderr,"shmat failed\n"); exit(EXIT_FAILURE); } printf("memory attached at %x\n",(int)shm); //设置共享内存 shared = (struct shared_use_st*)shm; while(1) { if(!semaphore_p()) exit(EXIT_FAILURE); //向共享内存中写入数据 fflush(stdout); printf("Enter some text...\n"); fgets(buffer,200,stdin); strncpy(shared->text,buffer,TEXT_SZ); if(!semaphore_v()) exit(EXIT_FAILURE); if(strncmp(shared->text,"end",3)==0) break; sleep(1); } //把共享内存从当前进程中分离 if(shmdt(shm)==-1) { fprintf(stderr,"shmdt failed\n"); exit(EXIT_FAILURE); } sleep(2); exit(EXIT_SUCCESS); }
输出结果:
5.共享内存的优缺点
1)进程间通信方便,快速。
2)没有提供同步机制,我们需要使用互斥锁,读写锁,信号量,条件变量等来确保数据的一致性。