1. 程式人生 > >共享記憶體實現原理

共享記憶體實現原理

共享記憶體的使用實現原理(必考必問,然後共享記憶體段被對映進程序空間之後,存在於程序空間的什麼位置?共享記憶體段最大限制是多少?)

nmap函式要求核心建立一個新額虛擬儲存器區域,最好是從地質start開始的一個區域,並將檔案描述符fd指定物件的一個連續的片(chunk)對映到這個新的區域。

 SHMMNI為128,表示系統中最多可以有128個共享記憶體物件。

 

共享記憶體可以說是最有用的程序間通訊方式,也是最快的IPC形式。兩個不同程序A、B共享記憶體的意思是,同一塊實體記憶體被對映到程序A、B各自的程序地址空間。程序A可以即時看到程序B對共享記憶體中資料的更新,反之亦然。由於多個程序共享同一塊記憶體區域,必然需要某種同步機制,互斥鎖和訊號量都可以。

採用共享記憶體通訊的一個顯而易見的好處是效率高,因為程序可以直接讀寫記憶體,而不需要任何資料的拷貝。對於像管道和訊息佇列等通訊方式,則需要在核心和使用者空間進行四次的資料拷貝,而共享記憶體則只拷貝兩次資料[1]:一次從輸入檔案到共享記憶體區,另一次從共享記憶體區到輸出檔案。實際上,程序之間在共享記憶體時,並不總是讀寫少量資料後就解除對映,有新的通訊時,再重新建立共享記憶體區域。而是保持共享區域,直到通訊完畢為止,這樣,資料內容一直儲存在共享記憶體中,並沒有寫回檔案。共享記憶體中的內容往往是在解除對映時才寫回檔案的。因此,採用共享記憶體的通訊方式效率是非常高的。

Linux的2.2.x核心支援多種共享記憶體方式,如mmap()系統呼叫,Posix共享記憶體,以及系統V共享記憶體。linux發行版本如Redhat 8.0支援mmap()系統呼叫及系統V共享記憶體,但還沒實現Posix共享記憶體,本文將主要介紹mmap()系統呼叫及系統V共享記憶體API的原理及應用。

一、核心怎樣保證各個程序定址到同一個共享記憶體區域的記憶體頁面

1、page cache及swap cache中頁面的區分:一個被訪問檔案的物理頁面都駐留在page cache或swap cache中,一個頁面的所有資訊由struct page來描述。struct page中有一個域為指標mapping ,它指向一個struct address_space型別結構。page cache或swap cache中的所有頁面就是根據address_space結構以及一個偏移量來區分的。

2、檔案與address_space結構的對應:一個具體的檔案在開啟後,核心會在記憶體中為之建立一個struct inode結構,其中的i_mapping域指向一個address_space結構。這樣,一個檔案就對應一個address_space結構,一個address_space與一個偏移量能夠確定一個page cache 或swap cache中的一個頁面。因此,當要定址某個資料時,很容易根據給定的檔案及資料在檔案內的偏移量而找到相應的頁面。

3、程序呼叫mmap()時,只是在程序空間內新增了一塊相應大小的緩衝區,並設定了相應的訪問標識,但並沒有建立程序空間到物理頁面的對映。因此,第一次訪問該空間時,會引發一個缺頁異常。

4、對於共享記憶體對映情況,缺頁異常處理程式首先在swap cache中尋找目標頁(符合address_space以及偏移量的物理頁),如果找到,則直接返回地址;如果沒有找到,則判斷該頁是否在交換區(swap area),如果在,則執行一個換入操作;如果上述兩種情況都不滿足,處理程式將分配新的物理頁面,並把它插入到page cache中。程序最終將更新程序頁表。
注:對於對映普通檔案情況(非共享對映),缺頁異常處理程式首先會在page cache中根據address_space以及資料偏移量尋找相應的頁面。如果沒有找到,則說明檔案資料還沒有讀入記憶體,處理程式會從磁碟讀入相應的頁面,並返回相應地址,同時,程序頁表也會更新。

