1、共享內存介紹
共享內存是三個IPC(Inter-Process Communication)機制中的一個。
它容許兩個不相關的進程訪問同一個邏輯內存。
共享內存是在兩個正在進行的進程之間傳遞數據的一種很是有效的方式。
大多數的共享內存的實現,
都把由不一樣進程之間共享的內存安排爲同一段物理內存。
共享內存是由IPC爲進程建立一個特殊的地址範圍,
它將出如今該進程的地址空間中。
其餘進程能夠將同一段共享內存鏈接它們本身的地址空間中。
全部進程均可以訪問共享內存中的地址,
就好像它們是由malloc分配的同樣。
若是某個進程向共享內存寫入了數據,
所作的改動將馬上被能夠訪問同一段共享內存的任何其餘進程看到。
2、共享內存的同步
共享內存爲在多個進程之間共享和傳遞數據提供了一種有效的方式。
可是它並未提供同步機制,
因此咱們一般須要用其餘的機制來同步對共享內存的訪問。
咱們一般是用共享內存來提供對大塊內存區域的有效訪問,
同時經過傳遞小消息來同步對該內存的訪問。
在第一個進程結束對共享內存的寫操做以前,
並沒有自動的機制能夠阻止第二個進程開始對它進行讀取。
對共享內存訪問的同步控制必須由程序員來負責。
下圖顯示了共享內存是如何共存的:
圖中的箭頭顯示了每一個進程的邏輯地址空間到可用物理內存的映射關係。
3、共享內存使用的函數
- #include <sys/shm.h>
- int shmget(key_t key, size_t size, int shmflg);
- void *shmat(int shm_id, const void *shm_addr, int shmflg);
- int shmdt(const void *shm_addr);
- int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
1. shmget函數
該函數用來建立共享內存:
- int shmget(key_t key, size_t size, int shmflg);
參數:
key : 和信號量同樣,程序須要提供一個參數key,
它有效地爲共享內存段命名。
有一個特殊的鍵值IPC_PRIVATE,
它用於建立一個只屬於建立進程的共享內存,
一般不會用到。
size: 以字節爲單位指定須要共享的內存容量。
shmflag: 包含9個比特的權限標誌,
它們的做用與建立文件時使用的mode標誌是同樣。
由IPC_CREAT定義的一個特殊比特必須和權限標誌按位或
才能建立一個新的共享內存段。
NOTE:
權限標誌對共享內存很是有用,
由於它容許一個進程建立的共享內存能夠被共享內存的建立者所擁有的進程寫入,
同時其它用戶建立的進程只能讀取共享內存。
咱們能夠利用這個功能來提供一種有效的對數據進行只讀訪問的方法,
經過將數據放共享內存並設置它的權限,
就能夠避免數據被其餘用戶修改。
返回值:
建立成功,則返回一個非負整數,即共享內存標識;
若是失敗,則返回-1.
2. shmat函數
第一次建立共享內存段時,它不能被任何進程訪問。
要想啓動對該內存的訪問,
必須將其鏈接到一個進程的地址空間。
這個工做由shmat函數完成:
- void *shmat(int shm_id, const void *shm_addr, int shmflg);
參數:
shm_id : 由shmget返回的共享內存標識。
shm_add: 指定共享內存鏈接到當前進程中的地址位置。
它一般是一個空指針,
表示讓系統來選擇共享內存出現的地址。
shmflg : 是一組標誌。
它的兩個可能取值是:
SHM_RND, 和shm_add聯合使用,
用來控制共享內存鏈接的地址。
SHM_RDONLY, 它使鏈接的內存只讀
返回值:
若是調用成功, 返回一個指向共享內存第一個字節的指針;
若是失敗,返回-1.
共享內存的讀寫權限由它的屬主(共享內存的建立者),
它的訪問權限和當前進程的屬主決定。
共享內存的訪問權限相似於文件的訪問權限。
3. shmdt
將共享內存從當前進程中分離。
- int shmdt(const void *shm_addr);
shm_addr: shmat返回的地址指針。
成功時,返回0,
失敗時,返回-1.
NOTE:
共享內存分離並未刪除它,
只是使得該共享內存對當前進程再也不可用。
4. shmctl
共享內存的控制函數
- int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
shmid_ds結構至少包含如下成員:
- struct shmid_ds {
- uid_t shm_perm.uid;
- uid_t shm_perm.gid;
- mode_t shm_perm.mode;
- }
參數:
shm_id : 是shmget返回的共享內存標識符。
command: 是要採起的動做,
它能夠取3個值:
IPC_STAT 把shmid_ds結構中的數據設置爲共享內存的當前關聯值
IPC_SET 若是進程有足夠的權限,
就把共享內存的當前關聯值設置爲shmid_ds結構中給出的值
IPC_RMID 刪除共享內存段
buf : 是一個指針,
包含共享內存模式和訪問權限的結構。
返回值:
成功時,返回0,
失敗時,返回-1.
4、示例
典型的消費者-生產者程序,
第一個程序(消費者)將建立一個共享內存段,
而後把寫到它裏面的數據都顯示出來。
第二個程序(生產者)將鏈接一個已有的共享內存段,
並容許咱們向其中輸入數據。
shm_com.h
- #define TEXT_SZ 2048
- struct shared_use_st {
- int written_by_you;
- char some_text[TEXT_SZ];
- };
當有數據寫入這個結構中時,
咱們用結構中的written_by_you標誌來通知消費者。
須要傳輸的文本長度2K是隨意定的。
shm1.c 消費者程序
- #include <unistd.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include <sys/shm.h>
- #include "shm_com.h"
- int main()
- {
- int running = 1;
- void *shared_memory = (void *)0;
- struct shared_use_st *shared_stuff;
- int shmid;
- srand((unsigned int)getpid());
- shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
- if (shmid == -1) {
- fprintf(stderr, "shmget failed\n");
- exit(EXIT_FAILURE);
- }
如今,讓程序能夠訪問這個共享內存:
- shared_memory = shmat(shmid, (void *)0, 0);
-
- if (shared_memory == (void *)-1) {
- fprintf(stderr, "shmat failed\n");
- exit(EXIT_FAILURE);
- }
- printf("Memory attached at %X\n", (int)shared_memory);
程序的下一部分將shared_memory分配給shared_stuff,
而後它輸出written_by_you中的文本。
循環將一直執行到在written_by_you中找到end字符串爲止。
sleep調用強迫消費者程序在臨界區域多待一會,
讓生產者程序等待:
- shared_stuff = (struct shared_use_st *)shared_memory;
- shared_stuff->written_by_you = 0;
-
- while(running)
- {
- if (shared_stuff->written_by_you)
- {
- printf("You wrote: %s", shared_stuff->some_text);
-
- sleep( rand() % 4 ); /* make the other process wait for us ! */
- shared_stuff->written_by_you = 0;
- if (strncmp(shared_stuff->some_text, 「end」, 3) == 0) {
- running = 0;
- }
- }
- }
最後,共享內存被分離,而後被刪除:
- if (shmdt(shared_memory) == -1)
- {
- fprintf(stderr, "shmdt failed\n");
- exit(EXIT_FAILURE);
- }
- if (shmctl(shmid, IPC_RMID, 0) == -1)
- {
- fprintf(stderr, "shmctl(IPC_RMID) failed\n");
- exit(EXIT_FAILURE);
- }
- exit(EXIT_SUCCESS);
- }
shm2.c 生產者程序
經過它向消費者程序輸入數據。
- #include <unistd.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include <sys/shm.h>
- #include "shm_com.h"
- int main()
- {
- int running = 1;
- void *shared_memory = (void *)0;
- struct shared_use_st *shared_stuff;
- char buffer[BUFSIZ];
- 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);
- }
-
- shared_memory = shmat(shmid, (void *)0, 0);
- if (shared_memory == (void *)-1)
- {
- fprintf(stderr, "shmat failed\n");
- exit(EXIT_FAILURE);
- }
- printf("Memory attached at %X\n", (int)shared_memory);
- shared_stuff = (struct shared_use_st *)shared_memory;
- while(running)
- {
- while(shared_stuff->written_by_you == 1)
- {
- sleep(1);
- printf("waiting for client...\n");
- }
- printf("Enter some text: ");
- fgets(buffer, BUFSIZ, stdin);
- strncpy(shared_stuff->some_text, buffer, TEXT_SZ);
- shared_stuff->written_by_you = 1;
- if (strncmp(buffer, "end", 3) == 0) {
- running = 0;
- }
- }
- if (shmdt(shared_memory) == -1) {
- fprintf(stderr, "shmdt failed\n");
- exit(EXIT_FAILURE);
- }
- exit(EXIT_SUCCESS);
- }
運行程序,
將看到以下所示的樣本輸出:
- $ ./shm1 &
- [1] 294
- Memory attached at 40017000
- $ ./shm2
- Memory attached at 40017000
- Enter some text: hello
- You wrote: hello
- waiting for client...
- waiting for client...
- Enter some text:
- You wrote:
- waiting for client...
- waiting for client...
- waiting for client...
- Enter some text: end
- You wrote: end
- $
程序解析:
消費者程序
建立共享內存段,
而後將它鏈接到它本身的地址空間中,
而且,
咱們在共享內存的開始處使用了一個結構shared_use_st.
該結構中有個標誌written_by_you,
當共享內存中有數據寫入時,就設置這個標誌。
這個標誌被設置時,
程序就從共享內存中讀取文本,
將它打印出來,
而後清除這個標誌,表示已經讀完數據。
咱們用一個特殊字符串end來退出循環。
接下來,
程序分離共享內存段並刪除它。
生產者程序
使用相同的鍵值1234來取得並鏈接同一個共享內存段,
而後提示用戶輸入一些文本。
若是標誌written_by_you被設置,
生產者就知道消費都進程還未讀完上一次的數據,
所以就繼續等待。
當其它進程清除了這個標誌後,
生產者寫入新的數據並設置這個標誌。
它還使用字符串end來終止並分離共享內存段。
這裏提供的同步標誌written_by_you,
它是一個很是缺少效率的忙等待(不停地循環)。
但在實際編程中,
應該使用信號量,
或經過傳遞消息(使用管道或IPC消息),
或生成信號
的方法來提供讀寫之間的更有效的同步機制。