linux 進程通信之內存共享

共享內存是系統出於多個進程之間通訊的考慮,而預留的的一塊內存區。在/proc/sys/kernel/目錄下,記錄着共享內存的一些限制,如一個共享內存區的最大字節數shmmax,系統範圍內最大共享內存區標識符數shmmni等,可以手工對其調整,但不推薦這樣做。

一、應用

共享內存的使用,主要有以下幾個API:ftok()、shmget()、shmat()、shmdt()及shmctl()。

1)用ftok()函數獲得一個ID號.

應用說明:
在IPC中,我們經常用用key_t的值來創建或者打開信號量,共享內存和消息隊列。

函數原型:
key_t ftok(const char *pathname, int proj_id);

Keys:
1)pathname一定要在系統中存在並且進程能夠訪問的
3)proj_id是一個1-255之間的一個整數值,典型的值是一個ASCII值。
當成功執行的時候,一個key_t值將會被返回,否則-1被返回。我們可以使用strerror(errno)來確定具體的錯誤信息。

考慮到應用系統可能在不同的主機上應用,可以直接定義一個key,而不用ftok獲得:
#define IPCKEY 0x344378

2)shmget()用來開闢/指向一塊共享內存的函數

應用說明:
shmget()用來獲得共享內存區域的ID,如果不存在指定的共享區域就創建相應的區域。

函數原型:
int shmget(key_t key, size_t size, int shmflg);

key_t key 是這塊共享內存的標識符。如果是父子關係的進程間通信的話,這個標識符用IPC_PRIVATE來代替。如果兩個進程沒有任何關係,所以就用ftok()算出來一個標識符(或者自己定義一個)使用了。

int size 是這塊內存的大小.
int flag 是這塊內存的模式(mode)以及權限標識。
模式可取如下值:        
IPC_CREAT 新建(如果已創建則返回目前共享內存的id)
IPC_EXCL   與IPC_CREAT結合使用,如果已創建則則返回錯誤
然後將“模式” 和“權限標識”進行“或”運算,做爲第三個參數。
如:    IPC_CREAT | IPC_EXCL | 0640   
例子中的0666爲權限標識,4/2/1 分別表示讀/寫/執行3種權限,第一個0是UID,第一個6(4+2)表示擁有者的權限,第二個4表示同組權限,第3個0表示他人的權限。
這個函數成功時返回共享內存的ID,失敗時返回-1。

關於這個函數,要多說兩句。
創建共享內存時,shmflg參數至少需要 IPC_CREAT | 權限標識,如果只有IPC_CREAT 則申請的地址都是k=0xffffffff,不能使用;
獲取已創建的共享內存時,shmflg不要用IPC_CREAT(只能用創建共享內存時的權限標識,如0640),否則在某些情況下,比如用ipcrm刪除共享內存後,用該函數並用IPC_CREAT參數獲取一次共享內存(當然,獲取失敗),則即使再次創建共享內存也不能成功,此時必須更改key來重建共享內存。

3) shmat()將這個內存區映射到本進程的虛擬地址空間。

函數原型:
void    *shmat( int shmid , char *shmaddr , int shmflag );

shmat()是用來允許本進程訪問一塊共享內存的函數。
int shmid是那塊共享內存的ID。
char *shmaddr是共享內存的起始地址,如果shmaddr爲0,內核會把共享內存映像到調用進程的地址空間中選定位置;如果shmaddr不爲0,內核會把共享內存映像到shmaddr指定的位置。所以一般把shmaddr設爲0。
int shmflag是本進程對該內存的操作模式。如果是SHM_RDONLY的話,就是隻讀模式。其它的是讀寫模式
成功時,這個函數返回共享內存的起始地址。失敗時返回-1。

4) shmdt()函數刪除本進程對這塊內存的使用,shmdt()與shmat()相反,是用來禁止本進程訪問一塊共享內存的函數。

函數原型:
int shmdt( char *shmaddr );
參數char *shmaddr是那塊共享內存的起始地址。
成功時返回0。失敗時返回-1。

5) shmctl() 控制對這塊共享內存的使用

函數原型:
int     shmctl( int shmid , int cmd , struct shmid_ds *buf );
int shmid是共享內存的ID。
int cmd是控制命令,可取值如下:
        IPC_STAT        得到共享內存的狀態
        IPC_SET         改變共享內存的狀態
        IPC_RMID        刪除共享內存