5、所有程序在對映同一個共享記憶體區域時,情況都一樣,在建立線性地址與實體地址之間的對映之後,不論程序各自的返回地址如何,實際訪問的必然是同一個共享記憶體區域對應的物理頁面。
注:一個共享記憶體區域可以看作是特殊檔案系統shm中的一個檔案,shm的安裝點在交換區上。

上面涉及到了一些
資料結構
,圍繞資料結構理解問題會容易一些。

二、mmap()及其相關係統呼叫

mmap()系統呼叫使得程序之間通過對映同一個普通檔案實現共享記憶體。普通檔案被對映到程序地址空間後,程序可以向訪問普通記憶體一樣對檔案進行訪問,不必再呼叫read(),write()等操作。

注:實際上,mmap()系統呼叫並不是完全為了用於共享記憶體而設計的。它本身提供了不同於一般對普通檔案的訪問方式,程序可以像讀寫記憶體一樣對普通檔案的操作。而Posix或系統V的共享記憶體IPC則純粹用於共享目的,當然mmap()實現共享記憶體也是其主要應用之一。

1、mmap()系統呼叫形式如下:

void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )
引數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指定檔案應被對映到程序空間的起始地址,一般被指定一個空指標,此時選擇起始地址的任務留給核心來完成。函式的返回值為最後檔案對映到程序空間的地址,程序可直接操作起始地址為該值的有效地址。這裡不再詳細介紹mmap()的引數,讀者可參考mmap()手冊頁獲得進一步的資訊。

2、系統呼叫mmap()用於共享記憶體的兩種方式:

(1)使用普通檔案提供的記憶體對映:適用於任何程序之間;此時,需要開啟或建立一個檔案,然後再呼叫mmap();典型呼叫程式碼如下: 

        fd=open(name, flag, mode);
if(fd<0)
        ...
        
ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 通過mmap()實現共享記憶體的通訊方式有許多特點和要注意的地方,我們將在範例中進行具體說明。 

(2)使用特殊檔案提供匿名記憶體對映:適用於具有親緣關係的程序之間;由於父子程序特殊的親緣關係,在父程序中先呼叫mmap(),然後呼叫fork()。那麼在呼叫fork()之後,子程序繼承父程序匿名對映後的地址空間,同樣也繼承mmap()返回的地址,這樣,父子程序就可以通過對映區域進行通訊了。注意,這裡不是一般的繼承關係。一般來說,子程序單獨維護從父程序繼承下來的一些變數。而mmap()返回的地址,卻由父子程序共同維護。
對於具有親緣關係的程序實現共享記憶體最好的方式應該是採用匿名記憶體對映的方式。此時,不必指定具體的檔案,只要設定相應的標誌即可,參見範例2。

3、系統呼叫munmap()

int munmap( void * addr, size_t len )
該呼叫在程序地址空間中解除一個對映關係,addr是呼叫mmap()時返回的地址,len是對映區的大小。當對映關係解除後,對原來對映地址的訪問將導致段錯誤發生。

4、系統呼叫msync()

int msync ( void * addr , size_t len, int flags)
一般說來,程序在對映空間的對共享內容的改變並不直接寫回到磁碟檔案中,往往在呼叫munmap()後才執行該操作。可以通過呼叫msync()實現磁碟上檔案內容與共享記憶體區的內容一致。

三、mmap()範例

下面將給出使用mmap()的兩個範例:範例1給出兩個程序通過對映普通檔案實現共享記憶體通訊;範例2給出父子程序通過匿名對映實現共享記憶體。系統呼叫mmap()有許多有趣的地方,下面是通過mmap()對映普通檔案實現程序間的通訊的範例,我們通過該範例來說明mmap()實現共享記憶體的特點及注意事項。

範例1:兩個程序通過對映普通檔案實現共享記憶體通訊

