ファイルマップピング/無名メモリーリージョンの獲得
Rev.7を表示中。最新版はこちら。
ヒープ領域でファイルマップピング/無名メモリーリージョンはスタックの取り扱いにより、アロケーションが異なっていることが分かりました。実装の面からその辺りを調べてみました。メモリーリージョン獲得はdo_mmapで行われます。そこからdo_mmap_pgoffをコールし、そこからget_unmapped_area_prot関数で実際のメモリーリージョンを確保しています。unsigned long get_unmapped_area_prot( struct file *file, unsigned long addr, unsigned long len, unsigned long pgoff, unsigned long flags, int exec) { unsigned long (*get_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); if (exec && current->mm->get_unmapped_exec_area) get_area = current->mm->get_unmapped_exec_area; else get_area = current->mm->get_unmapped_area; if (file && file->f_op && file->f_op->get_unmapped_area) get_area = file->f_op->get_unmapped_area; addr = get_area(file, addr, len, pgoff, flags); if (IS_ERR_VALUE(addr)) return addr; if (addr > TASK_SIZE - len) return -ENOMEM; if (addr & ~PAGE_MASK) return -EINVAL; return arch_rebalance_pgtables(addr, len); }ファイルマッピング、無名メモリーリージョンで処理する関数が異なります。get_unmapped_area_protでは、まずそのコールバック関数を設定します。ファイルメモリマップではファイルディスクリプターのオペレーションから(file->f_op->get_unmapped_area)、無名メモリーリージョンではcurrent->mmのget_unmapped_exec_areaないしmm->get_unmapped_areaを、から取得しています。そして、そのどれかを関数型ローカル変数get_areaに設定し、それをコールすることでメモリーリージョンを確保すると言う具合です。
(get_unmapped_exec_areaは、exec-shieldを考慮してのメモリリージョンの獲得かと思いますがよく分かりません。)
ではcurrent->mm->get_unmapped_exec_area/current->mm->get_unmapped_areaはどこで設定されるかということですが、これはプロセスのロード関数load_elf_binaryから、arch_pick_mmap_layout関数を呼び出すことで設定されているようです。
root/arch/x86/mm/mmap.c void arch_pick_mmap_layout(struct mm_struct *mm) { if (!(2 & exec_shield) && mmap_is_legacy()) { mm->mmap_base = mmap_legacy_base(); mm->get_unmapped_area = arch_get_unmapped_area; mm->unmap_area = arch_unmap_area; } else { mm->mmap_base = mmap_base(); mm->get_unmapped_area = arch_get_unmapped_area_topdown; if (!(current->personality & READ_IMPLIES_EXEC) && mmap_is_ia32()) mm->get_unmapped_exec_area = arch_get_unmapped_exec_area; mm->unmap_area = arch_unmap_area_topdown; } }mmap_is_legacy関数でスタックサイズの指定によりどちらのメモリ獲得関数を適用するか判断しています。スタックサイズが無限(current->signal->rlim[RLIMIT_STACK].rlim_cur == RLIM_INFINITY)の場合、従来型ということです。この場合arch_get_unmapped_areaが、そうで無い場合arch_get_unmapped_area_topdownが設定されることになります。
static int mmap_is_legacy(void) { : if (current->signal->rlim[RLIMIT_STACK].rlim_cur == RLIM_INFINITY) return 1; : }mm->mmap_baseはメモリーマップ割り当てときのベースとなるアドレスです。従来型の場合mmap_legacy_baseを、そうでない場合、mmap_baseで設定しています。mmap_legacy_baseは基本的にはTASK_UNMAPPED_BASEを返しているだけです。これはシステムに依存する値ですが、X86ではタスクサイズ(3G)の1/3、すなわち0x40000000ということです。
define TASK_UNMAPPED_BASE (PAGE_ALIGN(TASK_SIZE / 3)) static unsigned long mmap_legacy_base(void) { if (mmap_is_ia32()) return TASK_UNMAPPED_BASE; else return TASK_UNMAPPED_BASE + mmap_rnd(); }レガシーでないmm->mmap_baseは、スタックサイズからギャップ分の範囲を考慮した値を、TASK_SIZE(0xc0000000)- gapとして返しています。
#define MAX_GAP (TASK_SIZE/6*5) static unsigned long mmap_base(void) { unsigned long gap = current->signal->rlim[RLIMIT_STACK].rlim_cur; if (gap < MIN_GAP) gap = MIN_GAP; else if (gap > MAX_GAP) gap = MAX_GAP; return PAGE_ALIGN(TASK_SIZE - gap - mmap_rnd()); }すなわちmmap_legacy_base/mmap_baseで、従来型は0x40000000から、そうでない場合スタックの上からメモリリージョンが獲得されていくわけです。
mmap_rndはcurrent->flags & PF_RANDOMIZEなら、乱数から得られた値を上乗せしてその値としているようですが、理由は分かりません。
なおメモリーリージョン獲得において、従来型の場合arch_get_unmapped_areaが、そうでない場合arch_get_unmapped_area_topdownが呼ばれるわけですが、その獲得ベースとなるmm->mmap_baseは、arch_get_unmapped_areaでは参照されていないようで、arch_get_unmapped_areaでは、find_start_endから検索開始アドレスと終了アドレ取得し、その範囲内でメモリーリージョンを確保しているようです。そしてfind_start_endはその値をmm->mmap_baseからでなく、固定的に返しているようです。
static void find_start_end(unsigned long flags, unsigned long *begin, unsigned long *end) { if (!test_thread_flag(TIF_IA32) && (flags & MAP_32BIT)) { unsigned long new_begin; *begin = 0x40000000; *end = 0x80000000; if (current->flags & PF_RANDOMIZE) { new_begin = randomize_range(*begin, *begin + 0x02000000, 0); if (new_begin) *begin = new_begin; } } else { *begin = TASK_UNMAPPED_BASE; *end = TASK_SIZE; } }