进程间通信:共享内存概念及代码
前言
接下討論的IPC機(jī)制,它們最初由System V版本的Unix引入。由于這些機(jī)制都出現(xiàn)在同一個(gè)版本中并且有著相似的編程接口,所以它們被稱為System V IPC機(jī)制。接下來的內(nèi)容包括:
信號量:用于管理對資源的訪問。
共享內(nèi)存:用于在程序之間高效地共享數(shù)據(jù)。
消息隊(duì)列:在程序之間傳遞數(shù)據(jù)。
操作系統(tǒng)中的同步和異步:https://blog.csdn.net/qq_38289815/article/details/81012826
進(jìn)程間通信:管道和命名管道(FIFO) ?https://blog.csdn.net/qq_38289815/article/details/104742682
進(jìn)程間通信:信號量 ?https://blog.csdn.net/qq_38289815/article/details/104762940
進(jìn)程間通信:消息隊(duì)列 ?https://blog.csdn.net/qq_38289815/article/details/104786412
?
共享內(nèi)存
共享內(nèi)存允許兩個(gè)不相關(guān)的進(jìn)程訪問同一個(gè)邏輯內(nèi)存。共享內(nèi)存是在兩個(gè)正在運(yùn)行的進(jìn)程之間傳遞數(shù)據(jù)的一種非常有效的方式。不同進(jìn)程之間共享的內(nèi)存安排為同一段物理內(nèi)存。
共享內(nèi)存是由IPC為進(jìn)程創(chuàng)建的一個(gè)特殊的地址范圍,它將出現(xiàn)在該進(jìn)程的地址空間中。其他進(jìn)程可以將同一段共享內(nèi)存連接到它們自己的地址空間中,所有進(jìn)程都可以訪問共享內(nèi)存中的地址,就好像它們是由用C語言函數(shù)malloc()分配的內(nèi)存一樣。如果某個(gè)進(jìn)程向共享內(nèi)存寫入數(shù)據(jù),所做的改動(dòng)將立即影響到可以訪問同一段共享內(nèi)存的任何其他進(jìn)程。
特別提醒:共享內(nèi)存并未提供同步機(jī)制,也就是說,在第一個(gè)進(jìn)程結(jié)束對共享內(nèi)存的寫操作之前,并無自動(dòng)機(jī)制可以阻止第二個(gè)進(jìn)程開始對它進(jìn)行讀取。所以我們通常需要用其他的機(jī)制來同步對共享內(nèi)存的訪問,例如信號量。
?
Linux數(shù)字權(quán)限含義解釋
在學(xué)習(xí)Linux編程時(shí),常會用到755、777這些數(shù)字設(shè)置權(quán)限。下面我來詳細(xì)的介紹644、755、777這些數(shù)字所代表的含義。Linux系統(tǒng)使用9個(gè)比特的權(quán)限標(biāo)志來表示文件的權(quán)限。在終端中鍵入ll或ls -l查看目錄下的文件:
圖片中所展示的數(shù)字,從左至右,1-3位數(shù)字代表文件所有者的權(quán)限,4-6位數(shù)字代表同組用戶的權(quán)限,7-9數(shù)字代表其他用戶的權(quán)限。具體的權(quán)限是由數(shù)字來表示的,讀取(r)的權(quán)限等于4;寫入(w)的權(quán)限等于2;執(zhí)行(x)的權(quán)限等于1。
通過4、2、1的組合,得到以下幾種權(quán)限:0 (沒有權(quán)限);4 (讀取權(quán)限);5 (4+1 | 讀取+執(zhí)行);6 (4+2 | 讀取+寫入);7 (4+2+1 | 讀取+寫入+執(zhí)行)。以755為例:
1-3位7等于4+2+1,rwx,所有者具有讀取、寫入、執(zhí)行權(quán)限;
4-6位5等于4+1+0,r-x,同組用戶具有讀取、執(zhí)行權(quán)限但沒有寫入權(quán)限;
7-9位5,同上,也是r-x,其他用戶具有讀取、執(zhí)行權(quán)限但沒有寫入權(quán)限。
?
共享內(nèi)存的使用
與信號量一樣,在Linux中也提供了一組函數(shù)接口用于使用共享內(nèi)存,而且使用共享共存的接口還與信號量的非常相似,而且比使用信號量的接口來得簡單。定義如下:
#include <sys/shm.h>void *shmat(int shm_id, const void *shm_addr, int shmflg); int shmctl(int shm_id, int cmd, struct shmid_ds *buf); int shmdt(const void *shm_addr); int shmget(key_t key, size_t size, int shmflg);?
shmget()
shmget函數(shù)用來創(chuàng)建共享內(nèi)存,它的原型為:
int?shmget(key_t key, size_t size, int shmflg); 創(chuàng)建成功返回一個(gè)非負(fù)整數(shù),即共享內(nèi)存標(biāo)識符;失敗返回-1。第一個(gè)參數(shù),與信號量的semget函數(shù)一樣,程序需要提供一個(gè)參數(shù)key(非0整數(shù)),它有效地為共享內(nèi)存段命名,shmget函數(shù)成功時(shí)返回一個(gè)與key相關(guān)的共享內(nèi)存標(biāo)識符(非負(fù)整數(shù)),用于后續(xù)的共享內(nèi)存函數(shù)。調(diào)用失敗返回-1。
第二個(gè)參數(shù),size以字節(jié)為單位指定需要共享的內(nèi)存容量。
第三個(gè)參數(shù),shmflg是權(quán)限標(biāo)志,如果要想在key標(biāo)識的共享內(nèi)存不存在時(shí)創(chuàng)建它的話,可以與IPC_CREAT做按位或操作。共享內(nèi)存的權(quán)限標(biāo)志與文件的讀寫權(quán)限一樣。
不相關(guān)的進(jìn)程可以通過該函數(shù)的返回值訪問同一共享內(nèi)存,它代表程序可能要使用的某個(gè)資源,程序?qū)λ泄蚕韮?nèi)存的訪問都是間接的,程序先通過調(diào)用shmget函數(shù)并提供一個(gè)鍵,再由系統(tǒng)生成一個(gè)相應(yīng)的共享內(nèi)存標(biāo)識符(shmget函數(shù)的返回值),只有shmget函數(shù)才直接使用信號量鍵,所有其他的信號量函數(shù)使用由semget函數(shù)返回的信號量標(biāo)識符。
權(quán)限標(biāo)志對共享內(nèi)存非常有用,因?yàn)樗鼈冊试S一個(gè)進(jìn)程創(chuàng)建的共享內(nèi)存可以被共享內(nèi)存的創(chuàng)建者所擁有的進(jìn)程寫入,同時(shí)其他用戶創(chuàng)建的進(jìn)程只能讀取該共享內(nèi)存。我們可以利用這個(gè)功能來提供一種有效的對數(shù)據(jù)進(jìn)行只讀訪問的方法,通過將數(shù)據(jù)放入共享內(nèi)存并設(shè)置它的權(quán)限,就可以避免其他用戶修改它。
?
shmat()
第一次創(chuàng)建完共享內(nèi)存時(shí),它還不能被任何進(jìn)程訪問,shmat()函數(shù)的作用就是啟動(dòng)對該共享內(nèi)存的訪問,并把共享內(nèi)存連接到進(jìn)程的地址空間。它的原型為:
void *shmat(int shm_id, const void *shm_addr, int shmflg); shmat調(diào)用成功時(shí)返回一個(gè)指向共享內(nèi)存第一個(gè)字節(jié)的指針;調(diào)用失敗時(shí)返回-1。第一個(gè)參數(shù),shm_id是由shmget()函數(shù)返回的共享內(nèi)存標(biāo)識。
第二個(gè)參數(shù),shm_addr指定共享內(nèi)存連接到當(dāng)前進(jìn)程中的地址位置,通常是一個(gè)空指針,表示讓系統(tǒng)來選擇共享內(nèi)存出現(xiàn)的地址。
第三個(gè)參數(shù),shm_flg是一組標(biāo)志位,通常為0。一般很少需要控制共享內(nèi)存連接的地址,通常都是讓系統(tǒng)來選擇一個(gè)地址,否則就會使應(yīng)用程序?qū)τ布囊蕾囆赃^高。
共享內(nèi)存的讀寫權(quán)限由它的屬主(共享內(nèi)存的創(chuàng)建者)、它的訪問權(quán)限和當(dāng)前進(jìn)程的屬主決定。共享內(nèi)存的訪問權(quán)限類似于文件的訪問權(quán)限。這個(gè)規(guī)則有個(gè)例外,當(dāng)shmflg & SHM_RDONLY(它使得連接的內(nèi)存只讀)為true時(shí),此時(shí)即使該共享內(nèi)存的訪問權(quán)限允許寫操作,它也不能被寫入。
?
shmdt()
shmdt函數(shù)用于將共享內(nèi)存從當(dāng)前進(jìn)程中分離。注意,將共享內(nèi)存分離并不是刪除它,只是使該共享內(nèi)存對當(dāng)前進(jìn)程不再可用。它的原型為:
int?shmdt(const void *shmaddr); 參數(shù)shmaddr是shmat()函數(shù)返回的地址指針,調(diào)用成功時(shí)返回0,失敗時(shí)返回-1.?
shmctl()
shmctl用來控制共享內(nèi)存,它的原型為:
int?shmctl(int shm_id, int command, struct shmid_ds *buf); 成功時(shí)返回0,失敗時(shí)返回-1。第一個(gè)參數(shù),shm_id是shmget()函數(shù)返回的共享內(nèi)存標(biāo)識符。
第二個(gè)參數(shù),command是要采取的操作,它可以取下面的三個(gè)值 :
? ? ? ? IPC_STAT:把shmid_ds結(jié)構(gòu)中的數(shù)據(jù)設(shè)置為共享內(nèi)存的當(dāng)前關(guān)聯(lián)值,即用共享內(nèi)存的當(dāng)前關(guān)聯(lián)值覆蓋shmid_ds的值。
? ? ? ? IPC_SET:如果進(jìn)程有足夠的權(quán)限,就把共享內(nèi)存的當(dāng)前關(guān)聯(lián)值設(shè)置為shmid_ds結(jié)構(gòu)中給出的值
? ? ? ? IPC_RMID:刪除共享內(nèi)存段
第三個(gè)參數(shù),buf是一個(gè)結(jié)構(gòu)指針,它指向共享內(nèi)存模式和訪問權(quán)限的結(jié)構(gòu)。shmid_ds結(jié)構(gòu) 至少包括以下成員:
struct?shmid_ds {uid_t shm_perm.uid;uid_t shm_perm.gid;mode_t shm_perm.mode; };?
使用共享內(nèi)存完成進(jìn)程間通信
下面就以兩個(gè)不相關(guān)的進(jìn)程來說明進(jìn)程間如何利用共享內(nèi)存來進(jìn)行通信。其中一個(gè)文件shm1.c(消費(fèi)者)創(chuàng)建共享內(nèi)存,并讀取其中的信息,另一個(gè)文件shm2.c(生產(chǎn)者)將連接一個(gè)已有的共享內(nèi)存段,向共享內(nèi)存中寫入數(shù)據(jù)。
#ifndef _SHM_COM_H_HEADER //shm_com.h #define _SHM_COM_H_HEADER#define TEXT_SZ 2048struct shared_use_st {int written_by_you; // 作為一個(gè)標(biāo)志,非0:表示可讀,0:表示可寫char some_text[TEXT_SZ]; // 記錄寫入 和 讀取 的文本 };#endif #include <stddef.h> //shm1.c #include <sys/shm.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include "shm_com.h"int main(int argc, char **argv) {int running = 1;void *shared_memory = NULL;struct shared_use_st *shared_stuff; // 指向shmint shmid; // 共享內(nèi)存標(biāo)識符srand((unsigned int)getpid());// 創(chuàng)建共享內(nèi)存shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);if (shmid == -1){fprintf(stderr, "shmget failed\n");exit(EXIT_FAILURE);}// 將共享內(nèi)存連接到當(dāng)前進(jìn)程的地址空間shared_memory = shmat(shmid, (void *)0, 0);if (shared_memory == (void *)-1){fprintf(stderr, "shmat failed\n");exit(EXIT_FAILURE);}printf("\nMemory attached at %p\n", shared_memory);printf("Memory attched at %d\n", *(int*)shared_memory);// 設(shè)置共享內(nèi)存shared_stuff = (struct shared_use_st*)shared_memory; // 注意:shm有點(diǎn)類似通過 malloc() 獲取到的內(nèi)存,所以這里需要做個(gè) 類型強(qiáng)制轉(zhuǎn)換shared_stuff->written_by_you = 0;while (running) // 讀取共享內(nèi)存中的數(shù)據(jù){// 沒有進(jìn)程向內(nèi)存寫數(shù)據(jù),有數(shù)據(jù)可讀取if (shared_stuff->written_by_you == 1){printf("You wrote: %s", shared_stuff->some_text);sleep(1);// 讀取完數(shù)據(jù),設(shè)置written使共享內(nèi)存段可寫shared_stuff->written_by_you = 0;// 輸入了 end,退出循環(huán)(程序)if (strncmp(shared_stuff->some_text, "end", 3) == 0){running = 0;}}}// 把共享內(nèi)存從當(dāng)前進(jìn)程中分離if (shmdt(shared_memory) == -1){fprintf(stderr, "shmdt failed\n");exit(EXIT_FAILURE);}// 刪除共享內(nèi)存if (shmctl(shmid, IPC_RMID, 0) == -1){fprintf(stderr, "shmctl(IPC_RMID) failed\n");exit(EXIT_FAILURE);}exit(EXIT_SUCCESS);reutrn 0; } #include <unistd.h> //shm2.c #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/shm.h> #include "shm_com.h"int main(int argc, char **argv) {int running = 1;void *shared_memory = NULL;struct shared_use_st *shared_stuff; // 指向shmchar buffer[BUFSIZ];int shmid; // 共享內(nèi)存標(biāo)識符// 創(chuàng)建共享內(nèi)存shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);if (shmid == -1){fprintf(stderr, "shmget failed\n");exit(EXIT_FAILURE);}// 將共享內(nèi)存連接到當(dāng)前的進(jìn)程地址空間shared_memory = shmat(shmid, (void *)0, 0);if (shared_memory == (void *)-1){fprintf(stderr, "shmat failed\n");exit(EXIT_FAILURE);}printf("Memory attched at %p\n", shared_memory);printf("Memory attched at %d\n", *(int*)shared_memory);// 設(shè)置共享內(nèi)存shared_stuff = (struct shared_use_st *)shared_memory;while (running) // 向共享內(nèi)存中寫數(shù)據(jù){// 數(shù)據(jù)還沒有被讀取,則等待數(shù)據(jù)被讀取,不能向共享內(nèi)存中寫入文本while (shared_stuff->written_by_you == 1){sleep(1);printf("Waiting...\n");}// 向共享內(nèi)存中寫入數(shù)據(jù)printf("Enter some text: ");fgets(buffer, BUFSIZ, stdin);strncpy(shared_stuff->some_text, buffer, TEXT_SZ);// 寫完數(shù)據(jù),設(shè)置written使共享內(nèi)存段可讀shared_stuff->written_by_you = 1;// 輸入了end,退出循環(huán)(程序)if (strncmp(buffer, "end", 3) == 0){running = 0;}}// 把共享內(nèi)存從當(dāng)前進(jìn)程中分離if (shmdt(shared_memory) == -1){fprintf(stderr, "shmdt failed\n");exit(EXIT_FAILURE);}exit(EXIT_SUCCESS);return 0; }實(shí)驗(yàn)解析
第一個(gè)程序shm1創(chuàng)建共享內(nèi)存段,然后它連接到自己的地址空間中。我們在共享內(nèi)存的開始處使用一個(gè)結(jié)構(gòu)體shared_use_st。該結(jié)構(gòu)體中有個(gè)標(biāo)志written_by_you,當(dāng)共享內(nèi)存中有數(shù)據(jù)寫入時(shí),就設(shè)置這個(gè)標(biāo)志。這個(gè)標(biāo)志被設(shè)置時(shí),程序就從共享內(nèi)存中讀取文本,將它打印出來,然后清除這個(gè)標(biāo)志表示已經(jīng)讀完數(shù)據(jù)。我們用一個(gè)特殊字符串end來退出循環(huán)。接下來,程序分離共享內(nèi)存段并刪除它。
第二個(gè)程序shm2使用相同的鍵1234來取得并連接同一個(gè)共享內(nèi)存段。然后它提示用戶輸入一些文本。如果標(biāo)志written_by_you被設(shè)置,shm2就知道客戶進(jìn)程還未讀完上一次的數(shù)據(jù),因此就繼續(xù)等待。當(dāng)其他進(jìn)程清除了這個(gè)標(biāo)志后,shm2寫入數(shù)據(jù)并設(shè)置該標(biāo)志。它還使用字符串end來終止并分離共享內(nèi)存段。
注意,這里提供了非常簡陋的同步標(biāo)志written_by_you,它包括一個(gè)非常缺乏效率的忙等待(不停地循環(huán))。這可以使得我們的示例比較簡單,但在實(shí)際編程中,應(yīng)該使用信號量或通過傳遞消息、生成信號的方式來提供應(yīng)用程序讀、寫部分之間的一種更有效的同步機(jī)制。
總結(jié)
以上是生活随笔為你收集整理的进程间通信:共享内存概念及代码的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 进程间通信:管道和命名管道(FIFO)
- 下一篇: 进程间通信:消息队列概念及代码