範例1包含兩個子程式:map_normalfile1.c及map_normalfile2.c。編譯兩個程式,可執行檔案分別為map_normalfile1及map_normalfile2。兩個程式通過命令列引數指定同一個檔案來實現共享記憶體方式的程序間通訊。map_normalfile2試圖開啟命令列引數指定的一個普通檔案,把該檔案對映到程序的地址空間,並對對映後的地址空間進行寫操作。map_normalfile1把命令列引數指定的檔案對映到程序地址空間,然後對對映後的地址空間執行讀操作。這樣,兩個程序通過命令列引數指定同一個檔案來實現共享記憶體方式的程序間通訊。

下面是兩個程式程式碼:

/*-------------map_normalfile1.c-----------*/
#include <sys/mman.h>;
#include <sys/types.h>;
#include <fcntl.h>;
#include <unistd.h>;
typedef struct{
        char name[4];
        int  age;
}people;

main(int argc, char** argv) // map a normal file as shared mem:
{
        int fd,i;
        people *p_map;
        char temp;
        
        fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
        lseek(fd,sizeof(people)*5-1,SEEK_SET);
        write(fd,"",1);
        
        p_map = (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 );
        close( fd );
        temp = 'a';
        for(i=0; i<10; i++)
        {
                temp += 1;
                memcpy( ( *(p_map+i) ).name, &temp,2 );
                ( *(p_map+i) ).age = 20+i;
        }
        printf(" initialize over /n ");
        sleep(10);

        munmap( p_map, sizeof(people)*10 );
        printf( "umap ok /n" );
}

/*-------------map_normalfile2.c-----------*/
#include <sys/mman.h>;
#include <sys/types.h>;
#include <fcntl.h>;
#include <unistd.h>;
typedef struct{
        char name[4];
        int  age;
}people;

