System V Shared Memory
在撰寫 process 時,有時會希望一些數值或是資料結構可以在不同 process 之間傳遞或是修改。但是資料結構儲存於虛擬記憶體中,不同 process 之間的記憶體區塊都是各自獨立的,無法存取到其他 process 的記憶體位置。如果要實現不同 process 之間存取相同一塊記憶體這樣的功能,就必須使用共享記憶體 (shared memory) 了。
Linux System Call
Linux Kernel 提供以下幾個關於 Shared Memory 的 system call
int shmget(key_t key, size_t size, int shmflg); void *shmat(int shmid, const void *shmaddr,int shmflg); int shmdt(const void *shmaddr); int shmctl(int shmid, int cmd, struct shmid_ds *buf);
int shmget(key_t key, size_t size, int shmflg) – 取得/建立 Shared Memory
key: shared memory 的鍵值,可以為任意整數,用以區別不同 shared memory.
size: 欲配置記憶體的大小。
shmflg: 選項旗標。通常帶入權限號碼加上 IPC_CREAT 旗標用以建立用以建立新的 shared memory。若記憶體已經被配置過,也可以順利取得 shared memory ID,並不會發生錯誤。若要過濾此情況可以搭配 IPC_EXCL 旗標服用,在此情況下,如果該鍵值已經配置了記憶體,則會返回錯誤。
return: 執行成功返回 shared memory ID 的正整數。錯誤返回 -1。
void *shmat(int shmid, const void *shmaddr, int shmflg) – attach Shared Memory. 將共享記憶體 mapping 到指標。
shmid: shared memory ID。就是 shmget() 回傳的數值。
*shmaddr: 可藉由此參數來指定記憶體 mapping 的位址。通常直接帶入 (void *)0 由系統決定位址。
shmflg: 旗標。一般情況帶入 0。若希望 process 不要修改此共享記憶體的內容可以帶入 SHM_RDONLY 旗標。
return: 執行成功返回一個指向共享記憶體的指標。失敗返回 -1 。
int shmdt(const void *shmaddr) – detach Shared Memory。將記憶體脫離此 process (但記憶體內容還存在於系統中)。
*shmaddr: 要脫離的共享記憶體位址。也就是 shmat() 返回的指標。
return: 成功返回 0 。失敗返回 -1 。
int shmctl(int shmid, int cmd, struct shmid_ds *buf) – 控制 Shared Memory
shmid: shared memory ID。
cmd: 命令。最常使用 IPC_RMID 來終止此共享記憶體,但真正的共享記憶體被終止必須等到所有行程都脫離(detach)之後。執行 IPC_RMID 第三個參數 buf 帶入 NULL 即可。另外可以使用 IPC_STAT 取得該 shared memory 的資訊; IPC_SET 修改 shared memory 的權限與擁有者;IPC_INFO 取得整個系統的 shared memory 資訊等等。
*buf: 傳入指向 struct shmid_ds 結構的指標,但依照不同的 command 會需要帶入不同的參數。
return: 對於不同的命令執行成功的回傳值可能會有異,必須參考 manual。但可以肯定的是執行失敗一定是回傳 -1 。
對於共享記憶體的 attach/detach 有幾個情況需要留意:
- 行程執行 fork() 之後,子行程可以繼承父行程的共享記憶體。
- 行程執行 execve() 之後,所有的共享記憶體將會自動脫離。
- 行程執行 _exit() 之後,所有的共享記憶體將會自動脫離。
實作
如同之前敘述的 System V semaphore,若要使用 System V shared memory 核心也必須要支援,一般 x86 PC Linux kernel 預設都會支援。但若要執行在嵌入式系統上,請注意核心的 config file 有沒有選上 CONFIG_SYSVIPC 選項。若使用 menuconfig 設定,此選項會出現於 General setup -> System V IPC。可以檢驗檔案系統上是否有 /proc/sysvipc/ 路徑存在,判斷系統是否支援 System V IPC。
假設有兩個 process 分別為 proc1 與 proc2。proc1 會對 shared memory 的結構內容做寫值的動作,proc2 則會讀取 shared memory 的內容並且輸出到終端機。proc2 可以修改 shared memory 其中一個結構欄位,作為通知 proc1 移除這個 shared memory。
#include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; #include &lt;unistd.h&gt; #include &lt;error.h&gt; #include <sys/shm.h> #include "shmem.h" int main(int argc, char *argv[]) { int shm_id; struct shm_ex *ex; /* get the ID of shared memory */ shm_id = shmget((key_t)KEY_SHM_EXAMPLE, sizeof(struct shm_ex), 0666 | IPC_CREAT); if (shm_id == -1) { perror("shmget"); exit(EXIT_FAILURE); } /* attach shared memory */ ex = (struct shm_ex *)shmat(shm_id, (void *)0, 0); if (ex == (void *)-1) { perror("shmget"); exit(EXIT_FAILURE); } /* initial value */ ex->flag = 1; ex->data[0] = ex->data[1] = ex->data[2] = ex->data[3] = START_VALUE; while (ex->flag) { ex->data[0] += 2; ex->data[1] -= 2; ex->data[2] *= 2; ex->data[3] /= 2; sleep(1); } /* detach shared memory */ if (shmdt(ex) == -1) { perror("shmdt"); exit(EXIT_FAILURE); } /* destroy shared memory */ if (shmctl(shm_id, IPC_RMID, 0) == -1) { perror("shmctl"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); }
程式流程其實很簡單,就是
- shmget() – 取得 shared memory ID
- shmat() – attach shared memory
- 操作 shared memory 內容
- shmdt() – detach shared memory
- shmctl() – 移除 shared memory
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <error.h> #include <sys/shm.h> #include "shmem.h" int main(int argc, char *argv[]) { int shm_id; struct shm_ex *ex; int end = 0; if ((argc == 2) && (strcmp(argv[1], "end") == 0)) { end = 1; } /* get the ID of shared memory */ shm_id = shmget((key_t)KEY_SHM_EXAMPLE, sizeof(struct shm_ex), 0666 | IPC_CREAT); if (shm_id == -1) { perror("shmget"); exit(EXIT_FAILURE); } /* attach shared memory */ ex = (struct shm_ex *)shmat(shm_id, (void *)0, 0); if (ex == (void *)-1) { perror("shmget"); exit(EXIT_FAILURE); } printf("get data:\n"); printf("\tdata[0]: %d\n", ex->data[0]); printf("\tdata[1]: %d\n", ex->data[1]); printf("\tdata[2]: %d\n", ex->data[2]); printf("\tdata[3]: %d\n", ex->data[3]); if (end) { ex->flag = 0; printf("end\n"); } /* detach shared memory */ if (shmdt(ex) == -1) { perror("shmdt"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); }
流程與 proc1 大同小異,不過這裡並不會移除 shared memory,該動作將會在 proc1 完成。
執行 go.sh 輸出結果
get data: data[0]: 1002 data[1]: 998 data[2]: 2000 data[3]: 500 get data: data[0]: 1006 data[1]: 994 data[2]: 8000 data[3]: 125 get data: data[0]: 1008 data[1]: 992 data[2]: 16000 data[3]: 62 get data: data[0]: 1010 data[1]: 990 data[2]: 32000 data[3]: 31 end
實務上若有多個 process 都要寫入同一個 shared memory 時,最好加上像是 semaphore 的保護機制,以免發生 race condition 使得程式執行結果不如預期。
附件
解壓此 tarball 之後可以直接執行 ./go.sh 可以自動化完成編譯與執行。
延伸閱讀
- Beginning Linux Programming 4/e
你的proc1有些字是亂碼啊!