unix/linux共享內存應用

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

1、應用 數組

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

1)用ftok()函數得到一個ID號. 數據結構

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

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

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

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

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


2、陷阱(參考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對共享內存進行了刪除操做,則該共享內存將不能再接受任何新的鏈接,即便它依然存在於系統中!因此,能夠確知, 在對共享內存刪除以後不可能再有新的鏈接,則執行刪除操做是安全的;不然,在刪除操做以後如仍有新的鏈接發生,則這些鏈接都將失敗!

相關文章
相關標籤/搜索