1. 程式人生 > >IPC通訊:Posix共享記憶體

IPC通訊:Posix共享記憶體

http://www.cnblogs.com/polestar/archive/2012/04/23/2466003.html 

共享記憶體區是最快的可用IPC形式。它允許多個不相關的程序去訪問同一部分邏輯記憶體。如果需要在兩個執行中的程序之間傳輸資料,共享記憶體將是一種效率極高的解決方案。一旦這樣的記憶體區對映到共享它的程序的地址空間,這些程序間資料的傳輸就不再涉及核心。這樣就可以減少系統呼叫時間,提高程式效率

  共享記憶體是由IPC為一個程序建立的一個特殊的地址範圍,它將出現在程序的地址空間中。其他程序可以把同一段共享記憶體段“連線到”它們自己的地址空間裡去。所有程序都可以訪問共享記憶體中的地址。如果一個程序向這段共享記憶體寫了資料,所做的改動會立刻被有訪問同一段共享記憶體的其他程序看到。

  要注意的是共享記憶體本身沒有提供任何同步功能。也就是說,在第一個程序結束對共享記憶體的寫操作之前,並沒有什麼自動功能能夠預防第二個程序開始對它進行讀操作。共享記憶體的訪問同步問題必須由程式設計師負責。可選的同步方式有互斥鎖、條件變數、讀寫鎖、紀錄鎖、訊號燈。

相關函式

複製程式碼
 1 mmap()函式
 2 功能:把一個檔案或一個Posix共享記憶體區物件對映到呼叫程序的地址空間。
 3 標頭檔案:#include <sys/mman.h>
 4 函式原型:void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off);
5 返回值:如果mmap成功則返回對映首地址,如果出錯則返回常數MAP_FAILED。 6 引數: 7 addr 指向對映儲存區的起始地址; 8 len 對映的位元組 9 prot 對對映儲存區的保護要求 10 flag 標誌位 11 filedes 要被對映檔案的描述符 12 off 要對映位元組在檔案中的起始偏移量 13 14 引數解釋如下:整體相當於磁碟檔案的對應長度搬移到記憶體中。如果addr引數為NULL,核心會自己在程序地址空間中選擇合適的地址建立對映。 15 如果addr不是NULL,則給核心一個提示,應該從什麼地址開始對映,核心會選擇addr之上的某個合適的地址開始對映。建立對映後,真正的
16 對映首地址通過返回值可以得到。off引數是從檔案的什麼位置開始對映,必須是頁大小的 17 整數倍(在32位體系統結構上通常是4K)。 18 prot引數有四種取值: 19 PROT_EXEC表示對映的這一段可執行,例如對映共享庫 20 PROT_READ表示對映的這一段可讀 21 PROT_WRITE表示對映的這一段可寫 22 PROT_NONE表示對映的這一段不可訪問 23 flag引數有很多種取值,這裡只講兩種, 24 MAP_SHARED多個程序對同一個檔案的對映是共享的,一個程序對對映的記憶體做了修改,另一個程序也會看到這種變化。 25 MAP_PRIVATE多個程序對同一個檔案的對映不是共享的,一個程序對對映的記憶體做了修改,另一個程序並不會看到這種變化,也不會 26 真的寫到檔案中去。
複製程式碼

注:當程序終止時,該程序的對映記憶體會自動解除,也可以呼叫munmap解除對映。munmap成功返回0,出錯返回-1

複製程式碼
 1 munmap()函式
 2 功能:解除儲存對映
 3 標頭檔案:#include <sys/mman.h>
 4 函式原型:int munmap(caddr_t addr,size_t len);
 5 引數:
 6     addr   指向對映儲存區的起始地址
 7     len    對映的位元組
 8 返回值:若成功則返回0,若出錯則返回-1
 9 
