あかりラボ

七森中赤座あかり研究室の活動日記

Linuxのmemmap_allocについて

LinuxにはFLATMEMやSPARSEMEMなど,物理メモリを管理するための仕組み(物理メモリモデル)が複数用意されていますが, それらは全てpage構造体(struct page)の配列を利用しています.

各物理メモリモデルの解説については以下を参照してください.

qiita.com

さて,通常の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;
}

nidexact_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のイメージ

memblock_find_in_range_nodeは,memblockの中で使用可能なメモリ領域の中から,予約済み領域を除いた部分からsizeバイトのメモリ領域を探します.

濃い青色の範囲からsizeバイトのメモリ領域を探す

memblock_find_in_range_nodeが呼んでいるfor_each_mem_rangeは,使用可能かつ予約済みでない領域(図では濃い青色の範囲)をイテレートします.