linux信号量配合共享内存应用分析(详解)
共享內存與信號量
共享內存,指的是兩個不相關的進程訪問同一個邏輯內存,進程可以將同一段物理內存連接到他們自己的地址空間中,所有的進程都可以訪問共享內存中的地址。如果某個進程向共享內存寫入數據,所做的改動將立即影響到可以訪問同一段共享內存的任何其他進程。如果我們不允許兩個進程同時對共享內存進行讀寫操作,光靠共享內存的機制是做不到的。共享內存并未提供同步機制,也就是說,在第一個進程結束對共享內存的寫操作之前,并無自動機制可以阻止第二個進程開始對它進行讀取,所以我們通常需要用其他的機制來同步對共享內存的訪問,例如信號量。
共享內存的操作函數說明
1、創建共享內存(創建或者打開一塊內存后,會返回該內存的索引,進程通過該索引可以對共享內存進行連接)(記錄一個知識點:fortk第一個參數是輸入一個路徑,如果路勁不存在會返回一個默認值)
int shmget(key_t key, size_t size, int shmflg)創建共享內存/* key:由ftok生成的key標識,標識系統的唯一IPC資源。size:需要申請共享內存的大小。在操作系統中,申請內存的最小單位為頁,一頁是4k字節,為了避免內存碎片,我們一般申請的內存大小為頁的整數倍。shmflg:如果要創建新的共享內存,需要使用IPC_CREAT,IPC_EXCL,如果是已經存在的,可以使用IPC_CREAT或直接傳0。*/2、連接共享內存(不同的進程通過此函數,掛載到同一片內存上)
void *shmat(int shmid, const void *shmaddr, int shmflg);/*shmid:共享存儲段的標識符。shmaddr:shmaddr = 0,則存儲段連接到由內核選擇的第一個可以地址上(推薦使用)。shmflg:若指定了SHM_RDONLY位,則以只讀方式連接此段,否則以讀寫方式連接此段。返回值:成功返回共享存儲段的指針(虛擬地址),并且內核將使其與該共享存儲段相關的shmid_ds結構中的shm_nattch計數器加1(類似于引用計數);出錯返回-1。*/3、解除共享內存的連接(當一個進程不需要共享內存的時候,就需要解除與該共享內存的連接。該函數并不刪除所指定的共享內存區,而是將之前用shmat函數連接好的共享內存區脫離目前的進程。)
int shmdt(const void *shmaddr)/* shmaddr:連接以后返回的地址。返回值:成功返回0,并將shmid_ds結構體中的 shm_nattch計數器減1;出錯返回-1。*/4、銷毀共享內存
int shmctl(int shmid, int cmd, struct shmid_ds *buf); /* shmid:共享存儲段標識符。參數cmd:指定的執行操作,設置為IPC_RMID時表示可以刪除共享內存。參數buf:設置為NULL即可。返回值:成功返回0,失敗返回-1。 */信號量的操作函數說明
說信號量之前,先提一句,信號量和信號是完全兩個不同的東西,關于信號可以去看我這篇博客 ,點擊進入
信號量就是用來解決進程間的同步與互斥問題的一種進程間通信機制。值得一提的是,二值信號量與自旋鎖的效果差不多,但有一點需要注意是,信號量會引起進程的睡眠屬于睡眠鎖,不要在中斷中用信號量,不要在持鎖時,進行進程的切換!
1、信號量的建立(會返回一個信號標識符,用來指向這個信號量結構)
int semget(key_t key, int nsems, int semflg)/*key : 定義一個唯一的標識符nsems: 需要創建的信號量數量semflg : IPC_CREAT(一般用這個):當semflg&IPC_CREAT為真時,如果內核中不存在鍵值與key相等的信號量集,則新建一個信號量集;如果存在這樣的信號量集,返回此信號量集的標識符 。IPC_CREAT|IPC_EXCL:如果內核中不存在鍵值與key相等的信號量集,則新建一個消息隊列;如果存在這樣的信號量集則報錯*/2、對信號量進行設置
int semctl(int semid, int semnum, int cmd, union semun arg)/*semid: 就是由semget()得到的semnum: 當用到信號量集的時候才用,默認為0就好了cmd:你就用SETVAL這個好了,用來把信號量初始化為一個已知的值。其他的我也沒用過哈哈...arg: union semun {short val; /*就把這個val值設置一下就好了,其他的不用管,默認就好*/struct semid_ds* buf; /*IPC_STAT、IPC_SET用的semid_ds結構*/unsigned short* array; /*SETALL、GETALL用的數組值*/struct seminfo *buf; /*為控制IPC_INFO提供的緩存*/} arg;*/3、信號量的P操作或V操作(可以簡單的理解為,信號量的獲取與釋放,主要看各個val值,如果你把val設置為1 ,也就是資源只支持一個進程訪問,該進程訪問時進程P操作,會把val減1,當另一進程請求訪問時,發現val已經等于0了,就會被堵塞)
int semop(int semid, struct sembuf *sops, unsigned nsops)/*semid:信號量集標識符struct sembuf * sops : struct sembuf {short semnum; /*信號量集合中的信號量編號,0代表第1個信號量*/short val;/*若val>0進行V操作信號量值加val,表示進程釋放控制的資源 *//*若val<0進行P操作信號量值減val,則調用進程阻塞,直到資源可用;若設置IPC_NOWAIT不會睡眠,進 程直接返回EAGAIN錯誤*//*若val==0時阻塞等待信號量為0,調用進程進入睡眠狀態,直到信號值為0;若設置IPC_NOWAIT,進程不會 睡眠,直接返回EAGAIN錯誤*/short flag; /*0 設置信號量的默認操作*//*IPC_NOWAIT設置信號量操作不等待*/ /*SEM_UNDO 選項會讓內核記錄一個與調用進程相關的UNDO記錄,如果該進程崩潰,則根據這個進程的UNDO記錄自動恢復相應信號量的計數值*//*nsops:進行操作信號量的個數,即sops結構變量的個數,需大于或等于1。最常見設置此值等于1,只完成對一個信號量的操作*/*/信號量配合共享內存應用
comm.h文件
//comm.h #ifndef _COMM_H__ #define _COMM_H__#include<stdio.h> #include<sys/types.h> #include<sys/ipc.h> #include<sys/shm.h> #include<sys/sem.h>#define PATHNAME "." #define PROJ_ID 0x6666 #define PROJ_mm 0x6667int CreateShm(int size); int DestroyShm(int shmid); int GetShm(int size);int init_sem(int semid, int num, int val);int sem_p(int semid, int num) ;int sem_p(int semid, int num) ;int sem_v(int semid, int num) ;#endifcomm.c
//comm.c/*void *shmat(int shmid, const void *shmaddr, int shmflg);參數shmid:共享存儲段的標識符。參數*shmaddr:shmaddr = 0,則存儲段連接到由內核選擇的第一個可以地址上(推薦使用)。參數shmflg:若指定了SHM_RDONLY位,則以只讀方式連接此段,否則以讀寫方式連接此段。返回值:成功返回共享存儲段的指針(虛擬地址),并且內核將使其與該共享存儲段相關的shmid_ds結構中的shm_nattch計數器加1(類似于引用計數);出錯返回-1。*/ #include"comm.h"//以下定義的是共享內存的函數 static int CommShm(int size,int flags) {key_t key = ftok(PATHNAME,PROJ_ID); //由ftok生成的key標識,標識系統的唯一IPC資源。“PATHNAME”就是引用一個絕對路勁if(key < 0){perror("ftok");return -1;}int shmid = 0;/*int shmget(key_t key, size_t size, int shmflg)創建共享內存參數key:由ftok生成的key標識,標識系統的唯一IPC資源。參數size:需要申請共享內存的大小。在操作系統中,申請內存的最小單位為頁,一頁是4k字節,為了避免內存碎片,我們一般申請的內存大小為頁的整數倍。參數shmflg:如果要創建新的共享內存,需要使用IPC_CREAT,IPC_EXCL,如果是已經存在的,可以使用IPC_CREAT或直接傳0。*/if((shmid = shmget(key,size,flags)) < 0){perror("shmget");return -2;}return shmid; } int DestroyShm(int shmid) {if(shmctl(shmid,IPC_RMID,NULL) < 0){perror("shmctl");return -1;}return 0; } int CreateShm(int size) {return CommShm(size,IPC_CREAT | IPC_EXCL | 0666); } int GetShm(int size) {return CommShm(size,IPC_CREAT); }//以下定義的是信號量的函數union semun {int val; /* Value for SETVAL */struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */unsigned short *array; /* Array for GETALL, SETALL */struct seminfo *__buf; /* Buffer for IPC_INFO(Linux-specific) */ };int init_sem(int semid, int num, int val)// num = 0 val = 1 {union semun myun;myun.val = val;if(semctl(semid, num, SETVAL, myun) < 0) /*用來直接控制信號量信息:對信號量進行刪除或者控制// 0代表對1個信號來量初始化,即有1個資源 SETVAL:用來把信號量初始化為一個已知的值。p 這個值通過union semun中的val成員設置,其作用是在信號量第一次使用前對它進行設置。IPC_RMID:用于刪除一個已經無需繼續使用的信號量標識符。*/{perror("semctl");exit(1);}return 0; }int sem_p(int semid, int num) {struct sembuf mybuf;// 對信號量做減1操作,即等待P(sv)mybuf.sem_num = num;mybuf.sem_op = -1;mybuf.sem_flg = SEM_UNDO;if(semop(semid, &mybuf, 1) < 0){perror("semop");exit(1);}return 0; }int sem_v(int semid, int num) {struct sembuf mybuf; 這是一個釋放操作,它使信號量變為可用,即發送信號V(sv)mybuf.sem_num = num;mybuf.sem_op = 1;mybuf.sem_flg = SEM_UNDO;if(semop(semid, &mybuf, 1) < 0){perror("semop");exit(1);}return 0; }client.c
//client.c #include"comm.h"int main() {int semid ;key_t sem_key;if((sem_key = ftok(PATHNAME,PROJ_mm)) < 0){perror("ftok failed .\n");exit(-1);}semid = semget(sem_key, 0, IPC_CREAT); //創建一個新信號量或取得一個已有信號量,權限為666int shmid = GetShm(4096);sleep(1);char *addr = shmat(shmid,NULL,0);sleep(2);int i = 0;sem_p(semid,0);//獲取信號量while(i < 26){addr[i] = 'A' + i;i++;addr[i] = 0;sleep(1);}sem_v(semid,0);//釋放信號量shmdt(addr);semctl (semid, 1, IPC_RMID, NULL);DestroyShm(shmid);sleep(2);return 0; }server.c
//server.c #include"comm.h"int main() {int semid ;key_t sem_key;if((sem_key = ftok(PATHNAME,PROJ_mm)) < 0){perror("ftok failed .\n");exit(-1);}semid = semget(sem_key,1,IPC_CREAT|IPC_EXCL|0666); //創建一個新信號量或取得一個已有信號量,權限為666init_sem (semid, 0, 1); //初始化這個信號量int shmid = CreateShm(4096);sleep(1);char *addr = shmat(shmid,NULL,0);sleep(2);int i = 0;getchar(); //暫停在這,讓客服端先拿到信號量,當client運行后,按下回車鍵,讓server程序繼續運行sem_p(semid,0);//獲取信號量while(i++ < 26){printf("client# %s\n",addr);sleep(1);}sem_v(semid,0);//釋放信號量shmdt(addr);sleep(2);return 0; }Makefile
all:server clientclient:client.c comm.cgcc -o $@ $^ server:server.c comm.cgcc -o $@ $^ clean:rm -f client server不加信號量的實驗結果如下
加上信號量的實驗結果如下
總結 ,從這個兩個結果可以看出,不加信號量的時候,讀取數據和寫入數據是宏觀是同時進行的,會存在讀取到的數據不完整的現象,而加上信號量后,讀取數據進程,會等待數據全部寫完后,在進行讀取。保證了讀到的數據的完整性和讀寫進程的同步性
總結
以上是生活随笔為你收集整理的linux信号量配合共享内存应用分析(详解)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ANSYS之翼型NACA4412流场计算
- 下一篇: Mysql8.0以上重置初始密码的方法