10 其中addr引數是由mmap返回的地址,len是對映區的大小。再次訪問這些地址導致向呼叫程序產生一個SIGSEGV訊號。如果被對映區是使用
11 MAP_PRIVATE標誌對映的,那麼呼叫程序對它所作的變動都被丟棄掉。 
複製程式碼

  核心的虛存演算法保持記憶體對映檔案(一般在硬碟上)與記憶體對映區(在記憶體中)的同步(前提它是MAP_SHARED記憶體區)。這就是說,如果我們修改了記憶體對映到某個檔案的記憶體區中某個位置的內容,那麼核心將在稍後某個時刻相應地更新檔案。然而有時候我們希望確信硬碟上的檔案內容與記憶體對映區中的檔案內容一致,於是呼叫msync來執行這種同步。

複製程式碼
 1 msync()函式
 2 功能:同步檔案到儲存器
 3 標頭檔案:#include <sys/mman.h>
 4 函式原型:int msync(void *addr,size_t len,int flags);
 5 引數:
 6     addr  指向對映儲存區的起始地址
 7     len   對映的位元組
 8     flags 引數為MS_ASYNC(執行非同步寫),MS_SYNC(執行同步寫),MS_INVALIDATE(使快取記憶體的資料實效)。
 9           其中MS_ASYNC和MS_SYNC這兩個常值中必須指定一個,但不能都指定。它們的差別是,一旦寫操作已由核心排入佇列,
10           MS_ASYNC即返回,而MS_SYNC則要等到寫操作完成後才返回。如果還指定了MS_INVALIDATE,那麼與其最終拷貝不一致的檔案資料的
11           所有記憶體中拷貝都失效。後續的引用將從檔案取得資料。
12 返回值:    若成功則返回0,若出錯則返回-1
13 
14 
15 memcpy()函式
16 功能:    複製對映儲存區
17 標頭檔案:    #include <string.h>
18 函式原形:    void *memcpy(void *dest,const void *src,size_t n);
19 引數:    
20     dest   待複製的對映儲存區
21     src    複製後的對映儲存區
22     n      待複製的對映儲存區的大小
23 返回值:    返回dest的首地址
複製程式碼

示例程式碼:

複製程式碼
/*mycp.c*/
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>

int main(int argc,char *argv[])
{
    int fdin,fdout;
    void *src,*dst;
    struct stat statbuf;

    if(argc!=3)
    {
        printf("please input two file!\n");
        exit(1);
    }
    /*開啟原檔案*/
    if((fdin=open(argv[1],O_RDONLY))<0) 
        perror(argv[1]);
  /*建立並開啟目標檔案*/
    if((fdout=open(argv[2],O_RDWR|O_CREAT|O_TRUNC))<0)
        perror(argv[2]);
  /*獲得檔案大小資訊*/
    if(fstat(fdin,&statbuf)<0) 
        printf("fstat error");
  /*初始化輸出對映儲存區*/
    if(lseek(fdout,statbuf.st_size-1,SEEK_SET)==-1)
        printf("lseek error");
    if(write(fdout,"1",1)!=1)
        printf("write error");
  /*對映原檔案到輸入的對映儲存區*/
    if((src=mmap(0,statbuf.st_size,PROT_READ,MAP_SHARED,fdin,0))==MAP_FAILED)       
        printf("mmap error");
        
  /*對映目標檔案到輸出的對映儲存區*/ 
    if((dst=mmap(0,statbuf.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,fdout,0)) ==MAP_FAILED)
        printf("mmap error");
    memcpy(dst,src,statbuf.st_size);/*複製對映儲存區*/
    munmap(src,statbuf.st_size); /*解除輸入對映*/
    munmap(dst,statbuf.st_size); /*解除輸出對映*/
    close(fdin);
    close(fdout);
}
複製程式碼

編譯執行:

1 [email protected]:/mnt/hgfs/C_libary# vi mycp.c
2 [email protected]:/mnt/hgfs/C_libary# gcc -o mycp mycp.c
3 [email protected]:/mnt/hgfs/C_libary# ./mycp test1.txt test2.txt
4 [email protected]:/mnt/hgfs/C_libary# more test2.txt
5 hello world!

