1. 程式人生 > >【IPC】Posix共享記憶體區與mmap記憶體對映

【IPC】Posix共享記憶體區與mmap記憶體對映

共享記憶體是一種IPC形式,與其它IPC機制如管道、訊息佇列等相比,資料不必在程序與核心間多次交換,程序間通訊的速度更快。當共享記憶體區對映到共享它的程序的地址空間時,再加以一些同步控制,這些程序就可以進行資料傳送了。mmap函式提供了記憶體對映功能,可以把一個檔案或一個Posix共享記憶體區物件對映到呼叫程序的地址空間,下面首先介紹mmap的用法。

1、mmap

#include <sys/mman.h>
void* mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int
munmap(void *addr, size_t length); int msync(void *addr, size_t length, int flags);

mmap可使用普通檔案以提供記憶體對映IO,或者是特殊檔案以提供匿名記憶體對映,還可以使用shm_open以提供無親緣關係程序間的Posix共享記憶體區,成功時返回指向對映區域的指標,失敗時返回MAP_FAILED,即((void*)-1),並設定相應的errno。成功返回後,檔案描述字fd可關閉,不影響後續共享記憶體的使用。

引數addr指定了檔案描述字fd對映到程序地址空間的起始地址,不過這個引數一般為NULL,為空時地址由核心來選擇。
引數length指定了fd對映到程序地址空間的位元組數,位元組計數從fd的offset處開始算起,offset為從fd檔案開頭的位元組偏移量。
引數prot指定了記憶體對映區的讀寫許可權,有幾個常值:PROT_EXEC、PROT_READ、PROT_WRITE、PROT_NONE,分別表示可執行、可讀、可寫、不可訪問,前三個可使用按位或符號拼起來使用。
引數flags指定了記憶體對映區的共享模式,必須從MAP_SHARED與MAP_PRIVATE兩者之間選擇一個,前者表示當前程序修改的記憶體對映區對其它程序是可見的,也確實修改了檔案fd並影響到其它程序,後者則恰好相反,表示當前程序修改的記憶體對映區對其它程序是不可見的,並沒有修改檔案fd也不會影響其它程序。flags除了這兩個值之外,還有一些其它的可選值,用按位或符號連起來即可,不過考慮到移植安全,並不使用MAP_FIXED,引數addr也指定為NULL。

mmap用以記憶體對映,解除一個程序的的記憶體對映關係使用munmap。引數addr為mmap返回的地址,length為對映區的大小。成功時返回0,失敗時返回-1,並設定相應的errno。

對於mmap函式的flags引數,為MAP_PRIVATE時,呼叫程序對共享記憶體作的修改都會被丟棄掉;為MAP_SHARED時,核心維護共享記憶體與檔案fd的內容一致,但有時候我們要手動同步共享記憶體與檔案fd的內容,這個工作由msync函式來完成。msync成功時返回0,失敗時返回-1,並設定相應的errno。引數addr為mmap返回的地址,length即對映區的大小,flags是MS_ASYNC、MS_SYNC、MS_INVALIDATE中的集合,但前兩個不可同時指定。MS_ASYNC表示非同步寫,函式立即返回;MS_SYNC表示同步寫,同步完成後函式才返回;MS_INVALIDATE表示與同一個檔案fd對映的其它記憶體區域的原有資料將無效,以使它們立即更新。

下面以例子說明mmap的用法。

例一:fork

// increment.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

int g_count = 0;

