所謂共享內存就是使得多個進程能夠訪問同一塊內存空間,是最快的可用IPC形 式。是針對其餘通訊機制運行效率較低而設計的。每每與其它通訊機制,如信號量結合使用,來達到進程間的同步及互斥。其餘進程能把同一段共享內存段「鏈接 到」他們本身的地址空間裏去。全部進程都能訪問共享內存中的地址。若是一個進程向這段共享內存寫了數據,所作的改動會即時被有訪問同一段共享內存的其餘進 程看到。共享內存的使用大大下降了在大規模數據處理過程當中內存的消耗,可是共享內存的使用中有不少的陷阱,一不注意就很容易致使程序崩潰。linux
l 超過共享內存的大小限制?web
在一個linux服務器上,共享內存的整體大小是有限制的,這個大小經過SHMMAX參數來定義(以字節爲單位),您能夠經過執行如下命令來肯定 SHMMAX 的值:安全
# cat /proc/sys/kernel/shmmax服務器
若是機器上建立的共享內存的總共大小超出了這個限制,在程序中使用標準錯誤perror可能會出現如下的信息:數據結構
unable to attach to shared memory函數
解決方法:spa
1、設置 SHMMAX操作系統
SHMMAX 的默認值是 32MB 。通常使用下列方法之一種將 SHMMAX 參數設爲 2GB :設計
經過直接更改 /proc 文件系統,你不需從新啓動機器就能夠改變 SHMMAX 的默認設置。我使用的方法是將如下命令放入 /etc/rc.local 啓動文件中:指針
# >echo "2147483648" > /proc/sys/kernel/shmmax
您還可使用 sysctl 命令來更改 SHMMAX 的值:
# sysctl -w kernel.shmmax=2147483648
最後,經過將該內核參數插入到 /etc/sysctl.conf 啓動文件中,您可使這種更改永久有效:
# echo "kernel.shmmax=2147483648" >> /etc/sysctl.conf
2、設置 SHMMNI
咱們如今來看 SHMMNI 參數。這個內核參數用於設置系統範圍內共享內存段的最大數量。該參數的默認值是4096 。這一數值已經足夠,一般不須要更改。
您能夠經過執行如下命令來肯定 SHMMNI 的值:
# cat /proc/sys/kernel/shmmni
4096
3、設置 SHMALL
最後,咱們來看 SHMALL 共享內存內核參數。該參數控制着系統一次可使用的共享內存總量(以頁爲單位)。簡言之,該參數的值始終應該至少爲:
ceil(SHMMAX/PAGE_SIZE)
SHMALL 的默認大小爲 2097152 ,可使用如下命令進行查詢:
# cat /proc/sys/kernel/shmall
2097152
SHMALL 的默認設置對於咱們來講應該足夠使用。
注意: 在 i386 平臺上 Red Hat Linux 的 頁面大小 爲 4096 字節。可是,您可使用 bigpages ,它支持配置更大的內存頁面尺寸。
l 屢次進行shmat會出現什麼問題?
當首次建立共享內存段時,它並不能被任何進程所訪問。爲了使共享內存區能夠被訪問,則必須經過 shmat函數將其附加( attach )到本身的進程空間中,這樣進程就與共享內存創建了鏈接。該函數聲明在 linux/shm.h中:
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
參數 shmid 是 shmget() 的返回值,是個標識符;
參數 shmflg 是存取權限標誌;若是爲 0 ,則不設置任何限制權限。在 <bits/shm.h> 中定義了幾個權限:
#define SHM_RDONLY 010000
#define SHM_RND 020000
#define SHM_REMAP 040000
若是指定 SHM_RDONLY ,那麼共享內存區只有讀取權限。
參數 shmaddr 是共享內存的附加點,不一樣的取值有不一樣的含義:
Ø 若是爲空,則由內核選擇一個空閒的內存區;若是非空,返回地址取決於調用者是否給 shmflg 參數指定SHM_RND 值,若是沒有指定,則共享內存區附加到由 shmaddr 指定的地址;不然附加地址爲 shmaddr 向下舍入一個共享內存低端邊界地址後的地址 (SHMLBA ,一個常址)。
Ø 一般將參數 shmaddr 設置爲 NULL 。
shmat() 調用成功後返回一個指向共享內存區的指針,使用該指針就能夠訪問共享內存區了,若是失敗則返回-1。
其映射關係以下圖所示:
圖1.1 共享內存映射圖
其中,shmaddr表示的是物理內存空間映射到進程的虛擬內存空間時候,虛擬內存空間中該塊內存的起始地址,在使用中,由於咱們通常不清楚進程中哪些地址沒有被佔用,因此很差指定物理空間的內存要映射到本進程的虛擬內存地址,通常會讓內核本身指定:
void ptr = shmat(shmid, NULL,0);
這樣掛載一個共享內存若是是一次調用是沒有問題的,可是一個進程是能夠對同一個共享內存屢次 shmat進行掛載的,物理內存是指向同一塊,若是shmaddr爲NULL,則每次返回的線性地址空間都不一樣。並且指向這塊共享內存的引用計數會增長。也就是進程多塊線性空間會指向同一塊物理地址。這樣,若是以前掛載過這塊共享內存的進程的線性地址沒有被shmdt掉,即申請的線性地址都沒有釋放,就會一直消耗進程的虛擬內存空間,頗有可能會最後致使進程線性空間被使用完而致使下次shmat或者其餘操做失敗。
解決方法:
能夠經過判斷須要申請的共享內存指針是否爲空來標識是不是第一次掛載共享內存,如果則使用進行掛載,若不是則退出。
void* ptr = NULL;
...
if (NULL != ptr)
return;
ptr = shmat(shmid,ptr,0666);
附:
函數shmat將標識號爲shmid共享內存映射到調用進程的地址空間中,映射的地址由參數shmaddr和shmflg共同肯定,其準則爲:
(1) 若是參數shmaddr取值爲NULL,系統將自動肯定共享內存連接到進程空間的首地址。
(2) 若是參數shmaddr取值不爲NULL且參數shmflg沒有指定SHM_RND標誌,系統將運用地址shmaddr連接共享內存。
(3) 若是參數shmaddr取值不爲NULL且參數shmflg指定了SHM_RND標誌位,系統將地址shmaddr對齊後連接共享內存。其中選項SHM_RND的意思是取整對齊,常數SHMLBA表明了低邊界地址的倍數,公式「shmaddr - (shmaddr % SHMLBA)」的意思是將地址shmaddr移動到低邊界地址的整數倍上。
l Shmget建立共享內存,當key相同時,什麼狀況下會出錯?
shmget() 用來建立一個共享內存區,或者訪問一個已存在的共享內存區。該函數定義在頭文件 linux/shm.h中,原型以下:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
參數 key是由 ftok() 獲得的鍵值;
參數 size 是以字節爲單位指定內存的大小;
參數 shmflg 是操做標誌位,它的一些宏定義以下:
IPC_CREATE : 調用 shmget 時,系統將此值與其餘共享內存區的 key 進行比較,若是存在相同的 key ,說明共享內存區已存在,此時返回該共享內存區的標識符,不然新建一個共享內存區並返回其標識符。
IPC_EXCL : 該宏必須和 IPC_CREATE 一塊兒使用,不然沒意義。當 shmflg 取 IPC_CREATE | IPC_EXCL 時,表示若是發現內存區已經存在則返回 -1,錯誤代碼爲 EEXIST 。
注意,當建立一個新的共享內存區時,size 的值必須大於 0 ;若是是訪問一個已經存在的內存共享區,則置size 爲 0 。
通常咱們建立共享內存的時候會在一個進程中使用shmget來建立共享內存,
Int shmid = shmget(key, size, IPC_CREATE|0666);
而在另外的進程中,使用shmget和一樣的key來獲取到這個已經建立了的共享內存,
Int shmid = shmget(key, size, IPC_CREATE|0666);
若是建立進程和掛接進程key相同,而對應的size大小不一樣,是否會shmget失敗?
Ø 已經建立的共享內存的大小是能夠調整的,可是已經建立的共享內存的大小隻能調小,不能調大
如 shm_id = shmget(key,4194304,IPC_CREAT);
建立了一個4M大小的共享內存,若是這個共享內存沒有刪掉,咱們再使用
shm_id = shmget(key,10485760,IPC_CREAT);
來建立一個10M大小的共享內存的時候,使用標準錯誤輸出會有以下錯誤信息:
shmget error: Invalid argument
可是,若是咱們使用
shm_id = shmget(key,3145728,IPC_CREAT);
來建立一個3M大小的共享內存的時候,並不會輸出錯誤信息,只是共享內存大小會被修改成3145728,這也說明,使用共享內存的時候,是用key來做爲共享內存的惟一標識的,共享內存的大小不能區分共享內存。
這樣會致使什麼問題?
當多個進程都能建立共享內存的時候,若是key出 現相同的狀況,而且一個進程須要建立的共享內存的大小要比另一個進程要建立的共享內存小,共享內存大的進程先建立共享內存,共享內存小的進程後建立共享 內存,小共享內存的進程就會獲取到大的共享內存進程的共享內存,並修改其共享內存的大小和內容,從而可能致使大的共享內存進程崩潰。
解決方法:
方法一:
在全部的共享內存建立的時候,使用排他性建立,即便用IPC_EXCL標記:
Shmget(key, size,IPC_CREATE|IPC_EXCL);
在共享內存掛接的時候,先使用排他性建立判斷共享內存是否已經建立,若是還沒建立則進行出錯處理,若已經建立,則掛接
Shmid = Shmget(key, size,IPC_CREATE|IPC_EXCL);
If (-1 != shmid)
{
Printf("error");
}
Shmid = Shmget(key, size,IPC_CREATE);
方法二:
雖然都但願本身的程序能和其餘的程序預先約定一個惟一的鍵值,但實際上並非總可能的成行的,由於本身的程序沒法爲一塊共享內存選擇一個鍵值。所以,在此把key設爲IPC_PRIVATE,這樣,操做系統將忽略鍵,創建一個新的共享內存,指定一個鍵值,而後返回這塊共享內存IPC標識符ID。而將這個新的共享內存的標識符ID告訴其餘進程能夠在創建共享內存後經過派生子進程,或寫入文件或管道來實現,即這種方法不使用key來建立共享內存,由操做系統來保證惟一性。
l ftok是否必定會產生惟一的key值?
系統創建IPC通信(如消息隊列、共享內存時)必須指定一個ID值。一般狀況下,該id值經過ftok函數獲得。
ftok原型以下:
key_t ftok( char * pathname, int proj_id)
pathname就時你指定的文件名,proj_id是子序號。
在通常的UNIX實現中,是將文件的索引節點號取出,前面加上子序號獲得key_t的返回值。如指定文件的索引節點號爲65538,換算成16進製爲0x010002,而你指定的proj_id值爲38,換算成16進製爲0x26,則最後的key_t返回值爲0x26010002。
查詢文件索引節點號的方法是: ls -i
但當刪除重建文件後,索引節點號由操做系統根據當時文件系統的使用狀況分配,所以與原來不一樣,因此獲得的索引節點號也不一樣。
根據pathname指定的文件(或目錄)名稱,以及proj_id參數指定的數字,ftok函數爲IPC對象生成一個惟一性的鍵值。在實際應用中,很容易產生的一個理解是,在proj_id相同的狀況下,只要文件(或目錄)名稱不變,就能夠確保ftok返回始終一致的鍵值。然而,這個理解並不是徹底正確,有可能給應用開發埋下很隱晦的陷阱。由於ftok的實現存在這樣的風險,即在訪問同一共享內存的多個進程前後調用ftok函數的時間段中,若是pathname指定的文件(或目錄)被刪除且從新建立,則文件系統會賦予這個同名文件(或目錄)新的i節點信息,因而這些進程所調用的ftok雖 然都能正常返回,但獲得的鍵值卻並不能保證相同。由此可能形成的後果是,本來這些進程意圖訪問一個相同的共享內存對象,然而因爲它們各自獲得的鍵值不一樣, 實際上進程指向的共享內存再也不一致;若是這些共享內存都獲得建立,則在整個應用運行的過程當中表面上不會報出任何錯誤,然而經過一個共享內存對象進行數據傳 輸的目的將沒法實現。
因此若是要確保key_t值不變,要麼確保ftok的文件不被刪除,要麼不用ftok,指定一個固定的key_t值。
若是存在生成key_t值的文件被刪除過,則頗有可能本身如今使用的共享內存key_t值會和另一個進程的key_t值衝突,以下面這種狀況:
進程1使用文件1來ftok生成了key10000,進程2使用文件2來ftok生成了key 11111,此時若是進程1和進程2都須要下載文件,並將文件的內容更新到共享內存,此時進程1和2都須要先下文件,再刪掉以前的共享內存,再使用ftok生成新的key,再用這個key去申請新的共享內存來裝載新的問題,可是可能文件2比較大,下載慢,而文件1比較小,下載比較慢,因爲文件1和文件2都被修改,此時文件1所佔用的文件節點號多是文件2以前所佔用的,此時若是下載的文件1的ftok生成的key爲11111的話,就會和此時尚未是否11111這個key的進程2的共享內存衝突,致使出現問題。
解決方法:
方法一:
在有下載文件操做的程序中,對下載的文件使用ftok獲取key的時候,須要進行衝突避免的措施,如使用獨佔的方式獲取共享內存,若是不成功,則對key進行加一操做,再進行獲取共享內存,一直到不會產生衝突爲止。
方法二:
下載文件以前,將以前的文件進行mv一下,先「佔」着這個文件節點號,防止其餘共享內存申請key的時候獲取到。
另外:
建立進程在通知其餘進程掛接的時候,建議不使用ftok方式來獲取Key,而使用文件或者進程間通訊的方式告知。
l 共享內存刪除的陷阱?
當進程結束使用共享內存區時,要經過函數 shmdt 斷開與共享內存區的鏈接。該函數聲明在 sys/shm.h中,其原型以下:
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
參數 shmaddr 是 shmat 函數的返回值。
進程脫離共享內存區後,數據結構 shmid_ds 中的 shm_nattch 就會減 1 。可是共享段內存依然存在,只有shm_attch 爲 0 後,即沒有任何進程再使用該共享內存區,共享內存區纔在內核中被刪除。通常來講,當一個進程終止時,它所附加的共享內存區都會自動脫離。
咱們經過
int shmctl( int shmid , int cmd , struct shmid_ds *buf );
來刪除已經存在的共享內存。
第一個參數,shmid,是由shmget所返回的標記符。
第二個參數,cmd,是要執行的動做。他能夠有三個值:
命令 描述
IPC_STAT 設置shmid_ds結構中的數據反射與共享內存相關聯的值。
IPC_SET 若是進程有相應的權限,將與共享內存相關聯的值設置爲shmid_ds數據結構中所提供的值。
IPC_RMID 刪除共享內存段。
第三個參數,buf,是一個指向包含共享內存模式與權限的結構的指針,刪除的時候能夠默認爲0。
若是共享內存已經與全部訪問它的進程斷開了鏈接,則調用IPC_RMID子命令後,系統將當即刪除共享內存的標識符,並刪除該共享內存區,以及全部相關的數據結構;
若是仍有別的進程與該共享內存保持鏈接,則調用IPC_RMID子命令後,該共享內存並不會被當即從系統中刪除,而是被設置爲IPC_PRIVATE狀態,並被標記爲"已被刪除"(使用ipcs命令能夠看到dest字段);直到已有鏈接所有斷開,該共享內存纔會最終從系統中消失。
須要說明的是:一旦經過shmctl對共享內存進行了刪除操做,則該共享內存將不能再接受任何新的鏈接,即便它依然存在於系統中!因此,能夠確知,在對共享內存刪除以後不可能再有新的鏈接,則執行刪除操做是安全的;不然,在刪除操做以後如仍有新的鏈接發生,則這些鏈接都將可能失敗!
Shmdt和shmctl的區別:
Shmdt 是將共享內存從進程空間detach出來,使進程中的shmid無效化,不可使用。可是保留空間。
而shmctl(sid,IPC_RMID,0)則是刪除共享內存,完全不可用,釋放空間。