1. 程式人生 > >linux 共享記憶體(shmget,shmat,shmdt,shmctl)解析

linux 共享記憶體(shmget,shmat,shmdt,shmctl)解析

shmget

int shmget(key_t key, size_t size, int shmflg);
key:     識別符號的規則
size:    共享儲存段的位元組數
flag:    讀寫的許可權還有IPC_CREAT或IPC_EXCL對應檔案的O_CREAT或O_EXCL
返回值:  成功返回共享儲存的id,失敗返回-1

key_t key
-----------------------------------------------
    key標識共享記憶體的鍵值: 0/IPC_PRIVATE。 當key的取值為IPC_PRIVATE,則函式shmget()將建立一塊新的共享記憶體;如果key的取值為0,而引數shmflg中設定了IPC_PRIVATE這個標誌,則同樣將建立一塊新的共享記憶體。
    在IPC的通訊模式下,不管是使用訊息佇列還是共享記憶體,甚至是訊號量,每個IPC的物件(object)都有唯一的名字,稱為“鍵”(key)。通過“鍵”,程序能夠識別所用的物件。“鍵”與IPC物件的關係就如同檔名稱之於檔案,通過檔名,程序能夠讀寫檔案內的資料,甚至多個程序能夠共用一個檔案。而在IPC的通訊模式下,通過“鍵”的使用也使得一個IPC物件能為多個程序所共用。
    Linux系統中的所有表示System V中IPC物件的資料結構都包括一個ipc_perm結構,其中包含有IPC物件的鍵值,該鍵用於查詢System V中IPC物件的引用識別符號。如果不使用“鍵”,程序將無法存取IPC物件,因為IPC物件並不存在於程序本身使用的記憶體中。
    通常,都希望自己的程式能和其他的程式預先約定一個唯一的鍵值,但實際上並不是總可能的成行的,因為自己的程式無法為一塊共享記憶體選擇一個鍵值。因此,在此把key設為IPC_PRIVATE,這樣,作業系統將忽略鍵,建立一個新的共享記憶體,指定一個鍵值,然後返回這塊共享記憶體IPC識別符號ID。而將這個新的共享記憶體的識別符號ID告訴其他程序可以在建立共享記憶體後通過派生子程序,或寫入檔案或管道來實現。

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


size_t size(單位位元組Byte)
-----------------------------------------------
    size是要建立共享記憶體的長度。所有的記憶體分配操作都是以頁為單位的。所以如果一段程序只申請一塊只有一個位元組的記憶體,記憶體也會分配整整一頁(在i386機器中一頁的預設大小PACE_SIZE=4096位元組)這樣,新建立的共享記憶體的大小實際上是從size這個引數調整而來的頁面大小。即如果size為1至4096,則實際申請到的共享記憶體大小為4K(一頁);4097到8192,則實際申請到的共享記憶體大小為8K(兩頁),依此類推。


int shmflg
-----------------------------------------------
    shmflg主要和一些標誌有關。其中有效的包括IPC_CREAT和IPC_EXCL,它們的功能與open()的O_CREAT和O_EXCL相當。
    IPC_CREAT   如果共享記憶體不存在,則建立一個共享記憶體,否則開啟操作。
    IPC_EXCL     只有在共享記憶體不存在的時候,新的共享記憶體才建立,否則就產生錯誤。

    如果單獨使用IPC_CREAT,shmget()函式要麼返回一個已經存在的共享記憶體的操作符,要麼返回一個新建的共享記憶體的識別符號。

    如果將IPC_CREAT和IPC_EXCL標誌一起使用,shmget()將返回一個新建的共享記憶體的識別符號;如果該共享記憶體已存在,則返回-1。

     IPC_EXEL標誌本身並沒有太大的意義,但是和IPC_CREAT標誌一起使用可以用來保證所得的物件是新建的,而不是開啟已有的物件。

這個可以用,但最好不要用:

     對於使用者的讀取和寫入許可指定SHM_R和SHM_W;

     (SHM_R>3)和(SHM_W>3)是一組讀取和寫入許可,而(SHM_R>6)和(SHM_W>6)是全域性讀取和寫入許可。

