實作 System V Shared Memory in Linux User Space

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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <error.h>

#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);
}

程式流程其實很簡單,就是

  1. shmget() – 取得 shared memory ID
  2. shmat() – attach shared memory
  3. 操作 shared memory 內容
  4. shmdt() – detach shared memory
  5. 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 使得程式執行結果不如預期。

附件

程式碼:felix-lin_shm.tar.bz2

解壓此 tarball 之後可以直接執行 ./go.sh 可以自動化完成編譯與執行。

延伸閱讀

  • Beginning Linux Programming 4/e

1 則迴響於《實作 System V Shared Memory in Linux User Space

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *


一 + = 8

你可以使用這些 HTML 標籤與屬性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>