Linux系統編程我一直看 <GNU/LINUX編程指南>,只是講的太簡單了,通常是書和網絡上的資料結合着來掌握才比較全面 .在掌握了書上的內容后,再來都其他資料 .
原文鏈接 http://www.cnblogs.com/skyme/archive/2011/01/04/1925404.html
共享內存是系統出於多個進程之間通訊的考慮,而預留的的一塊內存區。在/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對共享內存進行了刪除操作,則該共享內存將不能再接受任何新的連接,即使它依然存在於系統中!所以,可以確知,在對共享內存刪除之后不可能再有新的連接,則執行刪除操作是安全的;否則,在刪除操作之后如仍有新的連接發生,則這些連接都將失敗!