CentOS下共用記憶體使用量常見陷阱與分析

來源:互聯網
上載者:User

所謂共用記憶體就是使得多個進程可以訪問同一塊記憶體空間,是最快的可用IPC形式。是針對其他通訊機制運行效率較低而設計的。往往與其它通訊機制,如訊號量結合使用,來達到進程間的同步及互斥。其他進程能把同一段共用記憶體段“串連到”他們自己的地址空間裡去。所有進程都能訪問共用記憶體中的地址。如果一個進程向這段共用記憶體寫了資料,所做的改動會即時被有訪問同一段共用記憶體的其他進程看到。共用記憶體的使用大大降低了在大規模資料處理過程中記憶體的消耗,但是共用記憶體的使用中有很多的陷阱,一不注意就很容易導致程式崩潰。

超過共用記憶體的大小限制?

在一個linux伺服器上,共用記憶體的總體大小是有限制的,這個大小通過SHMMAX參數來定義(以位元組為單位),您可以通過執行以下命令來確定 SHMMAX 的值:

 
  1. # cat /proc/sys/kernel/shmmax

如果機器上建立的共用記憶體的總共大小超出了這個限制,在程式中使用標準錯誤perror可能會出現以下的資訊:

 
  1. unable to attach to shared memory

解決方案:

1、設定 SHMMAX

SHMMAX 的預設值是 32MB 。一般使用下列方法之一種將 SHMMAX 參數設為 2GB :

通過直接更改 /proc 檔案系統,你不需重新啟動機器就可以改變 SHMMAX 的預設設定。我使用的方法是將以下命令放入 />etc/rc.local 開機檔案中:

 
  1. # echo "2147483648" > /proc/sys/kernel/shmmax

您還可以使用 sysctl 命令來更改 SHMMAX 的值:

 
  1. # sysctl -w kernel.shmmax=2147483648

最後,通過將該核心參數插入到 /etc/sysctl.conf 開機檔案中,您可以使這種更改永久有效:

 
  1. # echo "kernel.shmmax=2147483648" >> /etc/sysctl.conf

2、設定 SHMMNI

我們現在來看 SHMMNI 參數。這個核心參數用於設定系統範圍內共用記憶體段的最大數量。該參數的預設值是 4096 。這一數值已經足夠,通常不需要更改。

您可以通過執行以下命令來確定 SHMMNI 的值:

 
  1. # cat /proc/sys/kernel/shmmni

  2. 4096

3、設定 SHMALL

最後,我們來看 SHMALL 共用記憶體核心參數。該參數控制著系統一次可以使用的共用記憶體總量(以頁為單位)。簡言之,該參數的值始終應該至少為:

 
  1. ceil(SHMMAX/PAGE_SIZE)

SHMALL 的預設大小為 2097152 ,可以使用以下命令進行查詢:

 
  1. # cat /proc/sys/kernel/shmall

  2. 2097152

SHMALL 的預設設定對於我們來說應該足夠使用。

注意: 在 i386 平台上 Red Hat Linux 的 頁面大小 為 4096 位元組。但是,您可以使用 bigpages ,它支援配置更大的記憶體頁面尺寸。

多次進行shmat會出現什麼問題?

當首次建立共用記憶體段時,它並不能被任何進程所訪問。為了使共用記憶體區可以被訪問,則必須通過 shmat 函數將其附加( attach )到自己的進程空間中,這樣進程就與共用記憶體建立了串連。該函式宣告在 linux/shm.h中:

 
  1. #include

  2. #include

  3. void *shmat(int shmid, const void *shmaddr, int shmflg);

參數 shmid 是 shmget() 的傳回值,是個標識符;

參數 shmflg 是存取許可權標誌;如果為 0 ,則不設定任何限制許可權。在 中定義了幾個許可權:

 
  1. #define SHM_RDONLY 010000 /* attach read-only else read-write */

  2. #define SHM_RND 020000 /* round attach address to SHMLBA */

  3. #define SHM_REMAP 040000 /* take-over region on attach */

如果指定 SHM_RDONLY ,那麼共用記憶體區只有讀取許可權。

參數 shmaddr 是共用記憶體的附加點,不同的取值有不同的含義:

?如果為空白,則由核心選擇一個閒置記憶體區;如果非空,返回地址取決於調用者是否給 shmflg 參數指定 SHM_RND 值,如果沒有指定,則共用記憶體區附加到由 shmaddr 指定的地址;否則附加地址為 shmaddr 向下舍入一個共用記憶體低端邊界地址後的地址 (SHMLBA ,一個常址)。

Ø通常將參數 shmaddr 設定為 NULL 。

shmat() 調用成功後返回一個指向共用記憶體區的指標,使用該指標就可以訪問共用記憶體區了,如果失敗則返回 -1。

其映射關係如所示:

圖1.1 共用記憶體映射圖

其中,shmaddr表示的是實體記憶體空間映射到進程的虛擬記憶體空間時候,虛擬記憶體空間中該塊記憶體的起始地址,在使用中,因為我們一般不清楚進程中哪些地址沒有被佔用,所以不好指定物理空間的記憶體要映射到本進程的虛擬記憶體地址,一般會讓核心自己指定:

 
  1. void ptr = shmat(shmid, NULL,0);

這樣掛載一個共用記憶體如果是一次調用是沒有問題的,但是一個進程是可以對同一個共用記憶體多次 shmat進行掛載的,實體記憶體是指向同一塊,如果shmaddr為NULL,則每次返回的線性地址空間都不同。而且指向這塊共用記憶體的引用計數會增加。也就是進程多塊線性空間會指向同一塊物理地址。這樣,如果之前掛載過這塊共用記憶體的進程的線性地址沒有被shmdt掉,即申請的線性地址都沒有釋放,就會一直消耗進程的虛擬記憶體空間,很有可能會最後導致進程線性空間被使用完而導致下次shmat或者其他動作失敗。

