oom_killer

Limited Memorymysql

今天在虛擬機裏面用Word處理文檔的時候,忽然硬盤燈一陣狂閃,而後虛擬機就一塊兒消失了。sql

這種事情家常便飯,很明顯是Linux內核把佔用最多內存的程序(此次是VirtualBox)終止掉了,而硬盤燈爲何會狂閃呢?這是由於在內存 用光以前,Linux的pdflush會把dirty pages寫回磁盤上騰出內存給其餘程序用。這段時間系統幾乎處於不可用狀態,Annoying! :evil:小程序

oom_killer工具

默認配置下,當沒有內存能夠用而又要用到內存時,Linux內核的oom_killer(out of memory killer)會掃描一遍佔用內存最多的程序(可能有多個,好比VirtualBox和Firefox一塊兒悲劇),並把它們結束掉。spa

這種掃描其實代價仍是挺大的,能夠選擇讓oom_killer不要掃描出用內存最多的進程,只是解決掉申請內存的那些進程:操作系統

sysctl -w vm.oom_kill_allocating_task = 1code

使用這樣的設置,oom_killer不會去花時間尋找佔內存最多的進程殺掉,VirtualBox和Firefox就有必定機會倖免。可是在內存 用完的那一瞬間,誰去申請或者使用一片空白的內存,誰就會悲劇,並且多是幾個進程一塊兒被殺掉,充滿了不可預測性(好比,Xorg被殺掉,因而許多程序連 帶就掛掉了,再好比後臺的mysqld被殺掉也會帶來許多不方便),並且也沒有避免pdflush在最後關頭讓硬盤燈狂閃的狀況。orm

總之,oom_killer很不和諧,最好不要讓它出場。在這一點上,openSolaris彷佛作的就比較好,從外表上看,在內存不夠的時候,系 統不會去主動殺掉正在運行的程序,而是拒絕運行新的程序,而且運行中的程序若是申請內存的話就會被暫停,直至有內存能夠給它的時候才繼續運行。進程

overcommit內存

那麼系統爲何不能提早檢測到內存用完呢,malloc是有可能返回NULL的啊?如今的操做系統中,容許過度地申請內存,若是隻是申請內存而沒有實際使用的話,能夠申請到比實際內存大許多的空間(好比用malloc申請內存,while(1) malloc(x);這樣的程序均可以運行好長時間),只有一旦開始用(好比用memset去填),纔會計入真正的內存使用,這時候若是內存真的不夠了,那麼oom_killer就上場了。

目前的Linux提供了一些選項用來調整這種內存策略 :-)

默認狀況下,vm.overcommit_memory = 0,這時候能夠申請到比較多的內存,可是仍然會在必定的時候申請失敗。

還有更寬鬆一些的,若是 vm.overcommit_memory = 1,全部的malloc都會無條件成功 8-O 至關可怕的世界。

最後一種選擇就是這個了:

sysctl -w vm.overcommit_memory = 2

這時候,對申請內存總數有嚴格的限制,malloc會在超過限制的時候返回NULL,應用程序能夠適當處理這種狀況,而oom_killer不再會蹦出來了,pdflush也不會讓硬盤轉得系統沒響應,若是一個程序不能適當處理這種狀況,就當即掛掉,乾淨利落。

可是這也有壞處,這時候參數vm.overcommit_ratio也會起做用,默認是50,意思是隻能分配到實際物理內存的50%。若是沒有交換區的話,overcommit_ratio設置得小就會很悲劇,幾乎什麼都作不了。

那把它設置成100,事情就很是和諧了?沒有這樣簡單,這裏的限制是申請內存總數的限制,若是申請了卻沒有實際用到的話,也是計入總數的。這樣的話,實際內存沒有用完,程序也頗有可能申請不到內存,有一些內存就被浪費了。

雖然overcommit_ratio能夠被設置成大於100的數,可是到底設置成多少確是個棘手的問題,設置大了,就和沒有限制同樣,內存用完時硬盤會狂轉,系統會失去響應一段時間,oom_killer有可能會上場,設置小了,有可能幾百兆的內存被白白浪費了:-|


檢查內存信息能夠看到:

% cat /proc/meminfo
MemTotal: 2064616 kB
MemFree: 1556672 kB
....
CommitLimit: 2064616 kB
Committed_AS: 769068 kB
....

其中Committed_AS是程序申請的內存總和,不能超過CommitLimit。很明顯地看到Committed_AS+MemFree比MemTotal大,看起來把CommitLimit設置成Committed_AS+MemFree比較合適。

不過這個時候,CommitLimit是隻受overcommit_ratio影響的,內存使用狀態在動態變化,只好寫一個程序來動態修改overcommit_ratio了。

最後我寫了這樣的一段C的小程序,每一秒設置一次overcommit_ratio。在目前版本的Linux內核(2.6.31)上i686平臺可用:

#define _GNU_SOURCE 1
 
#include <unistd .h>
#include <stdio .h>
#include <stdlib .h>
#include <err .h>
#include <errno .h>
#include <string .h>
 
#define errexit(status, info) fprintf(stderr, "%s: %s\n", program_invocation_name, info), exit(status);
 
FILE *fp_meminfo;
 
void set_overcommitted_limit(int value)
{
char new_value[32];
int old_len = 0;
FILE *fp_overcommit_ratio;
 
if (!(fp_overcommit_ratio = fopen("/proc/sys/vm/overcommit_ratio", "w")))
err(-4, "can't write /proc/sys/vm/overcommit_ratio");
fprintf(fp_overcommit_ratio, "%d", value);
 
fclose(fp_overcommit_ratio);
}
 
int main(int argc, char const* argv[])
{
char item_name[32];
int item_value;
 
int mem_free, mem_total, committed_as, buffers, cached, item_count, i;
 
char essential_names[][32] = {"MemTotal", "MemFree", "Committed_AS", "Cached"};
int essential_values[sizeof(essential_names) / sizeof(essential_names[0])];
 
for(;;sleep(1)) {
if (!(fp_meminfo = fopen("/proc/meminfo", "r")))
err(-2, "can't read /proc/meminfo");
 
for (memset(essential_values, -1, sizeof(essential_values)),
item_count = sizeof(essential_values) / sizeof(essential_values[0]);
item_count; ) {
 
if (feof(fp_meminfo)) errexit(-3, "can't read all essential information");
 
fscanf(fp_meminfo, " %31[^:]%*[^ ]%d%*[^\n]", item_name, &item_value);
 
for(i = 0; i < sizeof(essential_values) / sizeof(essential_values[0]); i++) {
if (essential_values[i] == -1 && 
strcmp(essential_names[i], item_name) == 0) {
essential_values[i] = item_value;
--item_count;
break;
}
}
}
 
set_overcommitted_limit(
(essential_values[1] + essential_values[2] + essential_values[3] / 3)
* 100 / essential_values[0] + 1);
 
fclose(fp_meminfo);
}
 
return 0;
}

這段程序須要管理員權限運行,把它設置成開機必執行工具之一,同時在/etc/sysctl.conf上加上相關設置,就十分和諧啦 :-P

就這一方面,可能仍是openSolaris的作法最和諧,不過僅僅這一點並不能讓我投奔openSolaris,把它做爲平常使用的系統。默默地期待openSolaris和Linux都愈來愈好吧~

本站公眾號
   歡迎關注本站公眾號,獲取更多信息