int main(int argc, char **argv)
{
    int i, nloop;
    if (2 != argc) {
        printf("usage: %s <#loops>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    nloop = atoi(argv[1]);
    setbuf(stdout, NULL);
    if (fork() == 0) { // child
        for (i = 0; i < nloop; ++i) {
            printf("child: %d\n", g_count++);
        }
        exit(EXIT_SUCCESS);
    }
    for (i = 0; i < nloop; ++i) { // parent
        printf("parent: %d\n", g_count++);
    }
    exit(EXIT_SUCCESS);
}

例一是一個計數程式,全域性計數變數g_count初始值為0,計數次數nloop從命令列指定,我們都知道fork的子程序不會繼承父程序的地址空間,如這裡的g_count,而是一份獨立的拷貝,父、子程序分別對g_count計數nloop次,並輸出計數結果,程式執行結果如下,可以看出父、子程序對g_count的操作並沒有互相影響。

$ gcc -o test increment.c
$ ./test 10
parent: 0
parent: 1
parent: 2
parent: 3
parent: 4
parent: 5
parent: 6
parent: 7
parent: 8
parent: 9
child: 0
child: 1
child: 2
child: 3
child: 4
child: 5
child: 6
child: 7
child: 8
child: 9

例二:mmap sem

// increment2.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
#define SEM_NAME "/semtest"

int main(int argc, char **argv)
{
    int fd, i, nloop, zero = 0;
    int *ptr;
    sem_t *sem;
    if (3 != argc) {
        printf("usage: %s <pathname> <#loops>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    nloop = atoi(argv[2]);
    // open file
    if ((fd = open(argv[1], O_RDWR | O_CREAT, FILE_MODE)) == -1) {
        printf("open error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // initialize fd to 0
    if (write(fd, &zero, sizeof(int)) == -1) {
        printf("write error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // map into memory
    if ((ptr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
        printf("mmap error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // close file
    if (close(fd) == -1) {
        printf("close error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // create semaphore and initialize to 1
    if ((sem = sem_open(SEM_NAME, O_CREAT | O_EXCL, FILE_MODE, 1)) == SEM_FAILED) {
        printf("sem_open error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // unlink semaphore
    if (sem_unlink(SEM_NAME) == -1) {
        printf("sem_unlink error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // stdout is unbuffered
    setbuf(stdout, NULL);
    if (fork() == 0) { // child
        for (i = 0; i < nloop; ++i) {
            if (sem_wait(sem) == -1) {
                printf("sem_wait child: error: %s\n", strerror(errno));
                exit(EXIT_FAILURE);
            }
            printf("child: %d\n", (*ptr)++);
            if (sem_post(sem) == -1) {
                printf("sem_post child error: %s\n", strerror(errno));
                exit(EXIT_FAILURE);
            }
        }
        exit(EXIT_SUCCESS);
    }
    for (i = 0; i < nloop; ++i) { // parent
        if (sem_wait(sem) == -1) {
            printf("sem_wait parent error: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
        printf("parent: %d\n", (*ptr)++);
        if (sem_post(sem) == -1) {
            printf("sem_post parent error: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
    }
    exit(EXIT_SUCCESS);
}

例二在例一的基礎上增加了mmap記憶體對映以在程序間共享資料,因為涉及程序間的資料同步,所以還增加了Posix訊號燈sem。程式中,訊號燈是個有名訊號燈,名字為“/semtest”,mmap所需的共享檔案fd從命令列指定,開啟這個檔案後,初始化為0以提供計數功能,而不是例一中的一個全域性變數用來計數,接著使用mmap完成記憶體對映,mmap的引數指定了檔案讀寫許可權和程序間共享模式,隨後關閉檔案,記憶體對映完成後關閉檔案是沒有影響的。關於sem訊號燈,開啟時初始化為1,接著呼叫sem_unlink,這個對後續訊號燈的使用並沒有影響,因為核心維護著訊號燈的引用計數,只有當最後一個sem_close呼叫或者程式結束後訊號燈才會從系統中移除。最後同樣是父、子程序對計數值的操作,操作物件是mmap返回的地址,因為mmap指定了MAP_SHARED,所以程序間互相影響也確實修改了共享檔案中的內容,所以計數期間加了訊號燈sem_wait/sem_post以完成同步,避免衝突,否則計數結果就會出錯。程式執行結果如下,可以看出父、子程序實現了計數共享。

$ gcc -o test -pthread increment2.c
$ ./test count 10
parent: 0
parent: 1
child: 2
child: 3
child: 4
child: 5
child: 6
child: 7
child: 8
child: 9
child: 10
child: 11
parent: 12
parent: 13
parent: 14
parent: 15
parent: 16
parent: 17
parent: 18
parent: 19

使用file命令檢視共享檔案count的型別為data資料檔案。

$ file count
$ count: data

例三:sem_init

// increment3.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

struct shared
{
    sem_t sem;
    int count;
} shared;

int main(int argc, char **argv)
{
    int fd, i, nloop;
    struct shared *ptr;
    if (3 != argc) {
        printf("usage: %s <pathname> <#loops>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    nloop = atoi(argv[2]);
    // open file
    if ((fd = open(argv[1], O_RDWR | O_CREAT, FILE_MODE)) == -1) {
        printf("open error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // initialize fd to 0
    if (write(fd, &shared, sizeof(struct shared)) == -1) {
        printf("write error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // map into memory
    if ((ptr = mmap(NULL, sizeof(struct shared), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
        printf("mmap error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // close file
    if (close(fd) == -1) {
        printf("close error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // initialize semaphore that is shared between processes
    if (sem_init(&ptr->sem, 1, 1) == -1) {
        printf("sem_init error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // stdout is unbuffered
    setbuf(stdout, NULL);
    if (fork() == 0) { // child
        for (i = 0; i < nloop; ++i) {
            if (sem_wait(&ptr->sem) == -1) {
                printf("sem_wait child: error: %s\n", strerror(errno));
                exit(EXIT_FAILURE);
            }
            printf("child: %d\n", ptr->count++);
            if (sem_post(&ptr->sem) == -1) {
                printf("sem_post child error: %s\n", strerror(errno));
                exit(EXIT_FAILURE);
            }
        }
        exit(EXIT_SUCCESS);
    }
    for (i = 0; i < nloop; ++i) { // parent
        if (sem_wait(&ptr->sem) == -1) {
            printf("sem_wait parent error: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
        printf("parent: %d\n", ptr->count++);
        if (sem_post(&ptr->sem) == -1) {
            printf("sem_post parent error: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
    }
    exit(EXIT_SUCCESS);
}

例二中的訊號燈是個有名訊號燈,訊號燈還可以是基於記憶體對映的匿名訊號燈,為此例三添加了shard結構體,成員變數為一個訊號燈和一個計數器,mmap對映長度為這個結構體的長度,呼叫sem_init初始化訊號燈的值為1,其第二個引數為1,目的是保證訊號燈同步在程序間使用而非同一程序的不同執行緒間,其它用法不變,程式執行結果一樣。

例四:MAP_ANONYMOUS

// increment_map_anon.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
#define SEM_NAME "/semtest"

int main(int argc, char **argv)
{
    int i, nloop;
    int *ptr;
    sem_t *sem;
    if (2 != argc) {
        printf("usage: %s  <#loops>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    nloop = atoi(argv[1]);
    // anonymous mmap
    if ((ptr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)) == MAP_FAILED) {
        printf("mmap error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // create, initialize, and unlink semaphore
    if ((sem = sem_open(SEM_NAME, O_CREAT | O_EXCL, FILE_MODE, 1)) == SEM_FAILED) {
        printf("sem_open error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    if (sem_unlink(SEM_NAME) == -1) {
        printf("sem_unlink error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // stdout is unbuffered
    setbuf(stdout, NULL);
    if (fork() == 0) { // child
        for (i = 0; i < nloop; ++i) {
            if (sem_wait(sem) == -1) {
                printf("sem_wait child: error: %s\n", strerror(errno));
                exit(EXIT_FAILURE);
            }
            printf("child: %d\n", (*ptr)++);
            if (sem_post(sem) == -1) {
                printf("sem_post child error: %s\n", strerror(errno));
                exit(EXIT_FAILURE);
            }
        }
        exit(EXIT_SUCCESS);
    }
    for (i = 0; i < nloop; ++i) { //parent
        if (sem_wait(sem) == -1) {
            printf("sem_wait parent error: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
        printf("parent: %d\n", (*ptr)++);
        if (sem_post(sem) == -1) {
            printf("sem_post parent error: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
    }
    exit(EXIT_SUCCESS);
}

例二、例三中的mmap是對一個共享檔案的記憶體對映,為此首先要開啟一個檔案,不過這並不是必需的,mmap同樣也提供了匿名記憶體對映,不用基於一個確實存在的檔案來實現對映,這時mmap的flags為MAP_SHARED | MAP_ANONYMOUS,fd和offset被忽略,不過fd一般為-1,offset為0。例四在例二的基礎上作了修改,通過mmap的引數變更,使用匿名記憶體對映,程式執行結果相同。

mmap對映長度——
使用mmap對映一個普通檔案時,還有一點值得注意,那就是mmap的第二個引數,即mmap的對映長度,上面的例子中mmap的對映長度等於共享檔案的大小,然而它們可以不相等。

除了匿名記憶體對映之外,有時候我們還可以開啟一個特殊的檔案“/dev/zero”,將這個檔案描述字fd作為mmap的引數也是可以的,任何從這個fd讀取的資料都為0,寫往這個fd的資料也都被丟棄,也可以完成程序間的資料共享,例子如下。

// increment_dev_zero.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
#define SEM_NAME "/semtest"

int main(int argc, char **argv)
{
    int fd, i, nloop;
    int *ptr;
    sem_t *sem;
    if (2 != argc) {
        printf("usage: %s  <#loops>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    nloop = atoi(argv[1]);
    // open /dev/zero
    if ((fd = open("/dev/zero", O_RDWR)) == -1) {
        printf("open error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // mmap
    if ((ptr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
        printf("mmap error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // close fd
    if (close(fd) == -1) {
        printf("close error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // create, initialize, and unlink semaphore
    if ((sem = sem_open(SEM_NAME, O_CREAT | O_EXCL, FILE_MODE, 1)) == SEM_FAILED) {
        printf("sem_open error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    if (sem_unlink(SEM_NAME) == -1) {
        printf("sem_unlink error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // stdout is unbuffered
    setbuf(stdout, NULL);
    if (fork() == 0) { // child
        for (i = 0; i < nloop; ++i) {
            if (sem_wait(sem) == -1) {
                printf("sem_wait child: error: %s\n", strerror(errno));
                exit(EXIT_FAILURE);
            }
            printf("child: %d\n", (*ptr)++);
            if (sem_post(sem) == -1) {
                printf("sem_post child error: %s\n", strerror(errno));
                exit(EXIT_FAILURE);
            }
        }
        exit(EXIT_SUCCESS);
    }
    for (i = 0; i < nloop; ++i) { // parent
        if (sem_wait(sem) == -1) {
            printf("sem_wait parent error: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
        printf("parent: %d\n", (*ptr)++);
        if (sem_post(sem) == -1) {
            printf("sem_post parent error: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
    }
    exit(EXIT_SUCCESS);
}

2、Posix共享記憶體區

mmap函式中的引數fd,可以是open函式開啟的一個記憶體對映檔案,還可以是shm_open函式開啟的一個共享記憶體區物件,相關函式如下。

#include <sys/mman.h>
int shm_open(const char *name, int oflag, mode_t mode);
int shm_unlink(const char *name);

下面是幾個簡答的例子。

// shmcreate.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

int main(int argc, char **argv)
{
    int c, fd, flags;
    char *ptr;
    off_t length;
    flags = O_RDWR | O_CREAT;
    while ((c = getopt(argc, argv, "e")) != -1) {
        switch (c) {
        case 'e':
            flags |= O_EXCL;
            break;
        }
    }
    if (argc - 2 != optind) {
        printf("usage: %s [-e] <name> <length>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    length = atol(argv[optind + 1]);
    // sh_open
    if ((fd = shm_open(argv[optind], flags, FILE_MODE)) == -1) {
        printf("shm_open error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // ftruncate
    if (ftruncate(fd, length) == -1) {
        printf("ftruncate error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // mmap
    if ((ptr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
        printf("mmap error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    exit(EXIT_SUCCESS);
}

// shmunlink.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>

int main(int argc, char **argv)
{
    if (2 != argc) {
        printf("usage: %s [-e] <name>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    // sh_unlink
    if (shm_unlink(argv[1]) == -1) {
        printf("shm_unlink error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    exit(EXIT_SUCCESS);
}

// shmread.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

int main(int argc, char **argv)
{
    int i, fd;
    struct stat stat;
    unsigned char c, *ptr;
    if (2 != argc) {
        printf("usage: %s <name>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    // sh_open
    if ((fd = shm_open(argv[1], O_RDONLY, FILE_MODE)) == -1) {
        printf("shm_open error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // fstat
    if (fstat(fd, &stat) == -1) {
        printf("fstat error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // mmap
    if ((ptr = mmap(NULL, stat.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
        printf("mmap error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // close
    if (close(fd) == -1) {
        printf("close error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    for (i = 0; i < stat.st_size; ++i) {
        if ((c = *ptr++) != (i % 256)) {
            printf("error: ptr[%d] = %d\n", i, c);
        }
    }
    exit(EXIT_SUCCESS);
}

// shmwrite.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

int main(int argc, char **argv)
{
    int i, fd;
    struct stat stat;
    unsigned char *ptr;
    if (2 != argc) {
        printf("usage: %s <name>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    // sh_open
    if ((fd = shm_open(argv[1], O_RDWR, FILE_MODE)) == -1) {
        printf("shm_open error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // fstat
    if (fstat(fd, &stat) == -1) {
        printf("fstat error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // mmap
    if ((ptr = mmap(NULL, stat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
        printf("mmap error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // close
    if (close(fd) == -1) {
        printf("close error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    for (i = 0; i < stat.st_size; ++i) {
        *ptr++ = i % 256;
    }
    exit(EXIT_SUCCESS);
}

shm_open建立或開啟一個共享記憶體區物件,建立成功後共享記憶體區物件的位置在/dev/shm目錄下,shm_unlink移除一個共享記憶體區物件,連結時使用“-lrt”選項。

#include <unistd.h>
#include <sys/stat.h>
int ftruncate(int fd, off_t length);
int fstat(int fd, struct stat *buf);

ftruncate設定一個檔案的長度,引數fd為shm_open的返回值,fstat獲取一個檔案fd的狀態。

下面以一個例子說明shm_open等函式的用法。

// server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <semaphore.h>

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

struct shmstruct // struct stored in shared memory
{
    int count;
};

sem_t *sem; // pointer to named semaphore

int main(int argc, char **argv)
{
    int fd;
    struct shmstruct *ptr;
    if (3 != argc) {
        printf("usage: %s <shm_name> <sem_name>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    shm_unlink(argv[1]); // unlink if used, OK if failed
    // open shm
    if ((fd = shm_open(argv[1], O_RDWR | O_CREAT | O_EXCL, FILE_MODE)) == -1) {
        printf("shm_open error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // set size
    if (ftruncate(fd, sizeof(struct shmstruct)) == -1) {
        printf("ftruncate error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // mmap shm
    if ((ptr = mmap(NULL, sizeof(struct shmstruct), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
        printf("mmap error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // close shm
    if (close(fd) == -1) {
        printf("close error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    sem_unlink(argv[2]); // unlink if used, OK if failed
    // open sem
    if ((sem = sem_open(argv[2], O_CREAT | O_EXCL, FILE_MODE, 1)) == SEM_FAILED) {
        printf("sem_open error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // close sem
    if (sem_close(sem) == -1) {
        printf("sem_close error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    exit(EXIT_SUCCESS);
}

// client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <semaphore.h>

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

struct shmstruct // struct stored in shared memory
{
    int count;
};

sem_t *sem; // pointer to named semaphore

int main(int argc, char **argv)
{
    int fd, i, nloop;
    pid_t pid;
    struct shmstruct *ptr;
    if (4 != argc) {
        printf("usage: %s <shm_name> <sem_name> <#loops>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    nloop = atoi(argv[3]);
    // open shm
    if ((fd = shm_open(argv[1], O_RDWR, FILE_MODE)) == -1) {
        printf("shm_open error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // mmap shm
    if ((ptr = mmap(NULL, sizeof(struct shmstruct), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
        printf("mmap error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // close shm
    if (close(fd) == -1) {
        printf("close error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // open sem
    if ((sem = sem_open(argv[2], 0)) == SEM_FAILED) {
        printf("sem_open error: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    pid = getpid();
    for (i = 0; i < nloop; ++i) {
        if (sem_wait(sem) == -1) {
            printf("sem_wait error: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
        printf("pid %ld: %d\n", (long)pid, ptr->count++);
        if (sem_post(sem) == -1) {
            printf("sem_post error: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
    }
    exit(EXIT_SUCCESS);
}

例子中,仍然是一個計數程式,不過共享記憶體區的同步是在沒有親緣關係的程序間完成的。計數變數count放到了shmstruct結構中,同步機制使用Posix訊號燈sem。server.c的作用是建立一個共享記憶體區供其它程序使用,其大小為shmstruct結構體的大小,接著建立一個有名訊號燈並初始化為1。client.c的作用是計數,使用server.c已經建立好的共享記憶體區和訊號燈,輸出當前程序號和計數值,執行結果如下。

$ gcc -o server server.c -pthread -lrd
$ gcc -o client client.c -pthread -lrd
$ ./server shm sem
$ ./client shm sem 10 & ./client shm sem 10
[1] 8423
pid 8423: 0
pid 8423: 1
pid 8423: 2
pid 8423: 3
pid 8423: 4
pid 8423: 5
pid 8423: 6
pid 8423: 7
pid 8423: 8
pid 8423: 9
pid 8424: 10
pid 8424: 11
pid 8424: 12
pid 8424: 13
pid 8424: 14
pid 8424: 15
pid 8424: 16
pid 8424: 17
pid 8424: 18
pid 8424: 19
[1]+  已完成               ./c shm sem 10

可以看出,open與shm_open的用法類似,只不過shm_open建立的共享記憶體區物件位於虛擬檔案系統中,在Linux上的位置是“/dev/shm”,通過file命令檢視一下例子中建立的shm的型別,data型別,檢視其內容可使用od命令。

$ file /dev/shm/shm
/dev/shm/shm: data