ヒープ領域
ヒープは空いているメモリー空間から動的に獲得するものでが、ユーザメモリー空間に空きが有る限り獲得できるものでありません。ユーザプロセスで使用するmalloc関数は、glibcで実装されており、すなわちユーザプロセスとして実装されています。ヒープ処理としてカーネルはそのヒープと使えるメモリー空間を提供するのみで、ヒープ領域を設定するシステムコールはbrk関数です。
まず、min_brkにヒープ開始アドレスをセットしています。CONFIG_COMPAT_BRKでそのアドレスをmm->end_code(実行コードの終端アドレス)か、mm->start_brk(ヒープ開始アドレス)としているようです。そして拡張するアドレスがそれより小さければエラーです。
rlim = current->signal->rlim[RLIMIT_DATA].rlim_curで、プロセスのデータのリソースの拡張が可能で、ヒープ領域とデータ領域の合計が、rlimより小さいことを確認しています。
PAGE_ALIGNマクロは指定されたアドレスを、ページ単位アライメントします。拡張するページアライメントされたアドレスをnewbrkに、元のアドレスを oldbrk に設定します。
ヒープ領域を縮小する場合if (brk <= mm->brk)、do_munmapで縮小し、縮小したアドレスをmm->brk = brkとしています。拡張の場合、find_vma_intersection関数で実際に拡張できるかチェックします。これはnoldbrk, newbrk+PAGE_SIZE分拡張しても、ほかのメモリーリージョンと重ならないか(重なるというのはすでに利用されている。)チェックします。
すべてOKならdo_brkで無名メモリーリージョンとして確保し、mm->brk = brkとしています。
そこで、以下は詳解LINUXカーネルの柔軟なメモリーリージョンの割付という項目の焼き直しです。ユーザプロセスにスタックの大きさを付けて動作させた場合と、そうでない場合とで、ファイルマッピングメモリーリージョンおよび無名メモリーリージョンの配置が異なると言うものです。このヒープ領域を有効に取得できるようにするためだそうです。
下記のプログラムをスタックの大きさに制限をした場合と、そうでない場合のメモリーリージョンの配置を見てみたいと思います。
まず、min_brkにヒープ開始アドレスをセットしています。CONFIG_COMPAT_BRKでそのアドレスをmm->end_code(実行コードの終端アドレス)か、mm->start_brk(ヒープ開始アドレス)としているようです。そして拡張するアドレスがそれより小さければエラーです。
rlim = current->signal->rlim[RLIMIT_DATA].rlim_curで、プロセスのデータのリソースの拡張が可能で、ヒープ領域とデータ領域の合計が、rlimより小さいことを確認しています。
PAGE_ALIGNマクロは指定されたアドレスを、ページ単位アライメントします。拡張するページアライメントされたアドレスをnewbrkに、元のアドレスを oldbrk に設定します。
ヒープ領域を縮小する場合if (brk <= mm->brk)、do_munmapで縮小し、縮小したアドレスをmm->brk = brkとしています。拡張の場合、find_vma_intersection関数で実際に拡張できるかチェックします。これはnoldbrk, newbrk+PAGE_SIZE分拡張しても、ほかのメモリーリージョンと重ならないか(重なるというのはすでに利用されている。)チェックします。
すべてOKならdo_brkで無名メモリーリージョンとして確保し、mm->brk = brkとしています。
root/mm/mmap.c
SYSCALL_DEFINE1(brk, unsigned long, brk) { unsigned long rlim, retval; unsigned long newbrk, oldbrk; struct mm_struct *mm = current->mm; unsigned long min_brk; down_write(&mm->mmap_sem); #ifdef CONFIG_COMPAT_BRK min_brk = mm->end_code; #else min_brk = mm->start_brk; #endif if (brk < min_brk) goto out; rlim = current->signal->rlim[RLIMIT_DATA].rlim_cur; if (rlim < RLIM_INFINITY && (brk - mm->start_brk) + (mm->end_data - mm->start_data) > rlim) goto out; newbrk = PAGE_ALIGN(brk); oldbrk = PAGE_ALIGN(mm->brk); if (oldbrk == newbrk) goto set_brk; if (brk <= mm->brk) { if (!do_munmap(mm, newbrk, oldbrk-newbrk)) goto set_brk; goto out; } if (find_vma_intersection(mm, oldbrk, newbrk+PAGE_SIZE)) goto out; if (do_brk(oldbrk, newbrk-oldbrk) != oldbrk) goto out; set_brk: mm->brk = brk; out: retval = mm->brk; up_write(&mm->mmap_sem); return retval; }要は拡張するリニアアドレス分のメモリーリージョンとして確保して、そのアドレスをmm->brkにセットするということです。従ってヒープというのは空いているメモリー空間からでなく、mm->start_brkからmm->brkの連続する範囲で確保されるということです。
そこで、以下は詳解LINUXカーネルの柔軟なメモリーリージョンの割付という項目の焼き直しです。ユーザプロセスにスタックの大きさを付けて動作させた場合と、そうでない場合とで、ファイルマッピングメモリーリージョンおよび無名メモリーリージョンの配置が異なると言うものです。このヒープ領域を有効に取得できるようにするためだそうです。
下記のプログラムをスタックの大きさに制限をした場合と、そうでない場合のメモリーリージョンの配置を見てみたいと思います。
#include <stdlib.h> #include <stdio.h> #include <fcntl.h> #include <sys/mman.h> int main() { void mymmap(); mymmap(); while(1) sleep(100); } void mymmap() { int fd = open("a.c",O_RDWR); char *p = mmap(0,10,PROT_READ,MAP_SHARED,fd,0); }スタックに制限を付けないで実行した結果です。a.cをmmapでメモリーマップしたアドレスが40002000-40003000となっています。これらのメモリーリージョンは40000000番地から割り当てられるようになっているようです。そしてヒープは実行コードの後ろの083bb000-083dc000として割り当てられています。結果として、スタック制限を付けない場合、ヒープはコード領域の終了から40000000番地(1G)までの範囲でか確保できません。
[root@localhost kitamura]# ulimit -s unlimited; ./a.out & [1] 24485 [root@localhost kitamura]# cat /proc/24485/maps 00522000-00542000 r-xp 00000000 fd:00 10553 /lib/ld-2.9.so 00543000-00544000 r--p 00020000 fd:00 10553 /lib/ld-2.9.so 00544000-00545000 rw-p 00021000 fd:00 10553 /lib/ld-2.9.so 00547000-006b5000 r-xp 00000000 fd:00 10554 /lib/libc-2.9.so 006b5000-006b7000 r--p 0016e000 fd:00 10554 /lib/libc-2.9.so 006b7000-006b8000 rw-p 00170000 fd:00 10554 /lib/libc-2.9.so 006b8000-006bb000 rw-p 006b8000 00:00 0 08048000-08049000 r-xp 00000000 fd:00 139411 /home/kitamura/a.out 08049000-0804a000 rw-p 00000000 fd:00 139411 /home/kitamura/a.out 083bb000-083dc000 rw-p 083bb000 00:00 0 [heap] 40000000-40001000 r-xp 40000000 00:00 0 [vdso] 40001000-40002000 rw-p 40001000 00:00 0 40002000-40003000 r--s 00000000 fd:00 139603 /home/kitamura/a.c 40011000-40013000 rw-p 40011000 00:00 0 bfac9000-bfade000 rw-p bffeb000 00:00 0 [stack]今度はスタックに制限をつけて動作させると、a.cをmmapでメモリーマップしたアドレスがb78cd000-b78ce000となっていて、ちょうどスタックの上から配置されるようです。ヒープは実行コードの後ろの08af8000-08b19000に割り当てられています。この場合ヒープは実行コードの後ろから上位に向かって、スタックおよびメモリーマップ等(下位に向かって伸びる。)で使用していないメモリー空間すべてを確保することができると言うことです。
[root@localhost kitamura]# ulimit -s 100; ./a.out & [1] 24481 [root@localhost kitamura]# cat /proc/24481/maps 00522000-00542000 r-xp 00000000 fd:00 10553 /lib/ld-2.9.so 00543000-00544000 r--p 00020000 fd:00 10553 /lib/ld-2.9.so 00544000-00545000 rw-p 00021000 fd:00 10553 /lib/ld-2.9.so 00547000-006b5000 r-xp 00000000 fd:00 10554 /lib/libc-2.9.so 006b5000-006b7000 r--p 0016e000 fd:00 10554 /lib/libc-2.9.so 006b7000-006b8000 rw-p 00170000 fd:00 10554 /lib/libc-2.9.so 006b8000-006bb000 rw-p 006b8000 00:00 0 009e1000-009e2000 r-xp 009e1000 00:00 0 [vdso] 08048000-08049000 r-xp 00000000 fd:00 139411 /home/kitamura/a.out 08049000-0804a000 rw-p 00000000 fd:00 139411 /home/kitamura/a.out 08af8000-08b19000 rw-p 08af8000 00:00 0 [heap] b78bd000-b78bf000 rw-p b78bd000 00:00 0 b78cd000-b78ce000 r--s 00000000 fd:00 139603 /home/kitamura/a.c b78ce000-b78cf000 rw-p b78ce000 00:00 0 bfe9b000-bfeb0000 rw-p bffeb000 00:00 0 [stack]