struct shmid_ds *buf是一個結構體指針。IPC_STAT的時候,取得的狀態放在這個結構體中。如果要改變共享內存的狀態,用這個結構體指定。
返回值:        成功:0
                失敗:-1

示例程序:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#define IPCKEY 0x366378

typedef struct{
char agen[10];
unsigned char file_no;
} st_setting;

int main(int argc, char** argv)

    int shm_id;
    key_t key;
    st_setting *p_setting;
    
    //首先檢查共享內存是否存在,存在則先刪除
    shm_id = shmget(IPCKEY ,1028,0640);     
    if(shm_id != -1)
    {
        p_setting = (st_setting*)shmat(shm_id,NULL,0);
      if ( p_setting != (void *)-1)
      {
      shmdt(p_setting);
          shmctl(shm_id,IPC_RMID,0) ;
      }
    }
        
    shm_id=shmget(IPCKEY,1028,0640|IPC_CREAT|IPC_EXCL); 
    if(shm_id==-1)
    {
        printf("shmget error\n");
        return -1;
    }
    //將這塊共享內存區附加到自己的內存段
    p_setting=(st_setting*)shmat(shm_id,NULL,0);
    
    strncpy(p_setting->agen,"jinyh",10); 
    printf( "agen:%s\n",p_setting->agen );
    
    p_setting->file_no = 1;
    printf( "file_no:%d\n",p_setting->file_no );
    
    system("ipcs -m");//此時可看到有進程關聯到共享內存的信息,nattch爲1
    
    //將這塊共享內存區從自己的內存段刪除出去
    if(shmdt(p_setting) == -1)
       perror(" detach error ");
    
    system("ipcs -m");//此時可看到有進程關聯到共享內存的信息,nattch爲0
    
    //刪除共享內存
    if (shmctl( shm_id , IPC_RMID , NULL ) == -1)
      perror(" delete error ");
      
     //exit(0);
      
}

注意:在使用共享內存,結束程序退出後。如果你沒在程序中用shmctl()刪除共享內存的話,一定要在命令行下用ipcrm命令刪除這塊共享內存。你要是不管的話,它就一直在那兒放着了。
簡單解釋一下ipcs命令和ipcrm命令。

取得ipc信息:
ipcs [-m|-q|-s]
-m      輸出有關共享內存(shared memory)的信息
-q      輸出有關信息隊列(message queue)的信息
-s      輸出有關“遮斷器”(semaphore)的信息
%ipcs -m

刪除ipc
ipcrm -m|-q|-s shm_id
%ipcrm -m 105


二、陷阱(參考http://www.ibm.com/developerworks/cn/aix/library/au-cn-sharemem/

1)ftok陷阱

採用ftok來生成key的情況下,如果ftok的參數pathname指定文件被刪除後重建,則文件系統會賦予這個同名文件(或目錄)新的i節點信息,於是這些進程所調用的ftok雖然都能正常返回,但得到的鍵值卻並不能保證相同。

2)3. AIX中shmat的問題

AIX系統中,System V各類進程間通信機制在使用中均存在限制。區別於其它UNIX操作系統對IPC機制的資源配置方式,AIX使用了不同的方法;在AIX中定義了 IPC 機制的上限, 且是不可配置的。就共享內存機制而言,在4.2.1及以上版本的AIX系統上,存在下列限制:

對於64位進程,同一進程可連接最多268435456個共享內存段; 
對於32位進程,同一進程可連接最多11個共享內存段,除非使用擴展的shmat; 
上述限制對於64位應用不會帶來麻煩,因爲可供連接的數量已經足夠大了;但對於32位應用,卻很容易帶來意外的問題,因爲最大的連接數量只有11個。

下面的例程test02.c演示了這個問題,爲了精簡代碼,它反覆連接的是同一個共享內存對象;實際上,無論所連接的共享內存對象是否相同,該限制制約的是連接次數:

#include <stdio.h>
#include <errno.h> 
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define MAX_ATTACH_NUM 15