main(int argc, char** argv)        // map a normal file as shared mem:
{
        int fd,i;
        people *p_map;
        fd=open( argv[1],O_CREAT|O_RDWR,00777 );
        p_map = (people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
        for(i = 0;i<10;i++)
        {
        printf( "name: %s age %d;/n",(*(p_map+i)).name, (*(p_map+i)).age );

        }
        munmap( p_map,sizeof(people)*10 );
}

map_normalfile1.c首先定義了一個people資料結構,(在這裡採用資料結構的方式是因為,共享記憶體區的資料往往是有固定格式的,這由通訊的各個程序決定,採用結構的方式有普遍代表性)。map_normfile1首先開啟或建立一個檔案,並把檔案的長度設定為5個people結構大小。然後從mmap()的返回地址開始,設定了10個people結構。然後,程序睡眠10秒鐘,等待其他程序對映同一個檔案,最後解除對映。

map_normfile2.c只是簡單的對映一個檔案,並以people資料結構的格式從mmap()返回的地址處讀取10個people結構,並輸出讀取的值,然後解除對映。

分別把兩個程式編譯成可執行檔案map_normalfile1和map_normalfile2後,在一個終端上先執行./map_normalfile2 /tmp/test_shm,程式輸出結果如下:

initialize over
umap ok

在map_normalfile1輸出initialize over 之後,輸出umap ok之前,在另一個終端上執行map_normalfile2 /tmp/test_shm,將會產生如下輸出(為了節省空間,輸出結果為稍作整理後的結果):

name: b        age 20;        name: c        age 21;        name: d        age 22;        name: e        age 23;        name: f        age 24;
name: g        age 25;        name: h        age 26;        name: I        age 27;        name: j        age 28;        name: k        age 29;

在map_normalfile1 輸出umap ok後,執行map_normalfile2則輸出如下結果:

name: b        age 20;        name: c        age 21;        name: d        age 22;        name: e        age 23;        name: f        age 24;
name:        age 0;        name:        age 0;        name:        age 0;        name:        age 0;        name:        age 0;

從程式的執行結果中可以得出的結論

1、 最終被對映檔案的內容的長度不會超過檔案本身的初始大小,即對映不能改變檔案的大小;

2、 可以用於程序通訊的有效地址空間大小大體上受限於被對映檔案的大小,但不完全受限於檔案大小。開啟檔案被截短為5個people結構大小,而在map_normalfile1中初始化了10個people資料結構,在恰當時候(map_normalfile1輸出initialize over 之後,輸出umap ok之前)呼叫map_normalfile2會發現map_normalfile2將輸出全部10個people結構的值,後面將給出詳細討論。
注:在linux中,記憶體的保護是以頁為基本單位的,即使被對映檔案只有一個位元組大小,核心也會為對映分配一個頁面大小的記憶體。當被對映檔案小於一個頁面大小時,程序可以對從mmap()返回地址開始的一個頁面大小進行訪問,而不會出錯;但是,如果對一個頁面以外的地址空間進行訪問,則導致錯誤發生,後面將進一步描述。因此,可用於程序間通訊的有效地址空間大小不會超過檔案大小及一個頁面大小的和。

3、 檔案一旦被對映後,呼叫mmap()的程序對返回地址的訪問是對某一記憶體區域的訪問,暫時脫離了磁碟上檔案的影響。所有對mmap()返回地址空間的操作只在記憶體中有意義,只有在呼叫了munmap()後或者msync()時,才把記憶體中的相應內容寫回磁碟檔案,所寫內容仍然不能超過檔案的大小。

範例2:父子程序通過匿名對映實現共享記憶體

#include <sys/mman.h>;
#include <sys/types.h>;
#include <fcntl.h>;
#include <unistd.h>;
typedef struct{
        char name[4];
        int  age;
}people;
main(int argc, char** argv)
{
        int i;
        people *p_map;
        char temp;
        p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
        if(fork() == 0)
        {
                sleep(2);
                for(i = 0;i<5;i++)
                        printf("child read: the %d people's age is %d/n",i+1,(*(p_map+i)).age);
                (*p_map).age = 100;
                munmap(p_map,sizeof(people)*10); //實際上,程序終止時,會自動解除對映。
                exit();
        }
        temp = 'a';
        for(i = 0;i<5;i++)
        {
                temp += 1;
                memcpy((*(p_map+i)).name, &temp,2);
                (*(p_map+i)).age=20+i;
        }

        sleep(5);
        printf( "parent read: the first people,s age is %d/n",(*p_map).age );
        printf("umap/n");
        munmap( p_map,sizeof(people)*10 );
        printf( "umap ok/n" );
}

考察程式的輸出結果,體會父子程序匿名共享記憶體:

child read: the 1 people's age is 20
child read: the 2 people's age is 21
child read: the 3 people's age is 22
child read: the 4 people's age is 23
child read: the 5 people's age is 24

parent read: the first people,s age is 100
umap
umap ok

四、對mmap()返回地址的訪問

前面對範例執行結構的討論中已經提到,linux採用的是頁式管理機制。對於用mmap()對映普通檔案來說,程序會在自己的地址空間新增一塊空間,空間大小由mmap()的len引數指定,注意,程序並不一定能夠對全部新增空間都能進行有效訪問。程序能夠訪問的有效地址大小取決於檔案被對映部分的大小。簡單的說,能夠容納檔案被對映部分大小的最少頁面個數決定了程序從mmap()返回的地址開始,能夠有效訪問的地址空間大小。超過這個空間大小,核心會根據超過的嚴重程度返回傳送不同的訊號給程序。可用如下圖示說明:





注意:檔案被對映部分而不是整個檔案決定了程序能夠訪問的空間大小,另外,如果指定檔案的偏移部分,一定要注意為頁面大小的整數倍。下面是對程序對映地址空間的訪問範例:

#include <sys/mman.h>;
#include <sys/types.h>;
#include <fcntl.h>;
#include <unistd.h>;
typedef struct{
        char name[4];
        int  age;
}people;

main(int argc, char** argv)
{
        int fd,i;
        int pagesize,offset;
        people *p_map;
        
        pagesize = sysconf(_SC_PAGESIZE);
        printf("pagesize is %d/n",pagesize);
        fd = open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
        lseek(fd,pagesize*2-100,SEEK_SET);
        write(fd,"",1);
        offset = 0;        //此處offset = 0編譯成版本1;offset = pagesize編譯成版本2
        p_map = (people*)mmap(NULL,pagesize*3,PROT_READ|PROT_WRITE,MAP_SHARED,fd,offset);
        close(fd);
        
        for(i = 1; i<10; i++)
        {
                (*(p_map+pagesize/sizeof(people)*i-2)).age = 100;
                printf("access page %d over/n",i);
                (*(p_map+pagesize/sizeof(people)*i-1)).age = 100;
                printf("access page %d edge over, now begin to access page %d/n",i, i+1);
                (*(p_map+pagesize/sizeof(people)*i)).age = 100;
                printf("access page %d over/n",i+1);
        }
        munmap(p_map,sizeof(people)*10);
}

如程式中所註釋的那樣,把程式編譯成兩個版本,兩個版本主要體現在檔案被對映部分的大小不同。檔案的大小介於一個頁面與兩個頁面之間(大小為:pagesize*2-99),版本1的被對映部分是整個檔案,版本2的檔案被對映部分是檔案大小減去一個頁面後的剩餘部分,不到一個頁面大小(大小為:pagesize-99)。程式中試圖訪問每一個頁面邊界,兩個版本都試圖在程序空間中對映pagesize*3的位元組數。

版本1的輸出結果如下:

pagesize is 4096
access page 1 over
access page 1 edge over, now begin to access page 2
access page 2 over
access page 2 over
access page 2 edge over, now begin to access page 3
Bus error                //被對映檔案在程序空間中覆蓋了兩個頁面,此時,程序試圖訪問第三個頁面

版本2的輸出結果如下:

pagesize is 4096
access page 1 over
access page 1 edge over, now begin to access page 2
Bus error                //被對映檔案在程序空間中覆蓋了一個頁面,此時,程序試圖訪問第二個頁面

結論:採用系統呼叫mmap()實現程序間通訊是很方便的,在應用層上介面非常簡潔。內部實現機制區涉及到了linux儲存管理以及檔案系統等方面的內容,可以參考一下相關重要資料結構來加深理解。在本專題的後面部分,將介紹系統v共享記憶體的實現。

在共享記憶體(上)中,主要圍繞著系統呼叫mmap()進行討論的,本部分將討論系統V共享記憶體,並通過實驗結果對比來闡述兩者的異同。系統V共享記憶體指的是把所有共享資料放在共享記憶體區域(IPC shared memory region),任何想要訪問該資料的程序都必須在本程序的地址空間新增一塊記憶體區域,用來對映存放共享資料的實體記憶體頁面。

系統呼叫mmap()通過對映一個普通檔案實現共享記憶體。系統V則是通過對映特殊檔案系統shm中的檔案實現程序間的共享記憶體通訊。也就是說,每個共享記憶體區域對應特殊檔案系統shm中的一個檔案(這是通過shmid_kernel結構聯絡起來的),後面還將闡述。

1、系統V共享記憶體原理

程序間需要共享的資料被放在一個叫做IPC共享記憶體區域的地方,所有需要訪問該共享區域的程序都要把該共享區域對映到本程序的地址空間中去。系統V共享記憶體通過shmget獲得或建立一個IPC共享記憶體區域,並返回相應的識別符號。核心在保證shmget獲得或建立一個共享記憶體區,初始化該共享記憶體區相應的shmid_kernel結構注同時,還將在特殊檔案系統shm中,建立並開啟一個同名檔案,並在記憶體中建立起該檔案的相應dentry及inode結構,新開啟的檔案不屬於任何一個程序(任何程序都可以訪問該共享記憶體區)。所有這一切都是系統呼叫shmget完成的。

注:每一個共享記憶體區都有一個控制結構struct shmid_kernel,shmid_kernel是共享記憶體區域中非常重要的一個數據結構,它是儲存管理和檔案系統結合起來的橋樑,定義如下:

struct shmid_kernel /* private to the kernel */
{        
        struct kern_ipc_perm        shm_perm;
        struct file *                shm_file;
        int                        id;
        unsigned long                shm_nattch;
        unsigned long                shm_segsz;
        time_t                        shm_atim;
        time_t                        shm_dtim;
        time_t                        shm_ctim;
        pid_t                        shm_cprid;
        pid_t                        shm_lprid;
};

該結構中最重要的一個域應該是shm_file,它儲存了將被對映檔案的地址。每個共享記憶體區物件都對應特殊檔案系統shm中的一個檔案,一般情況下,特殊檔案系統shm中的檔案是不能用read()、write()等方法訪問的,當採取共享記憶體的方式把其中的檔案對映到程序地址空間後,可直接採用訪問記憶體的方式對其訪問。

這裡我們採用[1]中的圖表給出與系統V共享記憶體相關資料結構:





正如訊息佇列和訊號燈一樣,核心通過資料結構struct ipc_ids shm_ids維護系統中的所有共享記憶體區域。上圖中的shm_ids.entries變數指向一個ipc_id結構陣列,而每個ipc_id結構陣列中有個指向kern_ipc_perm結構的指標。到這裡讀者應該很熟悉了,對於系統V共享記憶體區來說,kern_ipc_perm的宿主是shmid_kernel結構,shmid_kernel是用來描述一個共享記憶體區域的,這樣核心就能夠控制系統中所有的共享區域。同時,在shmid_kernel結構的file型別指標shm_file指向檔案系統shm中相應的檔案,這樣,共享記憶體區域就與shm檔案系統中的檔案對應起來。

在建立了一個共享記憶體區域後,還要將它對映到程序地址空間,系統呼叫shmat()完成此項功能。由於在呼叫shmget()時,已經建立了檔案系統shm中的一個同名檔案與共享記憶體區域相對應,因此,呼叫shmat()的過程相當於對映檔案系統shm中的同名檔案過程,原理與mmap()大同小異。

2、系統V共享記憶體API

對於系統V共享記憶體,主要有以下幾個API:shmget()、shmat()、shmdt()及shmctl()。

#include <sys/ipc.h>;
#include <sys/shm.h>;

shmget()用來獲得共享記憶體區域的ID,如果不存在指定的共享區域就建立相應的區域。shmat()把共享記憶體區域對映到呼叫程序的地址空間中去,這樣,程序就可以方便地對共享區域進行訪問操作。shmdt()呼叫用來解除程序對共享記憶體區域的對映。shmctl實現對共享記憶體區域的控制操作。這裡我們不對這些系統呼叫作具體的介紹,讀者可參考相應的手冊頁面,後面的範例中將給出它們的呼叫方法。

注:shmget的內部實現包含了許多重要的系統V共享記憶體機制;shmat在把共享記憶體區域對映到程序空間時,並不真正改變程序的頁表。當程序第一次訪問記憶體對映區域訪問時,會因為沒有物理頁表的分配而導致一個缺頁異常,然後核心再根據相應的儲存管理機制為共享記憶體對映區域分配相應的頁表。

3、系統V共享記憶體限制

在/proc/sys/kernel/目錄下,記錄著系統V共享記憶體的一下限制,如一個共享記憶體區的最大位元組數shmmax,系統範圍內最大共享記憶體區識別符號數shmmni等,可以手工對其調整,但不推薦這樣做。

在[2]中,給出了這些限制的測試方法,不再贅述。

4、系統V共享記憶體範例

本部分將給出系統V共享記憶體API的使用方法,並對比分析系統V共享記憶體機制與mmap()對映普通檔案實現共享記憶體之間的差異,首先給出兩個程序通過系統V共享記憶體通訊的範例:

/***** testwrite.c *******/
#include <sys/ipc.h>;
#include <sys/shm.h>;
#include <sys/types.h>;
#include <unistd.h>;
typedef struct{
        char name[4];
        int age;
} people;
main(int argc, char** argv)
{
        int shm_id,i;
        key_t key;
        char temp;
        people *p_map;
        char* name = "/dev/shm/myshm2";
        key = ftok(name,0);
        if(key==-1)
                perror("ftok error");
        shm_id=shmget(key,4096,IPC_CREAT);        
        if(shm_id==-1)
        {
                perror("shmget error");
                return;
        }
        p_map=(people*)shmat(shm_id,NULL,0);
        temp='a';
        for(i = 0;i<10;i++)
        {
                temp+=1;
                memcpy((*(p_map+i)).name,&temp,1);
                (*(p_map+i)).age=20+i;
        }
        if(shmdt(p_map)==-1)
                perror(" detach error ");
}
/********** testread.c ************/
#include <sys/ipc.h>;
#include <sys/shm.h>;
#include <sys/types.h>;
#include <unistd.h>;
typedef struct{
        char name[4];
        int age;
} people;
main(int argc, char** argv)
{
        int shm_id,i;
        key_t key;
        people *p_map;
        char* name = "/dev/shm/myshm2";
        key = ftok(name,0);
        if(key == -1)
                perror("ftok error");
        shm_id = shmget(key,4096,IPC_CREAT);        
        if(shm_id == -1)
        {
                perror("shmget error");
                return;
        }
        p_map = (people*)shmat(shm_id,NULL,0);
        for(i = 0;i<10;i++)
        {
        printf( "name:%s/n",(*(p_map+i)).name );
        printf( "age %d/n",(*(p_map+i)).age );
        }
        if(shmdt(p_map) == -1)
                perror(" detach error ");
}

testwrite.c建立一個系統V共享記憶體區,並在其中寫入格式化資料;testread.c訪問同一個系統V共享記憶體區,讀出其中的格式化資料。分別把兩個程式編譯為testwrite及testread,先後執行./testwrite及./testread 則./testread輸出結果如下:

name: b        age 20;        name: c        age 21;        name: d        age 22;        name: e        age 23;        name: f        age 24;
name: g        age 25;        name: h        age 26;        name: I        age 27;        name: j        age 28;        name: k        age 29;

通過對試驗結果分析,對比系統V與mmap()對映普通檔案實現共享記憶體通訊,可以得出如下結論:

1、 系統V共享記憶體中的資料,從來不寫入到實際磁碟檔案中去;而通過mmap()對映普通檔案實現的共享記憶體通訊可以指定何時將資料寫入磁碟檔案中。注:前面講到,系統V共享記憶體機制實際是通過對映特殊檔案系統shm中的檔案實現的,檔案系統shm的安裝點在交換分割槽上,系統重新引導後,所有的內容都丟失。

2、 系統V共享記憶體是隨核心持續的,即使所有訪問共享記憶體的程序都已經正常終止,共享記憶體區仍然存在(除非顯式刪除共享記憶體),在核心重新引導之前,對該共享記憶體區域的任何改寫操作都將一直保留。

3、 通過呼叫mmap()對映普通檔案進行程序間通訊時,一定要注意考慮程序何時終止對通訊的影響。而通過系統V共享記憶體實現通訊的程序則不然。注:這裡沒有給出shmctl的使用範例,原理與訊息佇列大同小異。

結論:

共享記憶體允許兩個或多個程序共享一給定的儲存區,因為資料不需要來回複製,所以是最快的一種程序間通訊機制。共享記憶體可以通過mmap()對映普通檔案(特殊情況下還可以採用匿名對映)機制實現,也可以通過系統V共享記憶體機制實現。應用介面和原理很簡單,內部機制複雜。為了實現更安全通訊,往往還與訊號燈等同步機制共同使用。

共享記憶體涉及到了儲存管理以及檔案系統等方面的知識,深入理解其內部機制有一定的難度,關鍵還要緊緊抓住核心使用的重要資料結構。系統V共享記憶體是以檔案的形式組織在特殊檔案系統shm中的。通過shmget可以建立或獲得共享記憶體的識別符號。取得共享記憶體識別符號後,要通過shmat將這個記憶體區對映到本程序的虛擬地址空間。

--------------------- 本文來自 hai0808 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/liu0808/article/details/52967167?utm_source=copy