所謂共享內(nèi)存就是使得多個(gè)進(jìn)程可以訪問(wèn)同一塊內(nèi)存空間,是最快的可用IPC形式。是針對(duì)其他通信機(jī)制運(yùn)行效率較低而設(shè)計(jì)的。往往與其它通信機(jī)制,如信號(hào)量結(jié)合使用,來(lái)達(dá)到進(jìn)程間的同步及互斥。其他進(jìn)程能把同一段共享內(nèi)存段“連接到”他們自己的地址空間里去。所有進(jìn)程都能訪問(wèn)共享內(nèi)存中的地址。如果一個(gè)進(jìn)程向這段共享內(nèi)存寫了數(shù)據(jù),所做的改動(dòng)會(huì)即時(shí)被有訪問(wèn)同一段共享內(nèi)存的其他進(jìn)程看到。共享內(nèi)存的使用大大降低了在大規(guī)模數(shù)據(jù)處理過(guò)程中內(nèi)存的消耗,但是共享內(nèi)存的使用中有很多的陷阱,一不注意就很容易導(dǎo)致程序崩潰。
l 超過(guò)共享內(nèi)存的大小限制? 在一個(gè)linux服務(wù)器上,共享內(nèi)存的總體大小是有限制的,這個(gè)大小通過(guò)SHMMAX參數(shù)來(lái)定義(以字節(jié)為單位),您可以通過(guò)執(zhí)行以下命令來(lái)確定 SHMMAX 的值:
# cat /proc/sys/kernel/shmmax
如果機(jī)器上創(chuàng)建的共享內(nèi)存的總共大小超出了這個(gè)限制,在程序中使用標(biāo)準(zhǔn)錯(cuò)誤perror可能會(huì)出現(xiàn)以下的信息:
unable to attach to shared memory
解決方法: 1、設(shè)置 SHMMAX SHMMAX 的默認(rèn)值是 32MB 。一般使用下列方法之一種將 SHMMAX 參數(shù)設(shè)為 2GB :
通過(guò)直接更改 /proc 文件系統(tǒng),你不需重新啟動(dòng)機(jī)器就可以改變 SHMMAX 的默認(rèn)設(shè)置。我使用的方法是將以下命令放入 /etc/rc.local 啟動(dòng)文件中:
# >echo "2147483648" > /proc/sys/kernel/shmmax
您還可以使用 sysctl 命令來(lái)更改 SHMMAX 的值:
# sysctl -w kernel.shmmax=2147483648
最后,通過(guò)將該內(nèi)核參數(shù)插入到 /etc/sysctl.conf 啟動(dòng)文件中,您可以使這種更改永久有效:
# echo "kernel.shmmax=2147483648" >> /etc/sysctl.conf
2、設(shè)置 SHMMNI
我們現(xiàn)在來(lái)看 SHMMNI 參數(shù)。這個(gè)內(nèi)核參數(shù)用于設(shè)置系統(tǒng)范圍內(nèi)共享內(nèi)存段的最大數(shù)量。該參數(shù)的默認(rèn)值是 4096 。這一數(shù)值已經(jīng)足夠,通常不需要更改。
您可以通過(guò)執(zhí)行以下命令來(lái)確定 SHMMNI 的值:
# cat /proc/sys/kernel/shmmni
4096
3、設(shè)置 SHMALL
最后,我們來(lái)看 SHMALL 共享內(nèi)存內(nèi)核參數(shù)。該參數(shù)控制著系統(tǒng)一次可以使用的共享內(nèi)存總量(以頁(yè)為單位)。簡(jiǎn)言之,該參數(shù)的值始終應(yīng)該至少為:
ceil(SHMMAX/PAGE_SIZE)
SHMALL 的默認(rèn)大小為 2097152 ,可以使用以下命令進(jìn)行查詢:
# cat /proc/sys/kernel/shmall
2097152
SHMALL 的默認(rèn)設(shè)置對(duì)于我們來(lái)說(shuō)應(yīng)該足夠使用。
注意: 在 i386 平臺(tái)上 Red Hat Linux 的 頁(yè)面大小 為 4096 字節(jié)。但是,您可以使用 bigpages ,它支持配置更大的內(nèi)存頁(yè)面尺寸。
l 多次進(jìn)行shmat會(huì)出現(xiàn)什么問(wèn)題? 當(dāng)首次創(chuàng)建共享內(nèi)存段時(shí),它并不能被任何進(jìn)程所訪問(wèn)。為了使共享內(nèi)存區(qū)可以被訪問(wèn),則必須通過(guò) shmat 函數(shù)將其附加( attach )到自己的進(jìn)程空間中,這樣進(jìn)程就與共享內(nèi)存建立了連接。該函數(shù)聲明在 linux/shm.h中:
#include <sys/types.h>
#define SHM_RDONLY 010000 /* attach read-only else read-write */
Ø 如果為空,則由內(nèi)核選擇一個(gè)空閑的內(nèi)存區(qū);如果非空,返回地址取決于調(diào)用者是否給 shmflg 參數(shù)指定 SHM_RND 值,如果沒(méi)有指定,則共享內(nèi)存區(qū)附加到由 shmaddr 指定的地址;否則附加地址為 shmaddr 向下舍入一個(gè)共享內(nèi)存低端邊界地址后的地址 (SHMLBA ,一個(gè)常址)。 Ø 通常將參數(shù) shmaddr 設(shè)置為 NULL 。 shmat() 調(diào)用成功后返回一個(gè)指向共享內(nèi)存區(qū)的指針,使用該指針就可以訪問(wèn)共享內(nèi)存區(qū)了,如果失敗則返回 -1。
其映射關(guān)系如下圖所示:
圖1.1 共享內(nèi)存映射圖
其中,shmaddr表示的是物理內(nèi)存空間映射到進(jìn)程的虛擬內(nèi)存空間時(shí)候,虛擬內(nèi)存空間中該塊內(nèi)存的起始地址,在使用中,因?yàn)槲覀円话悴磺宄M(jìn)程中哪些地址沒(méi)有被占用,所以不好指定物理空間的內(nèi)存要映射到本進(jìn)程的虛擬內(nèi)存地址,一般會(huì)讓內(nèi)核自己指定:
void ptr = shmat(shmid, NULL,0);
這樣掛載一個(gè)共享內(nèi)存如果是一次調(diào)用是沒(méi)有問(wèn)題的,但是一個(gè)進(jìn)程是可以對(duì)同一個(gè)共享內(nèi)存多次 shmat進(jìn)行掛載的,物理內(nèi)存是指向同一塊,如果shmaddr為NULL,則每次返回的線性地址空間都不同。而且指向這塊共享內(nèi)存的引用計(jì)數(shù)會(huì)增加。也就是進(jìn)程多塊線性空間會(huì)指向同一塊物理地址。這樣,如果之前掛載過(guò)這塊共享內(nèi)存的進(jìn)程的線性地址沒(méi)有被shmdt掉,即申請(qǐng)的線性地址都沒(méi)有釋放,就會(huì)一直消耗進(jìn)程的虛擬內(nèi)存空間,很有可能會(huì)最后導(dǎo)致進(jìn)程線性空間被使用完而導(dǎo)致下次shmat或者其他操作失敗。 解決方法: 可以通過(guò)判斷需要申請(qǐng)的共享內(nèi)存指針是否為空來(lái)標(biāo)識(shí)是否是第一次掛載共享內(nèi)存,若是則使用進(jìn)行掛載,若不是則退出。 void* ptr = NULL; ... if (NULL != ptr) return; ptr = shmat(shmid,ptr,0666); 附: 函數(shù)shmat將標(biāo)識(shí)號(hào)為shmid共享內(nèi)存映射到調(diào)用進(jìn)程的地址空間中,映射的地址由參數(shù)shmaddr和shmflg共同確定,其準(zhǔn)則為:
l Shmget創(chuàng)建共享內(nèi)存,當(dāng)key相同時(shí),什么情況下會(huì)出錯(cuò)?
shmget() 用來(lái)創(chuàng)建一個(gè)共享內(nèi)存區(qū),或者訪問(wèn)一個(gè)已存在的共享內(nèi)存區(qū)。該函數(shù)定義在頭文件 linux/shm.h中,原型如下: #include <sys/ipc.h>
IPC_CREATE : 調(diào)用 shmget 時(shí),系統(tǒng)將此值與其他共享內(nèi)存區(qū)的 key 進(jìn)行比較,如果存在相同的 key ,說(shuō)明共享內(nèi)存區(qū)已存在,此時(shí)返回該共享內(nèi)存區(qū)的標(biāo)識(shí)符,否則新建一個(gè)共享內(nèi)存區(qū)并返回其標(biāo)識(shí)符。 IPC_EXCL : 該宏必須和 IPC_CREATE 一起使用,否則沒(méi)意義。當(dāng) shmflg 取 IPC_CREATE | IPC_EXCL 時(shí),表示如果發(fā)現(xiàn)內(nèi)存區(qū)已經(jīng)存在則返回 -1,錯(cuò)誤代碼為 EEXIST 。 注意,當(dāng)創(chuàng)建一個(gè)新的共享內(nèi)存區(qū)時(shí),size 的值必須大于 0 ;如果是訪問(wèn)一個(gè)已經(jīng)存在的內(nèi)存共享區(qū),則置 size 為 0 。
一般我們創(chuàng)建共享內(nèi)存的時(shí)候會(huì)在一個(gè)進(jìn)程中使用shmget來(lái)創(chuàng)建共享內(nèi)存, Int shmid = shmget(key, size, IPC_CREATE|0666); 而在另外的進(jìn)程中,使用shmget和同樣的key來(lái)獲取到這個(gè)已經(jīng)創(chuàng)建了的共享內(nèi)存, Int shmid = shmget(key, size, IPC_CREATE|0666); 如果創(chuàng)建進(jìn)程和掛接進(jìn)程key相同,而對(duì)應(yīng)的size大小不同,是否會(huì)shmget失敗?
Ø 已經(jīng)創(chuàng)建的共享內(nèi)存的大小是可以調(diào)整的,但是已經(jīng)創(chuàng)建的共享內(nèi)存的大小只能調(diào)小,不能調(diào)大
如 shm_id = shmget(key,4194304,IPC_CREAT); 創(chuàng)建了一個(gè)4M大小的共享內(nèi)存,如果這個(gè)共享內(nèi)存沒(méi)有刪掉,我們?cè)偈褂?/span> shm_id = shmget(key,10485760,IPC_CREAT); 來(lái)創(chuàng)建一個(gè)10M大小的共享內(nèi)存的時(shí)候,使用標(biāo)準(zhǔn)錯(cuò)誤輸出會(huì)有如下錯(cuò)誤信息: shmget error: Invalid argument 但是,如果我們使用 shm_id = shmget(key,3145728,IPC_CREAT); 來(lái)創(chuàng)建一個(gè)3M大小的共享內(nèi)存的時(shí)候,并不會(huì)輸出錯(cuò)誤信息,只是共享內(nèi)存大小會(huì)被修改為3145728,這也說(shuō)明,使用共享內(nèi)存的時(shí)候,是用key來(lái)作為共享內(nèi)存的唯一標(biāo)識(shí)的,共享內(nèi)存的大小不能區(qū)分共享內(nèi)存。 這樣會(huì)導(dǎo)致什么問(wèn)題? 當(dāng)多個(gè)進(jìn)程都能創(chuàng)建共享內(nèi)存的時(shí)候,如果key出現(xiàn)相同的情況,并且一個(gè)進(jìn)程需要?jiǎng)?chuàng)建的共享內(nèi)存的大小要比另外一個(gè)進(jìn)程要?jiǎng)?chuàng)建的共享內(nèi)存小,共享內(nèi)存大的進(jìn)程先創(chuàng)建共享內(nèi)存,共享內(nèi)存小的進(jìn)程后創(chuàng)建共享內(nèi)存,小共享內(nèi)存的進(jìn)程就會(huì)獲取到大的共享內(nèi)存進(jìn)程的共享內(nèi)存,并修改其共享內(nèi)存的大小和內(nèi)容,從而可能導(dǎo)致大的共享內(nèi)存進(jìn)程崩潰。
解決方法: 方法一: 在所有的共享內(nèi)存創(chuàng)建的時(shí)候,使用排他性創(chuàng)建,即使用IPC_EXCL標(biāo)記: Shmget(key, size,IPC_CREATE|IPC_EXCL); 在共享內(nèi)存掛接的時(shí)候,先使用排他性創(chuàng)建判斷共享內(nèi)存是否已經(jīng)創(chuàng)建,如果還沒(méi)創(chuàng)建則進(jìn)行出錯(cuò)處理,若已經(jīng)創(chuàng)建,則掛接 Shmid = Shmget(key, size,IPC_CREATE|IPC_EXCL); If (-1 != shmid) { Printf("error"); Shmid = Shmget(key, size,IPC_CREATE);
方法二:
雖然都希望自己的程序能和其他的程序預(yù)先約定一個(gè)唯一的鍵值,但實(shí)際上并不是總可能的成行的,因?yàn)樽约旱某绦驘o(wú)法為一塊共享內(nèi)存選擇一個(gè)鍵值。因此,在此把key設(shè)為IPC_PRIVATE,這樣,操作系統(tǒng)將忽略鍵,建立一個(gè)新的共享內(nèi)存,指定一個(gè)鍵值,然后返回這塊共享內(nèi)存IPC標(biāo)識(shí)符ID。而將這個(gè)新的共享內(nèi)存的標(biāo)識(shí)符ID告訴其他進(jìn)程可以在建立共享內(nèi)存后通過(guò)派生子進(jìn)程,或?qū)懭胛募蚬艿纴?lái)實(shí)現(xiàn),即這種方法不使用key來(lái)創(chuàng)建共享內(nèi)存,由操作系統(tǒng)來(lái)保證唯一性。
l ftok是否一定會(huì)產(chǎn)生唯一的key值? 系統(tǒng)建立IPC通訊(如消息隊(duì)列、共享內(nèi)存時(shí))必須指定一個(gè)ID值。通常情況下,該id值通過(guò)ftok函數(shù)得到。 ftok原型如下: key_t ftok( char * pathname, int proj_id) pathname就時(shí)你指定的文件名,proj_id是子序號(hào)。
在一般的UNIX實(shí)現(xiàn)中,是將文件的索引節(jié)點(diǎn)號(hào)取出,前面加上子序號(hào)得到key_t的返回值。如指定文件的索引節(jié)點(diǎn)號(hào)為65538,換算成16進(jìn)制為0x010002,而你指定的proj_id值為38,換算成16進(jìn)制為0x26,則最后的key_t返回值為0x26010002。 查詢文件索引節(jié)點(diǎn)號(hào)的方法是: ls -i
但當(dāng)刪除重建文件后,索引節(jié)點(diǎn)號(hào)由操作系統(tǒng)根據(jù)當(dāng)時(shí)文件系統(tǒng)的使用情況分配,因此與原來(lái)不同,所以得到的索引節(jié)點(diǎn)號(hào)也不同。
根據(jù)pathname指定的文件(或目錄)名稱,以及proj_id參數(shù)指定的數(shù)字,ftok函數(shù)為IPC對(duì)象生成一個(gè)唯一性的鍵值。在實(shí)際應(yīng)用中,很容易產(chǎn)生的一個(gè)理解是,在proj_id相同的情況下,只要文件(或目錄)名稱不變,就可以確保ftok返回始終一致的鍵值。然而,這個(gè)理解并非完全正確,有可能給應(yīng)用開(kāi)發(fā)埋下很隱晦的陷阱。因?yàn)?/span>ftok的實(shí)現(xiàn)存在這樣的風(fēng)險(xiǎn),即在訪問(wèn)同一共享內(nèi)存的多個(gè)進(jìn)程先后調(diào)用ftok函數(shù)的時(shí)間段中,如果pathname指定的文件(或目錄)被刪除且重新創(chuàng)建,則文件系統(tǒng)會(huì)賦予這個(gè)同名文件(或目錄)新的i節(jié)點(diǎn)信息,于是這些進(jìn)程所調(diào)用的ftok雖然都能正常返回,但得到的鍵值卻并不能保證相同。由此可能造成的后果是,原本這些進(jìn)程意圖訪問(wèn)一個(gè)相同的共享內(nèi)存對(duì)象,然而由于它們各自得到的鍵值不同,實(shí)際上進(jìn)程指向的共享內(nèi)存不再一致;如果這些共享內(nèi)存都得到創(chuàng)建,則在整個(gè)應(yīng)用運(yùn)行的過(guò)程中表面上不會(huì)報(bào)出任何錯(cuò)誤,然而通過(guò)一個(gè)共享內(nèi)存對(duì)象進(jìn)行數(shù)據(jù)傳輸?shù)哪康膶o(wú)法實(shí)現(xiàn)。 所以如果要確保key_t值不變,要么確保ftok的文件不被刪除,要么不用ftok,指定一個(gè)固定的key_t值。 如果存在生成key_t值的文件被刪除過(guò),則很有可能自己現(xiàn)在使用的共享內(nèi)存key_t值會(huì)和另外一個(gè)進(jìn)程的key_t值沖突,如下面這種情況:
進(jìn)程1使用文件1來(lái)ftok生成了key10000,進(jìn)程2使用文件2來(lái)ftok生成了key 11111,此時(shí)如果進(jìn)程1和進(jìn)程2都需要下載文件,并將文件的內(nèi)容更新到共享內(nèi)存,此時(shí)進(jìn)程1和2都需要先下文件,再刪掉之前的共享內(nèi)存,再使用ftok生成新的key,再用這個(gè)key去申請(qǐng)新的共享內(nèi)存來(lái)裝載新的問(wèn)題,但是可能文件2比較大,下載慢,而文件1比較小,下載比較慢,由于文件1和文件2都被修改,此時(shí)文件1所占用的文件節(jié)點(diǎn)號(hào)可能是文件2之前所占用的,此時(shí)如果下載的文件1的ftok生成的key為11111的話,就會(huì)和此時(shí)還沒(méi)有是否11111這個(gè)key的進(jìn)程2的共享內(nèi)存沖突,導(dǎo)致出現(xiàn)問(wèn)題。
解決方法: 方法一: 在有下載文件操作的程序中,對(duì)下載的文件使用ftok獲取key的時(shí)候,需要進(jìn)行沖突避免的措施,如使用獨(dú)占的方式獲取共享內(nèi)存,如果不成功,則對(duì)key進(jìn)行加一操作,再進(jìn)行獲取共享內(nèi)存,一直到不會(huì)產(chǎn)生沖突為止。 方法二: 下載文件之前,將之前的文件進(jìn)行mv一下,先“占”著這個(gè)文件節(jié)點(diǎn)號(hào),防止其他共享內(nèi)存申請(qǐng)key的時(shí)候獲取到。
另外: 創(chuàng)建進(jìn)程在通知其他進(jìn)程掛接的時(shí)候,建議不使用ftok方式來(lái)獲取Key,而使用文件或者進(jìn)程間通信的方式告知。
l 共享內(nèi)存刪除的陷阱? 當(dāng)進(jìn)程結(jié)束使用共享內(nèi)存區(qū)時(shí),要通過(guò)函數(shù) shmdt 斷開(kāi)與共享內(nèi)存區(qū)的連接。該函數(shù)聲明在 sys/shm.h 中,其原型如下: #include <sys/types.h>
我們通過(guò) int shmctl( int shmid , int cmd , struct shmid_ds *buf ); 來(lái)刪除已經(jīng)存在的共享內(nèi)存。 第一個(gè)參數(shù),shmid,是由shmget所返回的標(biāo)記符。 第二個(gè)參數(shù),cmd,是要執(zhí)行的動(dòng)作。他可以有三個(gè)值: 命令 描述 第三個(gè)參數(shù),buf,是一個(gè)指向包含共享內(nèi)存模式與權(quán)限的結(jié)構(gòu)的指針,刪除的時(shí)候可以默認(rèn)為0。
如果共享內(nèi)存已經(jīng)與所有訪問(wèn)它的進(jìn)程斷開(kāi)了連接,則調(diào)用IPC_RMID子命令后,系統(tǒng)將立即刪除共享內(nèi)存的標(biāo)識(shí)符,并刪除該共享內(nèi)存區(qū),以及所有相關(guān)的數(shù)據(jù)結(jié)構(gòu); 需要說(shuō)明的是:一旦通過(guò)shmctl對(duì)共享內(nèi)存進(jìn)行了刪除操作,則該共享內(nèi)存將不能再接受任何新的連接,即使它依然存在于系統(tǒng)中!所以,可以確知,在對(duì)共享內(nèi)存刪除之后不可能再有新的連接,則執(zhí)行刪除操作是安全的;否則,在刪除操作之后如仍有新的連接發(fā)生,則這些連接都將可能失??!
Shmdt和shmctl的區(qū)別: Shmdt 是將共享內(nèi)存從進(jìn)程空間detach出來(lái),使進(jìn)程中的shmid無(wú)效化,不可以使用。但是保留空間。 |
|
來(lái)自: fym0121 > 《Linux進(jìn)程通信》