void main(int argc, char* argv[])
{
    key_t       mem_key;
    long        mem_id;
    void*       mem_addr[MAX_ATTACH_NUM];
    int          i;
    if ( ( mem_key = ftok("/tmp/mykeyfile", 1) ) == (key_t)(-1) ) {
            printf("Failed to generate shared memory access key, ERRNO=%d\n",
    errno);
            goto MOD_EXIT;
    }
    if ( ( mem_id = shmget(mem_key, 256, IPC_CREAT) ) == (-1) ) {
            printf("Failed to obtain shared memory ID, ERRNO=%d\n", errno);
            goto MOD_EXIT;
    }
    for ( i=1; i<=MAX_ATTACH_NUM; i++ ) {
   if ( ( mem_addr[i] = (void *)shmat(mem_id, 0, 0) ) == (void *)(-1) )
    printf("Failed to attach shared memory, times [%02d], errno:%d\n", i,
    errno);
   else
    printf("Successfully attached shared memory, times [%02d]\n", i);
    }
MOD_EXIT:
    shmctl(mem_id, IPC_RMID, NULL);
}

在AIX系統上,我們將其編譯爲test02,並運行,可以看到如下輸出:

Successfully attached shared memory, times [01] 
Successfully attached shared memory, times [02] 
Successfully attached shared memory, times [03] 
Successfully attached shared memory, times [04] 
Successfully attached shared memory, times [05] 
Successfully attached shared memory, times [06] 
Successfully attached shared memory, times [07] 
Successfully attached shared memory, times [08] 
Successfully attached shared memory, times [09] 
Successfully attached shared memory, times [10] 
Successfully attached shared memory, times [11] 
Failed to attach shared memory, times [12], errno:24
Failed to attach shared memory, times [13], errno:24
Failed to attach shared memory, times [14], errno:24
Failed to attach shared memory, times [15], errno:24


說明超出11個連接之後,所有後續的共享內存連接都將無法建立。錯誤碼24的定義是EMFILE,AIX給予的解釋是:

The number of shared memory segments attached to the calling process exceeds the system-imposed limit。

解決這個問題的方法是,使用擴展的shmat;具體而言就是,在運行相關應用之前(確切地說,是在共享內存被創建之前),首先在shell中設置EXTSHM環境變量,通過它擴展shmat,對於源代碼本身無需作任何修改:

   export EXTSHM=ON

值得注意的是,雖然設置環境變量,在程序中也可通過setenv函數來做到,比如在程序的開始,加入下列代碼:

   setenv("EXTSHM", "ON", 1);

但實踐證明這樣的方法在解決這個問題上是無效的;也就是說唯一可行的辦法,就是在shell中設置EXTSHM環境變量,而非在程序中。

在AIX上配置32位DB2實例時,也要求確保將環境變量 EXTSHM 設爲 ON,這是運行 Warehouse Manager 和 Query Patroller 之前必需的操作:
export EXTSHM=ON
db2set DB2ENVLIST=EXTSHM
db2start
其原因即來自我們剛剛介紹的AIX中32位應用連接共享內存時,存在最大連接數限制。這個問題同樣普遍存在於AIX平臺上Oracle等軟件產品中。

3)HP-UX中shmget和shmat的問題

3.1 32位和64位應用兼容問題

在HP-UX平臺上,如果同時運行32位應用和64位應用,而且它們訪問的是一個相同的共享內存區,則會遇到兼容性問題。

在HP-UX中,應用程序設置IPC_CREAT標誌調用shmget,所創建的共享內存區,只可被同類型的應用所訪問;即32位應用程序所創建的共享內存區只可被其它的32位應用程序訪問,同樣地,64位應用程序所創建的共享內存區只可被其它的64位應用程序訪問。

如果,32位應用企圖訪問一個由64位應用創建的共享內存區,則會在調用shmget時失敗,得到EINVAL錯誤碼,其解釋是:

A shared memory identifier exists for key but is in 64-bit address space and the process performing the request has been compiled as a 32-bit executable.

解決這一問題的方法是,當64位應用創建共享內存時,合併IPC_CREAT標誌,同時給定IPC_SHARE32標誌:

shmget(mem_key, size, 0666 | IPC_CREAT | IPC_SHARE32)


對於32位應用,沒有設定IPC_SHARE32標誌的要求,但設置該標誌並不會帶來任何問題,也就是說無論應用程序將被編譯爲32位還是64位模式,都可採用如上相同的代碼;並且由此解決32位應用和64位應用在共享內存訪問上的兼容性問題。

3.2 對同一共享內存的連接數限制

在HP-UX上,應用進程對同一個共享內存區的連接次數被限制爲最多1次;區別於上面第3節所介紹的AIX上的連接數限制,HP-UX並未對指向不同共享內存區的連接數設置上限,也就是說,運行在HP-UX上的應用進程可以同時連接很多個不同的共享內存區,但對於同一個共享內存區,最多隻允許連接1次;否則,shmat調用將失敗,返回錯誤碼EINVAL,在shmat的man幫助中,對該錯誤碼有下列解釋:

shmid is not a valid shared memory identifier, (possibly because the shared memory segment was already removed using shmctl(2) with IPC_RMID), or the calling process is already attached to shmid.

這個限制會對多線程應用帶來無法避免的問題,只要一個應用進程中有超過1個以上的線程企圖連接同一個共享內存區,則都將以失敗而告終。

解決這個問題,需要修改應用程序設計,使應用進程具備對同一共享內存的多線程訪問能力。相對於前述問題的解決方法,解決這個問題的方法要複雜一些。

作爲可供參考的方法之一,以下介紹的邏輯可以很好地解決這個問題:

基本思路是,對於每一個共享內存區,應用進程首次連接上之後,將其鍵值(ftok的返回值)、系統標識符(shmid,shmget調用的返回值)和訪問地址(即shmat調用的返回值)保存下來,以這個進程的全局數組或者鏈表的形式留下記錄。在任何對共享內存的連接操作之前,程序都將先行檢索這個記錄列表,根據鍵值和標誌符去匹配希望訪問的共享內存,如果找到匹配記錄,則從記錄中直接讀取訪問地址,而無需再次調用shmat函數,從而解決這一問題;如果沒有找到匹配目標,則調用shmat建立連接,並且爲新連接上來的共享內存添加一個新記錄。

記錄條目的數據結構,可定義爲如下形式:


typedef struct _Shared_Memory_Record
{
key_t   mem_key;   // key generated by ftok()   
int    mem_id;    // id returned by shmget()   
void*   mem_addr;   // access address returned by shmat() 
int    nattach;    // times of attachment    
} Shared_

4)Solaris中的shmdt函數原型問題

Solaris系統中的shmdt調用,在原型上與System V標準有所不同,

    Default
     int shmdt(char *shmaddr);

即形參shmaddr的數據類型在Solaris上是char *,而System V定義的是void * 類型;實際上Solaris上shmdt調用遵循的函數原型規範是SVID-v4之前的標準;以Linux系統爲例,libc4和libc5 採用的是char * 類型的形參,而遵循SVID-v4及後續標準的glibc2及其更新版本,均改爲採用void * 類型的形參。

如果仍在代碼中採用System V的標準原型,就會在Solaris上編譯代碼時造成編譯錯誤;比如:

Error: Formal argument 1 of type char* in call to shmdt(char*) 
is being passed void*.

解決方法是,引入一個條件編譯宏,在編譯平臺是Solaris時,採用char * 類型的形參,而對其它平臺,均仍採用System V標準的void * 類型形參,比如:

#ifdef _SOLARIS_SHARED_MEMORY         
shmdt((char *)mem_addr); 
#else                 
shmdt((void *)mem_addr); 
#endif

5)通過shmctl刪除共享內存的風險

如果共享內存已經與所有訪問它的進程斷開了連接,則調用IPC_RMID子命令後,系統將立即刪除共享內存的標識符,並刪除該共享內存區,以及所有相關的數據結構; 
如果仍有別的進程與該共享內存保持連接,則調用IPC_RMID子命令後,該共享內存並不會被立即從系統中刪除,而是被設置爲IPC_PRIVATE狀態,並被標記爲"已被刪除";直到已有連接全部斷開,該共享內存纔會最終從系統中消失。

需要說明的是:一旦通過shmctl對共享內存進行了刪除操作,則該共享內存將不能再接受任何新的連接,即使它依然存在於系統中!所以,可以確知,在對共享內存刪除之後不可能再有新的連接,則執行刪除操作是安全的;否則,在刪除操作之後如仍有新的連接發生,則這些連接都將失敗!

 
 
 
 