Posix共享記憶體區涉及兩個步驟:

1、指定一個名字引數呼叫shm_open,以建立一個新的共享記憶體區物件或開啟一個以存在的共享記憶體區物件。

2、呼叫mmap把這個共享記憶體區對映到呼叫程序的地址空間。傳遞給shm_open的名字引數隨後由希望共享該記憶體區的任何其他程序使用。

相關函式:

複製程式碼
 1 shm_open()函式
 2 功能:    開啟或建立一個共享記憶體區
 3 標頭檔案:    #include <sys/mman.h>
 4 函式原形:    int shm_open(const char *name,int oflag,mode_t mode);
 5 返回值:    成功返回0,出錯返回-1
 6 引數:    
 7     name    共享記憶體區的名字
 8     oflag    標誌位
 9     mode    許可權位
10 
11 引數解釋:oflag引數必須含有O_RDONLY和O_RDWR標誌,還可以指定如下標誌:O_CREAT,O_EXCL或O_TRUNC.mode引數指定許可權位,
12 它指定O_CREAT標誌的前提下使用。shm_open的返回值是一個整數描述字,它隨後用作mmap的第五個引數。
13 
14 shm_unlink()函式
15 功能:    刪除一個共享記憶體區
16 標頭檔案:    #include <sys/mman.h>
17 函式原形:    int shm_unlink(const char *name);
18 引數:     name    共享記憶體區的名字
19 返回值:    成功返回0,出錯返回-1
20 
21 shm_unlink函式刪除一個共享記憶體區物件的名字,刪除一個名字僅僅防止後續的open,mq_open或sem_open呼叫取得成功。
複製程式碼

示例程式碼:

複製程式碼
 1 /*shm_open.c建立共享記憶體區*/
 2 #include <stdlib.h>
 3 #include <sys/mman.h>
 4 #include <stdio.h>
 5 #include <fcntl.h>
 6 
 7 int main(int argc,char **argv)
 8 {
 9     int shm_id;
10 
11     if(argc!=2)
12     {
13         printf("usage:shm_open <pathname>\n");
14         exit(1);
15     }
16     shm_id=shm_open(argv[1],O_RDWR|O_CREAT,0644);
17     printf("shmid:%d\n",shm_id);
18     shm_unlink(argv[1]);
19 }
複製程式碼

編譯執行:

