malloc による overcommit

mallocのバグに関する興味深いエントリーを友達が投稿していたので、再現してみた。

環境

OS: Ubuntu 9.04

$ uname -a
Linux glassonion 2.6.28-11-generic #42-Ubuntu SMP Fri Apr 17 01:57:59 UTC 2009 i686 GNU/Linux

マシン VMware
メモリ 256MB

$ free
total used free shared buffers cached
Mem: 250888 142688 108200 0 1064 26328
-/+ buffers/cache: 115296 135592
Swap: 722884 0 722884

再現手順

1. swapを切る
swapがあると仮想記憶の容量がでかくなりすぎて面倒なので一時的に殺しておく

# swapoff -a -v

2. カーネルオプション変更
vm.overcommit_memory を 1にすることで、無条件で overcommit するようになる。
ちなみに 0 だと、やばそうなら overcommit しない(やばそうの条件は不明)。 2 だと、一切しない。

# sysctl -w vm.overcommit_memory=1

3. サンプルコード

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<string.h>
  4
  5 #define BUFF 1024
  6
  7 int main() {
  8     char *buff;
  9     unsigned int size;
 10
 11     size = BUFF*BUFF;   // 正常にmalloc出来るケース
 12     buff = (char *) malloc(sizeof(char) * size);
 13     fprintf(stderr, "buff: %p, size: %d\n", buff, sizeof(char) * size);
 14     fprintf(stderr, "memseting...\n");
 15     memset(buff, 1, sizeof(char) * size);
 16     system("free");
 17     free(buff);
 18
 19     size = BUFF * BUFF * 250;  // overcommitするケース
 20     buff = (char *) malloc(sizeof(char) * size );
 21     fprintf(stderr, "buff: %p, size: %d\n", buff, sizeof(char)*size);
 22     if(NULL != buff){
 23         fprintf(stderr, "memseting...\n");
 24         memset(buff, 1, sizeof(char) * size);
 25         system("free");
 26     }
 27     free(buff);
 28 }

4. 実行結果
以下、サンプルプログラムの出力結果

$ ./a.out
buff: 0xb7de9008, size: 1048576
memseting...
total used free shared buffers cached
Mem: 250888 143728 107160 0 880 26732
-/+ buffers/cache: 116116 134772
Swap: 0 0 0
buff: 0xa84e9008, size: 262144000
memseting...
Killed

250MBのmallocは見かけ上成功する(nullが返らない)が、その後の memset 中にプロセスがKillされる。
ちなみにsyslogを見ると、

kernel: [ 6901.885898] Out of memory: kill process 7865 (a.out) score 64447 or a child
kernel: [ 6901.885927] Killed process 7865 (a.out)

と出ており、メモリ不足でkillされたことがよく分かる。


5. 結論
mallocはメモリが足りなくてもNULLを返さないことがある は 真である。
こわいこわい。