共享內存


  共享內存可以說是最有用的進程間通信方式,也是最快的IPC形式。兩個不同進程A、B共享內存的意思是,同一塊物理內存被映射到進程A、B各自的進程地址空間。進程A可以即時看到進程B對共享內存中數據的更新,反之亦然。由於多個進程共享同一塊內存區域,必然需要某種同步機制,互斥鎖和信號量都可以。
  采用共享內存通信的一個顯而易見的好處是效率高,因為進程可以直接讀寫內存,而不需要任何數據的拷貝。對於像管道和消息隊列等通信方式,則需要在內核和用戶空間進行四次的數據拷貝,而共享內存則只拷貝兩次數據[1]:一次從輸入文件到共享內存區,另一次從共享內存區到輸出文件。實際上,進程之間在共享內存時,並不總是讀寫少量數據后就解除映射,有新的通信時,再重新建立共享內存區域。而是保持共享區域,直到通信完畢為止,這樣,數據內容一直保存在共享內存中,並沒有寫回文件。共享內存中的內容往往是在解除映射時才寫回文件的。因此,采用共享內存的通信方式效率是非常高的。

  默認情況下通過fork派生的子進程並不與父進程共享內存區。通過一個程序來驗證,程序功能是讓父子進程都給一個名為count的全局變量加1操作,程序如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <sys/types.h>
 5 #include <semaphore.h>
 6 #include <fcntl.h>
 7 
 8 #define   SEM_NAME         "mysem"
 9 #define   FILE_MODE   (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
10 int   count = 0;
11 
12 int main(int argc,char* argv[])
13 {
14     int  i,nloop;
15     sem_t *mutex;
16     if(argc != 2)
17     {
18         printf("usage: incrl <#loops>");
19         exit(0);
20     }
21     nloop = atoi(argv[1]);
22         //創建有名信號量
23     mutex = sem_open(SEM_NAME,O_RDWR|O_CREAT|O_EXCL,FILE_MODE,1);
24     sem_unlink(SEM_NAME);
25         //將stdout設置為非緩沖的
26     setbuf(stdout,NULL);
27        //子進程開始執行增加1
28     if(fork() == 0)
29     {
30         for(i = 0;i<nloop;++i)
31         {
32             sem_wait(mutex);
33             printf("child: %d\n",count++);
34             sem_post(mutex);
35         }
36         exit(0);
37     }
38        //父進程執行增加1操作
39     for(i = 0;i<nloop;++i)
40     {
41         sem_wait(mutex);
42         printf("parent: %d\n",count++);
43         sem_post(mutex);
44     }
45         //等待子進程退出
46     wait(NULL); 
47     exit(0);
48 }

程序執行結果如下所示:

從結果可以看出父子進程都有各自的全局變量count的副本,每個進程都從該變量為0的初始值開始的,每次增加的對象是各自的變量的副本。

共享內存操作函數:

1、系統調用mmap()
   void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )

   mamap函數把一個文件或一個Posix共享內存區對象映射到調用進程的地址空間。參數fd為即將映射到進程空間的文件描述字,一般由open()返回,同時,fd可以指定為-1,此時須指定flags參數中的MAP_ANON,表明進行的是匿名映射(不涉及具體的文件名,避免了文件的創建及打開,很顯然只能用於具有親緣關系的進程間通信)。len是映射到調用進程地址空間的字節數,它從被映射文件開頭offset個字節開始算起。prot 參數指定共享內存的訪問權限。可取如下幾個值的或:PROT_READ(可讀) , PROT_WRITE (可寫), PROT_EXEC (可執行), PROT_NONE(不可訪問)。flags由以下幾個常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必選其一,而MAP_FIXED則不推薦使用。offset參數一般設為0,表示從文件頭開始映射。參數addr指定文件應被映射到進程空間的起始地址,一般被指定一個空指針,此時選擇起始地址的任務留給內核來完成。函數的返回值為最后文件映射到進程空間的地址,進程可直接操作起始地址為該值的有效地址。

2、系統調用munmap()
   int munmap( void * addr, size_t len )

   munmap函數從某個進程的地址空間中刪除一個映射關系。addr是調用mmap()時返回的地址,len是映射區的大小。當映射關系解除后,對原來映射地址的訪問將導致段錯誤發生。

3、系統調用msync()
   int msync ( void * addr , size_t len, int flags)

  一般說來,進程在映射空間的對共享內容的改變並不直接寫回到磁盤文件中,往往在調用munmap()后才執行該操作。可以通過調用msync()實現磁盤上文件內容與共享內存區的內容一致。

 現使用共享內存實現在內存映射文件中個計數器持續加1,程序如下所示:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <sys/types.h>
 5 #include <semaphore.h>
 6 #include <fcntl.h>
 7 #include <sys/mman.h>
 8 #include <errno.h>
 9 