共享內存(Shared Memory)

  共享內存區域是被多個進程共享的一部分物理內存。如果多個進程都把該內存區域映射到自己的虛擬地址空間,則這些進程就都可以直接訪問該共享內存區域,從而 可以通過該區域進行通信。共享內存是進程間共享數據的一種最快的方法,一個進程向共享內存區域寫入了數據,共享這個內存區域的所有進程就可以立刻看到其中 的內容。這塊共享虛擬內存的頁面,出現在每一個共享該頁面的進程的頁表中。但是它不需要在所有進程的虛擬內存中都有相同的虛擬地址。

  
                  圖 共享內存映射圖

  象所有的 System V IPC對象一樣,對於共享內存對象的訪問由key控制,並要進行訪問權限檢查。內存共享之後,對進程如何使用這塊內存就不再做檢查。它們必須依賴於其它機 制,比如System V的信號燈來同步對於共享內存區域的訪問。

  每一個新創建的共享內存對象都用一個shmid_kernel數據結構來表達。系統中所有的shmid_kernel數據結構都保存在shm_segs向 量表中,該向量表的每一個元素都是一個指向shmid_kernel數據結構的指針。shm_segs向量表的定義如下:

  struct shmid_kernel *shm_segs[SHMMNI];

  SHMMNI爲128,表示系統中最多可以有128個共享內存對象。

  數據結構shmid_kernel的定義如下:

  struct shmid_kernel

  {

  struct shmid_ds u;         /* the following are private */

  unsigned long shm_npages;  /* size of segment (pages) */

  unsigned long *shm_pages;  /* array of ptrs to frames -> SHMMAX */

  struct vm_area_struct *attaches;  /* descriptors for attaches */

  };

  其中:shm_pages是該共享內存對象的頁表,每個共享內存對象一個,它描述瞭如何把該共享內存區域映射到進程的地址空間的信息。

  shm_npages是該共享內存區域的大小,以頁爲單位。

  shmid_ds是一個數據結構,它描述了這個共享內存區的認證信息,字節大小,最後一次粘附時間、分離時間、改變時間,創建該共享區域的進程,最後一次 對它操作的進程,當前有多少個進程在使用它等信息。其定義如下:

  struct shmid_ds {

  struct ipc_perm shm_perm;   /* operation perms */

  int shm_segsz;              /* size of segment (bytes) */

  __kernel_time_t shm_atime;  /* last attach time */

  __kernel_time_t shm_dtime;  /* last detach time */

  __kernel_time_t shm_ctime;  /* last change time */

  __kernel_ipc_pid_t shm_cpid; /* pid of creator */

  __kernel_ipc_pid_t shm_lpid; /* pid of last operator */

  unsigned short shm_nattch;   /* no. of current attaches */

  unsigned short shm_unused;   /* compatibility */

  void *shm_unused2;           /* ditto - used by DIPC */

  void *shm_unused3;           /* unused */

  };

  attaches描述被共享的物理內存對象所映射的各進程的虛擬內存區域。每一個希望共享這塊內存的進程都必須通過系統調用將其粘附(attach)到它 的虛擬內存中。這一過程將爲該進程創建了一個新的描述這塊共享內存的vm_area_struct數據結構。進程可以選擇共享內存在它的虛擬地址空間的位 置,也可以讓Linux爲它選擇一塊足夠的空閒區域。

  這個新的vm_area_struct結構除了要連接到進程的內存結構mm中以外,還被連接到共享內存數據結構shmid_kernel的一個鏈表中,該 結構中的attaches指針指向該鏈表。vm_area_struct數據結構中專門提供了兩個指針:vm_next_shared和 vm_prev_shared,用於連接該共享區域在使用它的各進程中所對應的vm_area_struct數據結構。其實,虛擬內存並沒有在粘附的時候 創建,而要等到第一個進程試圖訪問它的時候才創建。

  
              圖 System V IPC 機制 - 共享內存

 