推薦使用這個:

           可以使用0666|IPC_CREAT,來作為shmflg的值。

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

獲取已有共享記憶體時,注意size不要超過建立時的大小,否則返回EINVAL錯誤。


返回值
-----------------------------------------------
成功返回共享記憶體的識別符號;不成功返回-1,errno儲存錯誤原因。
    EINVAL           引數size小於SHMMIN或大於SHMMAX。
    EEXIST           預建立key所致的共享記憶體,但已經存在。
    EIDRM            引數key所致的共享記憶體已經刪除。
    ENOSPC        超過了系統允許建立的共享記憶體的最大值(SHMALL )。
    ENOENT        引數key所指的共享記憶體不存在,引數shmflg也未設IPC_CREAT位。
    EACCES        沒有許可權。
    ENOMEM       核心記憶體不足。


struct shmid_ds
-----------------------------------------------
    shmid_ds資料結構表示每個新建的共享記憶體。當shmget()建立了一塊新的共享記憶體後,返回一個可以用於引用該共享記憶體的shmid_ds資料結構的識別符號。

include/linux/shm.h

    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 */ 
    };


struct ipc_perm
-----------------------------------------------
    對於每個IPC物件,系統共用一個struct ipc_perm的資料結構來存放許可權資訊,以確定一個ipc操作是否可以訪問該IPC物件。

    struct ipc_perm { 
        __kernel_key_t   key; 
        __kernel_uid_t   uid; 
        __kernel_gid_t   gid; 
        __kernel_uid_t   cuid; 
        __kernel_gid_t   cgid; 
        __kernel_mode_t mode; 
        unsigned short   seq; 
};
//----------------------------------------

備註:也可使用ipcrm -m  shmid的形式刪除共享記憶體,但是如果有其他的程序在使用共享記憶體,則不會真正的刪除共享記憶體,但會把共享記憶體的狀態(使用ipcs -m檢視status)製為dest,該動作是系統維護的。此時共享內能可以使用,當最後一個的程序結束或是不掛載共享記憶體時,共享記憶體則會自動刪除。



shmat

void *shmat(int shmid, const void *addr, int flag);
shmid:共享儲存的id
addr:一般為0,表示連線到由核心選擇的第一個可用地址上,否則,如果flag沒有指定SHM_RND,則連線到addr所指定的地址上,如果flag為SHM_RND,則地址取整
flag:如前所述,一般為0                //推薦值
返回值:如果成功,返回共享儲存段地址,出錯返回-1

shmdt

int shmdt(void *addr);
addr:共享儲存段的地址,以前呼叫shmat時的返回值
shmdt將使相關shmid_ds結構中的shm_nattch計數器值減1

這並不是從系統中刪除其識別符號以及其資料結構。函式刪除本程序對這塊記憶體的使用,shmdt()與shmat()相反,是用來禁止本程序訪問一塊共享記憶體的函式。

備註:也可使用ipcrm -m  shmid的形式刪除共享記憶體,但是如果有其他的程序在使用共享記憶體,則不會真正的刪除共享記憶體,但會把共享記憶體的狀態(使用ipcs -m檢視status)製為dest,該動作是系統維護的。此時共享內能可以使用,當最後一個的程序結束或是不掛載共享記憶體時,共享記憶體則會自動刪除。

shmctl

int shmctl(int shmid,int cmd,struct shmid_ds *buf)
shmid:共享儲存段的id
cmd:一些命令,有:IPC_STAT,IPC_RMID,SHM_LOCK,SHM_UNLOCK

共享記憶體不會隨著程式結束而自動消除,要麼呼叫shmctl刪除,要麼自己用手敲命令ipcrm去刪除,否則永遠留在系統中。

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

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

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

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

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

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

結論:

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

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

mmap 方式對應的真實檔案,如果使用者有許可權即可檢視,甚至刪除

shmget方式可通過ipcs,ipcrm命令檢視與清除