複製程式碼
 1 [email protected]:/mnt/hgfs/C_libary# vi shm_open.c
 2 [email protected]:/mnt/hgfs/C_libary# gcc -o shm_open shm_open.c
 3 /tmp/cceAejzp.o: In function `main':
 4 shm_open.c:(.text+0x43): undefined reference to `shm_open'
 5 shm_open.c:(.text+0x6c): undefined reference to `shm_unlink'
 6 collect2: ld returned 1 exit status
 7 [email protected]:/mnt/hgfs/C_libary# gcc -lrt -o shm_open shm_open.c
 8 [email protected]:/mnt/hgfs/C_libary# ./shm_open shm_test
 9 shmid:3
10 [email protected]:/mnt/hgfs/C_libary# 
複製程式碼

相關函式2:

複製程式碼
 1 ftruncate()函式 3 功能:    調整檔案或共享記憶體區大小
 4 標頭檔案:    #include <unistd.h>
 5 函式原形:    int ftruncate(int fd,off_t length);
 6 引數:    
 7     fd          描述符
 8     length       大小
 9 返回值:    成功返回0,出錯返回-1
10 
11 當開啟一個已存在的共享記憶體區物件時,我們可呼叫fstat來獲取有關該物件的資訊
12 
13 fstat()函式
14 功能:    獲得檔案或共享記憶體區的資訊
15 標頭檔案:    #include <unistd.h>
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 函式原形:    int stat(const char *file_name,struct stat *buf);
19 引數:    
20 file_name          檔名
21 buf               stat結構
22 返回值:    成功返回0,出錯返回-1
23 
24 對於普通檔案stat結構可以獲得12個以上的成員資訊,然而當fd指代一個共享記憶體區物件時,只有四個成員含有資訊。
25 struct stat{
26     mode_t st_mode;
27     uid_t st_uid;
28     gid_t st_gid;
29     off_t st_size;
30 };
複製程式碼

示例程式碼:

複製程式碼
 1 /*shm_show.c顯示共享區資訊*/
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 #include <unistd.h>
 5 #include <sys/stat.h>
 6 #include <sys/types.h>
 7 #include <fcntl.h>
 8 #include <sys/mman.h>
 9 
10 int main(int argc,char **argv)
11 {
12     int shm_id;
13         struct stat buf;
14 
15         if(argc!=2)
16         {
17             printf("usage:shm_open <pathname>\n");
18             exit(1);
19         }
20         shm_id=shm_open(argv[1],O_RDWR|O_CREAT,0644);/*建立共享記憶體*/
21         printf("size :%d\n",buf.st_size); /*修改前共享記憶體區大小*/
22         ftruncate(shm_id,100);/*修改共享記憶體的大小*/
23         fstat(shm_id,&buf); /*把共享記憶體的資訊記錄到buf中*/
24         printf("uid_t:%d\n",buf.st_uid); /*共享記憶體區所有者ID*/
25         printf("git_t:%d\n",buf.st_gid); /*共享記憶體區所有者組ID*/
26         printf("size :%d\n",buf.st_size); /*修改後共享記憶體區大小*/
27 }
複製程式碼

編譯執行:

複製程式碼
 1 [email protected]:/mnt/hgfs/C_libary# gcc -lrt -o shm_show shm_show.c
 2 shm_show.c: In function ‘main’:
 3 shm_show.c:20: warning: format ‘%d’ expects type ‘int’, but argument 2 has type ‘__off_t’
 4 shm_show.c:25: warning: format ‘%d’ expects type ‘int’, but argument 2 has type ‘__off_t’
 5 
 6 /* 內容來自網際網路
 7         有人提出來在sys/types.h裡面加上 typedef long __off_t; typedef long __time_t; 
 8         
 9         小魚我看了一下原始檔,認為上面的說法還是有問題的,露了off_t和time_t
10         #ifndef __off_t_defined 
11         # ifndef __USE_FILE_OFFSET64 
12         typedef __off_t off_t; 
13         # else 
14         typedef __off64_t off_t; 
15         # endif 
16         # define __off_t_defined 
17         #endif  
18         
19         如果定義了__USE_FILE_OFFSET64 就把off_t定義為__off64_t ,否則定義為32位(傳說中的大檔案支援處理) 
20         ------------------------------
21         其實沒必要casting。直接用%ld換掉%d就行了,因為是long int所以%d會拋警告的 
22 */
23 [email protected]:/mnt/hgfs/C_libary# gcc -lrt -o shm_show shm_show.c
24 [email protected]:/mnt/hgfs/C_libary# 
25 [email protected]:/mnt/hgfs/C_libary# ./shm_show test
26 size :4983648
27 uid_t:0
28 git_t:0
29 size :100
30 [email protected]:/mnt/hgfs/C_libary# 
複製程式碼

共享記憶體區的寫入和讀出

  前面我們介紹了mmap函式,下面我們就可以通過這些函式,把程序對映到共享記憶體區。然後我們就可以通過共享記憶體區進行程序間通訊了。

示例程式碼:

複製程式碼
 1 /*shm_write.c寫入/讀出共享記憶體區*/
 2 #include <stdio.h>
 3 #include <string.h>
 4 #include <stdlib.h>
 5 #include <sys/stat.h>
 6 #include <fcntl.h>
 7 #include <sys/mman.h>
 8 #include <unistd.h>
 9 
10 int main(int argc,char **argv)
11 {
12     int shm_id;
13     struct stat buf;
14     char *ptr;
15 
16     if(argc!=2)
17     {
18         printf("usage:shm_open <pathname>\n");
19         exit(1);
20