【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