unix/linux共用記憶體應用與陷阱

來源:互聯網
上載者:User

 

共用記憶體是系統出於多個進程之間通訊的考慮,而預留的的一塊記憶體區。在/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對共用記憶體進行了刪除操作,則該共用記憶體將不能再接受任何新的串連,即使它依然存在於系統中!所以,可以確知,在對共用記憶體刪除之後不可能再有新的串連,則執行刪除操作是安全的;否則,在刪除操作之後如仍有新的串連發生,則這些串連都將失敗!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.