共享記憶體是系統出于多個程序之間通訊的考慮,而預留的的一塊記憶體區。在/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
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 <errno.h>
#include <sys/types.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);
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,
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 之前必需的操作:
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對共享記憶體進行了删除操作,則該共享記憶體将不能再接受任何新的連接配接,即使它依然存在于系統中!是以,可以确知,在對共享記憶體删除之後不可能再有新的連接配接,則執行删除操作是安全的;否則,在删除操作之後如仍有新的連接配接發生,則這些連接配接都将失敗!