解決方案:

可以通過判斷需要申請的共用記憶體指標是否為空白來標識是否是第一次掛載共用記憶體,若是則使用進行掛載,若不是則退出。

 
  1. void* ptr = NULL;

  2. ...

  3. if (NULL != ptr)

  4. return;

  5. 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移動到低邊界地址的整數倍上。

Shmget建立共用記憶體,當key相同時,什麼情況下會出錯?

shmget() 用來建立一個共用記憶體區,或者訪問一個已存在的共用記憶體區。該函數定義在標頭檔 linux/shm.h中,原型如下:

 
  1. #include

  2. #include

  3. 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來建立共用記憶體,

 
  1. Int shmid = shmget(key, size, IPC_CREATE|0666);

而在另外的進程中,使用shmget和同樣的key來擷取到這個已經建立了的共用記憶體,

 
  1. Int shmid = shmget(key, size, IPC_CREATE|0666);

如果建立進程和掛接進程key相同,而對應的size大小不同,是否會shmget失敗?

Ø 已經建立的共用記憶體的大小是可以調整的,但是已經建立的共用記憶體的大小隻能調小,不能調大

如:

 
  1. shm_id = shmget(key,4194304,IPC_CREAT);

建立了一個4M大小的共用記憶體,如果這個共用記憶體沒有刪掉,我們再使用

 
  1. shm_id = shmget(key,10485760,IPC_CREAT);

來建立一個10M大小的共用記憶體的時候,使用標準錯誤輸出會有如下錯誤資訊:

 
  1. shmget error: Invalid argument

但是,如果我們使用:

 
  1. shm_id = shmget(key,3145728,IPC_CREAT);

來建立一個3M大小的共用記憶體的時候,並不會輸出錯誤資訊,只是共用記憶體大小會被修改為3145728,這也說明,使用共用記憶體的時候,是用key來作為共用記憶體的唯一標識的,共用記憶體的大小不能區分共用記憶體。

這樣會導致什麼問題?

當多個進程都能建立共用記憶體的時候,如果key出現相同的情況,並且一個進程需要建立的共用記憶體的大小要比另外一個進程要建立的共用記憶體小,共用記憶體大的進程先建立共用記憶體,共用記憶體小的進程後建立共用記憶體,小共用記憶體的進程就會擷取到大的共用記憶體進程的共用記憶體, 並修改其共用記憶體的大小和內容(留意下面的評論補充),從而可能導致大的共用記憶體進程崩潰。

解決方案:

方法一:

在所有的共用記憶體建立的時候,使用排他性建立,即使用IPC_EXCL標記:

 
  1. Shmget(key, size,IPC_CREATE|IPC_EXCL);

在共用記憶體掛接的時候,先使用排他性建立判斷共用記憶體是否已經建立,如果還沒建立則進行出錯處理,若已經建立,則掛接:

 
  1. Shmid = Shmget(key, size,IPC_CREATE|IPC_EXCL);

  2. If (-1 != shmid)

  3. {

  4. Printf("error");

  5. }

  6. Shmid = Shmget(key, size,IPC_CREATE);

方法二:

雖然都希望自己的程式能和其他的程式預先約定一個唯一的索引值,但實際上並不是總可能的成行的,因為自己的程式無法為一塊共用記憶體選擇一個索引值。因此,在此把key設為IPC_PRIVATE,這樣,作業系統將忽略鍵,建立一個新的共用記憶體,指定一個索引值,然後返回這塊共用記憶體IPC標識符ID。而將這個新的共用記憶體的標識符ID告訴其他進程可以在建立共用記憶體後通過派生子進程,或寫入檔案或管道來實現,即這種方法不使用key來建立共用記憶體,由作業系統來保證唯一性。

ftok是否一定會產生唯一的key值?

系統建立IPC通訊(如訊息佇列、共用記憶體時)必須指定一個ID值。通常情況下,該id值通過ftok函數得到。

ftok原型如下:

 
  1. key_t ftok( char * pathname, int proj_id)

pathname就時你指定的檔案名稱,proj_id是子序號。

在一般的UNIX實現中,是將檔案的索引節點號取出,前面加上子序號得到key_t的傳回值。如指定檔案的索引節點號為65538,換算成16進位為0×010002,而你指定的proj_id值為38,換算成16進位為0×26,則最後的key_t傳回值為0×26010002。

查詢檔案索引節點號的方法是: 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,而使用檔案或者處理序間通訊的方式告知。

共用記憶體刪除的陷阱?

當進程結束使用共用記憶體區時,要通過函數 shmdt 斷開與共用記憶體區的串連。該函式宣告在 sys/shm.h 中,其原型如下:

 
  1. #include

  2. #include

  3. int shmdt(const void *shmaddr);

參數 shmaddr 是 shmat 函數的傳回值。

進程脫離共用記憶體區後,資料結構 shmid_ds 中的 shm_nattch 就會減 1 。但是共用段記憶體依然存在,只有 shm_attch 為 0 後,即沒有任何進程再使用該共用記憶體區,共用記憶體區才在核心中被刪除。一般來說,當一個進程終止時,它所附加的共用記憶體區都會自動脫離。

我們通過:

 
  1. 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)則是刪除共用記憶體,徹底不可用,釋放空間。

原文連結:http://www.dcshi.com/?p=79


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.