LinuxにはFLATMEMやSPARSEMEMなど,物理メモリを管理するための仕組み(物理メモリモデル)が複数用意されていますが,
それらは全てpage構造体(struct page
)の配列を利用しています.
各物理メモリモデルの解説については以下を参照してください.
さて,通常のLinuxの物理メモリ割り当て関数(alloc_pages
など)はこの物理メモリモデルに依存しているわけですが,
物理メモリモデルの実装に利用されるpage構造体の配列を格納するための物理メモリ領域はどのように割り当てられているのでしょうか?
当然alloc_pages
などは利用できないので,それ専用の物理メモリ割り当て関数が用意されています.それがmemmap_alloc
です.
memmap_alloc
は以下のように実装されています.
void __init *memmap_alloc(phys_addr_t size, phys_addr_t align, phys_addr_t min_addr, int nid, bool exact_nid) { void *ptr; if (exact_nid) ptr = memblock_alloc_exact_nid_raw(size, align, min_addr, MEMBLOCK_ALLOC_ACCESSIBLE, nid); else ptr = memblock_alloc_try_nid_raw(size, align, min_addr, MEMBLOCK_ALLOC_ACCESSIBLE, nid); if (ptr && size > 0) page_init_poison(ptr, size); return ptr; }
nid
とexact_nid
はNUMAノード関係なので,一旦無視してmemblock_alloc_try_nid_raw
を見ます.
void * __init memblock_alloc_try_nid_raw( phys_addr_t size, phys_addr_t align, phys_addr_t min_addr, phys_addr_t max_addr, int nid) { memblock_dbg("%s: %llu bytes align=0x%llx nid=%d from=%pa max_addr=%pa %pS\n", __func__, (u64)size, (u64)align, nid, &min_addr, &max_addr, (void *)_RET_IP_); return memblock_alloc_internal(size, align, min_addr, max_addr, nid, false); }
memblock_alloc_internal
を呼び出しているだけのようです.
static void * __init memblock_alloc_internal( phys_addr_t size, phys_addr_t align, phys_addr_t min_addr, phys_addr_t max_addr, int nid, bool exact_nid) { phys_addr_t alloc; /* * Detect any accidental use of these APIs after slab is ready, as at * this moment memblock may be deinitialized already and its * internal data may be destroyed (after execution of memblock_free_all) */ if (WARN_ON_ONCE(slab_is_available())) return kzalloc_node(size, GFP_NOWAIT, nid); if (max_addr > memblock.current_limit) max_addr = memblock.current_limit; alloc = memblock_alloc_range_nid(size, align, min_addr, max_addr, nid, exact_nid); /* retry allocation without lower limit */ if (!alloc && min_addr) alloc = memblock_alloc_range_nid(size, align, 0, max_addr, nid, exact_nid); if (!alloc) return NULL; return phys_to_virt(alloc); }
memblock_alloc_range_nid
で割り当てられたメモリ領域の先頭物理アドレスを仮想アドレスに変換して返していることが分かります.
memblock_alloc_range_nid
の中では,ここが実際の割り当て処理となります.
found = memblock_find_in_range_node(size, align, start, end, nid, flags); if (found && !memblock_reserve(found, size)) goto done;
memblock_find_in_range_node
で使用可能なメモリブロックの中から[start, end]
の範囲でalign
バイトにアラインメントされたsize
バイトのメモリ領域を探します.見つかったらmemblock_reserve
でその領域を予約済みにマークします.
ここで,Linuxのメモリブロックについて少し説明します.
struct memblock
は以下のように定義され,Linuxが使用可能なメモリ領域を表すmemory
,予約済みメモリ領域を表すreserved
を持っています.
これらはx86ならe820,aarch64ならdevicetreeなどで取得したメモリマップから構築されます.
struct memblock { bool bottom_up; phys_addr_t current_limit; struct memblock_type memory; struct memblock_type reserved; #ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP struct memblock_type physmem; #endif };
struct memblock_type
はメモリ範囲を表すstruct memblock_region
の配列で定義されています.以上を踏まえ,memblock
のイメージ図を描いてみます.
memblock_find_in_range_node
は,memblockの中で使用可能なメモリ領域の中から,予約済み領域を除いた部分からsize
バイトのメモリ領域を探します.
memblock_find_in_range_node
が呼んでいるfor_each_mem_range
は,使用可能かつ予約済みでない領域(図では濃い青色の範囲)をイテレートします.