Linux爲共享內存提供了四種操作。

  1. 共享內存對象的創建或獲得。與其它兩種IPC機制一樣,進程在使用共享內存區域以前,必須通過系統調用sys_ipc (call值爲SHMGET)創建一個鍵值爲key的共享內存對象,或獲得已經存在的鍵值爲key的某共享內存對象的引用標識符。以後對共享內存對象的訪 問都通過該引用標識符進行。對共享內存對象的創建或獲得由函數sys_shmget完成,其定義如下:

  int sys_shmget (key_t key, int size, int shmflg)

  這裏key是表示該共享內存對象的鍵值,size是該共享內存區域的大小(以字節爲單位),shmflg是標誌(對該共享內存對象的特殊要求)。

  它所做的工作如下:

  1) 如果key == IPC_PRIVATE,則創建一個新的共享內存對象。

  * 算出size對應的頁數,檢查其合法性。

  * 搜索向量表shm_segs,爲新創建的共享內存對象找一個空位置。

  * 申請一塊內存用於建立shmid_kernel數據結構,注意這裏申請的內存區域大小不包括真正的共享內存區,實際上,要等到第一個進程試圖訪問它的時候 才真正創建共享內存區。

  * 根據該共享內存區所佔用的頁數,爲其申請一塊空間用於建立頁表(每頁4個字節),將頁表清0。

  * 填寫shmid_kernel數據結構,將其加入到向量表shm_segs中爲其找到的空位置。

  * 返回該共享內存對象的引用標識符。

  2) 在向量表shm_segs中查找鍵值爲key的共享內存對象,結果有三:

  * 如果沒有找到,而且在操作標誌shmflg中沒有指明要創建新共享內存,則錯誤返回,否則創建一個新的共享內存對象。

  * 如果找到了,但該次操作要求必須創建一個鍵值爲key的新對象,那麼錯誤返回。

  * 否則,合法性、認證檢查,如有錯,則錯誤返回;否則,返回該內存對象的引用標識符。

  共享內存對象的創建者可以控制對於這塊內存的訪問權限和它的key是公開還是私有。如果有足夠的權限,它也可以把共享內存鎖定在物理內存中。

  參見include/linux/shm.h

  2. 粘附。在創建或獲得某個共享內存區域的引用標識符後,還必須將共享內存區域映射(粘附)到進程的虛擬地址空間,然後才能使用該共享內存區域。系統調用 sys_ipc(call值爲SHMAT)用於共享內存區到進程虛擬地址空間的映射,而真正完成粘附動作的是函數sys_shmat,其定義如下:

  int sys_shmat (int shmid, char *shmaddr, int shmflg, ulong *raddr)

  其中:shmid是共享內存對象的引用標識符;

  shmaddr該共享內存區域在進程的虛擬地址空間對應的虛擬地址;

  shmflg是映射標誌;

  raddr是實際映射的虛擬空間地址。

  該函數所做的工作如下:

  1) 根據shmid找到共享內存對象。

  2) 如果shmaddr爲0,即用戶沒有指定該共享內存區域在它的虛擬空間中的位置,則由系統在進程的虛擬地址空間中爲其找一塊區域(從1G開始);否則,就 用shmaddr作爲映射的虛擬地址。

  3) 檢查虛擬地址的合法性(不能超過進程的最大虛擬空間大小—3G,不能太接近堆棧棧頂)。

  4) 認證檢查。

  5) 申請一塊內存用於建立數據結構vm_area_struct,填寫該結構。

  6) 檢查該內存區域,將其加入到進程的mm結構和該共享內存對象的vm_area_struct隊列中。

  共享內存的粘附只是創建一個vm_area_struct數據結構,並將其加入到相應的隊列中,此時並沒有創建真正的共享內存頁。

  當進程第一次訪問共享虛擬內存的某頁時,因爲所有的共享內存頁還都沒有分配,所以會發生一個page fault異常。當Linux處理這個page fault的時候,它找到發生異常的虛擬地址所在的vm_area_struct數據結構。在該數據結構中包含有這類共享虛擬內存的一組處理例程,其中的 nopage操作用來處理虛擬頁對應的物理頁不存在的情況。對共享內存,該操作是shm_nopage(定義在ipc/shm.c中)。該操作在描述這個 共享內存的shmid_kernel數據結構的頁表shm_pages中查找發生page fault異常的虛擬地址所對應的頁表條目,看共享頁是否存在(頁表條目爲0,表示共享頁是第一次使用)。如果不存在,它就分配一個物理頁,併爲它創建一 個頁表條目。這個條目不但進入當前進程的頁表,同時也存到shmid_kernel數據結構的頁表shm_pages中。

  當下一個進程試圖訪問這塊內存並得到一個page fault的時候,經過同樣的路徑,也會走到函數shm_nopage。此時,該函數查看shmid_kernel數據結構的頁表shm_pages時, 發現共享頁已經存在,它只需把這裏的頁表項填到進程頁表的相應位置即可,而不需要重新創建物理頁。所以,是第一個訪問共享內存頁的進程使得這一頁被創建, 而隨後訪問它的其它進程僅把此頁加到它們的虛擬地址空間。

  3. 分離。當進程不再需要共享虛擬內存的時候,它們與之分離(detach)。只要仍舊有其它進程在使用這塊內存,這種分離就只會影響當前的進程,而不會影響 其它進程。當前進程的vm_area_struct數據結構被從shmid_ds中刪除,並被釋放。當前進程的頁表也被更新,共享內存對應的虛擬內存頁被 標記爲無效。當共享這塊內存的最後一個進程與之分離時,共享內存頁被釋放,同時,這塊共享內存的shmid_kernel數據結構也被釋放。

  系統調用sys_ipc(call值爲SHMDT)用於共享內存區與進程虛擬地址空間的分離,而真正完成分離動作的是函數sys_shmdt,其定義如 下:

  int sys_shmdt (char *shmaddr)

  其中shmaddr是進程要分離的共享頁的開始虛擬地址。

  該函數搜索進程的內存結構中的所有vm_area_struct數據結構,找到地址shmaddr對應的一個,調用函數do_munmap將其釋放。

  在函數do_munmap中,將要釋放的vm_area_struct數據結構從進程的虛擬內存中摘下,清除它在進程頁表中對應的頁表項(可能佔多個頁表 項),調用該共享內存數據結構vm_area_struct的操作例程中的close操作(此處爲shm_close)做進一步的處理。

  在函數shm_close(定義在ipc/shm.c中)中,找到該共享內存對象在向量表shm_segs中的索引,從而找到該共享內存對象,將該共享內 存在當前進程中對應的vm_area_struct數據結構從對象的共享內存區域鏈表(由vm_next_share和vm_pprev_share指針 連接)中摘下。如果目前與該共享內存對象粘附的進程數變成了0,則釋放共享內存頁,釋放共享內存頁表,釋放該對象的shmid_kernel數據結構,將 向量表shm_segs中該共享內存對象所佔用的項改爲IPC_UNUSED。

 

 