10 #define   SEM_NAME         "mysem"
11 #define   FILE_MODE   (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
12 
13 int main(int argc,char* argv[])
14 {
15     int     fd,i,nloop,zero = 0;
16     int      *ptr;
17     sem_t   *mutex;
18     if(argc != 3)
19     {
20         printf("usage: incrl <#loops>");
21         exit(0);
22     }
23     nloop = atoi(argv[2]);
24     //打開文件
25     fd = open(argv[1],O_RDWR|O_CREAT,FILE_MODE);
26     //向文件中寫入0值
27     write(fd,&zero,sizeof(int));
28     //將文件映射到進程地址空間,返回被映射區的起始地址
29     ptr = mmap(NULL,sizeof(int),PROT_READ| PROT_WRITE,MAP_SHARED,fd,0);
30     if(ptr == MAP_FAILED)
31     {
32         perror("mmap() error");
33         exit(0);
34     }
35     close(fd);
36     mutex = sem_open(SEM_NAME,O_RDWR|O_CREAT|O_EXCL,FILE_MODE,1);
37     sem_unlink(SEM_NAME);
38     setbuf(stdout,NULL);
39     if(fork() == 0)
40     {
41         for(i = -0;i<nloop;++i)
42         {
43             sem_wait(mutex);
44             printf("child: %d\n",(*ptr)++);
45             sem_post(mutex);
46         }
47         exit(0);
48     }
49     for(i = 0;i<nloop;++i)
50     {
51         sem_wait(mutex);
52         printf("parent: %d\n",(*ptr)++);
53         sem_post(mutex);
54     }
55     wait(NULL);
56     exit(0);
57 }

程序執行結果如下所示:

從結果可以看出父子進程共享內存區。可以將這個程序改成使用[osix基於內存的信號量,而不是一個Posix有名信號量,並把該信號量存放在共享內存中。程序如下所示:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <sys/types.h>
 5 #include <semaphore.h>
 6 #include <fcntl.h>
 7 #include <sys/mman.h>
 8 #include <errno.h>
 9 
10 #define   SEM_NAME         "mysem"
11 #define   FILE_MODE   (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
12 
13 //共享內存結構
14 struct shared
15 {
16     sem_t mutex;       //信號量
17     int count;         //計數器
18 }shared;
19 
20 int main(int argc,char* argv[])
21 {
22     int     fd,i,nloop;
23     struct  shared *ptr;
24     if(argc != 3)
25     {
26         printf("usage: incrl <#loops>");
27         exit(0);
28     }
29     nloop = atoi(argv[2]);
30     fd = open(argv[1],O_RDWR|O_CREAT,FILE_MODE);
31     write(fd,&shared,sizeof(struct shared));
32     ptr = mmap(NULL,sizeof(struct shared),PROT_READ| PROT_WRITE,MAP_SHARED,fd,0);
33     if(ptr == MAP_FAILED)
34     {
35         perror("mmap() error");
36         exit(0);
37     }
38     close(fd);
39     sem_init(&ptr->mutex,1,1);
40     setbuf(stdout,NULL);
41     if(fork() == 0)
42     {
43         for(i = -0;i<nloop;++i)
44         {
45             sem_wait(&ptr->mutex);
46             printf("child: %d\n",ptr->count++);
47             sem_post(&ptr->mutex);
48         }
49         exit(0);
50     }
51     for(i = 0;i<nloop;++i)
52     {
53         sem_wait(&ptr->mutex);
54         printf("parent: %d\n",ptr->count++);
55         sem_post(&ptr->mutex);
56     }
57     wait(NULL);
58     exit(0);
59 }

程序執行結果與上面的一致。從上面的程序發現,我們在進行文件映射的時候,當文件不存在的時候需要在文件系統中創建一個文件然后打開。4.4BSD提供匿名內存映射,避免了文件的創建和打開。其解決辦法是將mmap的flags的參數指定為MAP_SHARED | MAP_ANON,把fd參數指定為-1,offset參數則被忽略。這樣的內存區初始化為0。實現如下所示:

int *ptr;
ptr = mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED | MAP_ANON,-1,0);

SVR4提供了/dev/zero設備文件,從該設備讀是返回的字節全為0,寫往該設備的任何字節被丟棄。實現如下所示:

int *ptr;
fd = open("dev/zero",O_RDWR);
ptr = mmap(NULL,sizeof(int),PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM