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)沒有提供同步機制,我們需要使用互斥鎖,讀寫鎖,信號量,條件變量等來確保數據的一致性。