如果共享的虛擬內存沒有被鎖定在物理內存中,分離會更加複雜。因爲在這種情況下,共享內存的頁可能在系統大量使用內存的時候被交換到系統的交換磁盤。爲了 避免這種情況,可以通過下面的控制操作,將某共享內存頁鎖定在物理內存不允許向外交換。共享內存的換出和換入,已在第3章中討論。

  4. 控制。Linux在共享內存上實現的第四種操作是共享內存的控制(call值爲SHMCTL的sys_ipc調用),它由函數sys_shmctl實現。 控制操作包括獲得共享內存對象的狀態,設置共享內存對象的參數(如uid、gid、mode、ctime等),將共享內存對象在內存中鎖定和釋放(在對象 的mode上增加或去除SHM_LOCKED標誌),釋放共享內存對象資源等。

  共享內存提供了一種快速靈活的機制,它允許進程之間直接共享大量的數據,而無須使用拷貝或系統調用。共享內存的主要侷限性是它不能提供同步,如果兩個進程 企圖修改相同的共享內存區域,由於內核不能串行化這些動作,因此寫的數據可能任意地互相混合。所以使用共享內存的進程必須設計它們自己的同步協議,如用信 號燈等。

  以下是使用共享內存機制進行進程間通信的基本操作:

  需要包含的頭文件:

  #include <sys/types.h>

  #include <sys/ipc.h>

  #include <sys/shm.h>

  1.創建共享內存:

  int shmget(key_t key,int size,int shmflg);

  參數說明:

  key:用來表示新建或者已經存在的共享內存去的關鍵字。

  size:創建共享內存的大小。

  shmflg:可以指定的特殊標誌。IPC_CREATE,IPC_EXCL以及低九位的權限。

  eg:

  int shmid;

  shmid=shmget(IPC_PRIVATE,4096,IPC_CREATE|IPC_EXCL|0660);

  if(shmid==-1)

  perror("shmget()");

  2.連接共享內存

  char *shmat(int shmid,char *shmaddr,int shmflg);

  參數說明:

  shmid:共享內存的關鍵字

  shmaddr:指定共享內存出現在進程內存地址的什麼位置,通常我們讓內核自己決定一個合適的地址位置,用的時候設爲0。

  shmflg:制定特殊的標誌位。

  eg:

  int shmid;

  char *shmp;

  shmp=shmat(shmid,0,0);

  if(shmp==(char *)(-1))

  perror("shmat()\n");

  3.使用共享內存

  在使用共享內存是需要注意的是,爲防止內存訪問衝突,我們一般與信號量結合使用。

  4.分離共享內存:當程序不再需要共享內後,我們需要將共享內存分離以便對其進行釋放,分離共享內存的函數原形如下:

  int shmdt(char *shmaddr);

  5.

  釋放共享內存

  int shmctl(int shmid,int cmd,struct shmid_ds *buf);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章