CMA とは、Contiguous Memory Allocator の略で、
Linux カーネルに実装されているメモリアロケーターの一種です。
今回は、この CMA についてご紹介いたします。
こんな方におすすめ
- そもそも、CMA(Contiguous Memory Allocator)って何か知りたい方
- CMA は何のために用意された仕組みか知りたい方
- CMA はどのような振る舞いをするのか知りたい方
CMA とは?
CMA は Contiguous Memory Allocator の略で、連続的な物理メモリを確保するための仕組みです。
SAMSUNG 社の Marek Szyprowski 氏が提案し、Linux Kernel v3.5 で導入されています。
メモリアドレスが連続した物理メモリが必要となる「組込み向け」のメモリアロケータで、
下図のように DMA(Direct Memory Access)を経由してメモリアクセスを行う場合に必要となります。

物理メモリへのアクセス経路
参考
仮想メモリやMMUについては、「いまさら人には聞けないMMUの仕組み」をご覧ください。
CMA が実装された経緯
従来のメモリ管理

従来のメモリ割り当て
従来の Linux ページアロケータでは、取得可能な連続物理メモリの上限が決まっていました。
必要な連続メモリ、例えばビデオデコード用の巨大バッファの確保などを行う場合は、
ブート時にメモリの一部を静的に確保し、そこに専用のドライバでアクセスする方法を用いていました。

しかし、従来の方法では Kernel のメモリ管理下には無いため、
デコードを行わない時でも他の用途に使えず、メモリが無駄になってしまいます。
CMA によるメモリ管理

CMAメモリ割り当て
従来の「無駄」問題を解決する新しいメモリアロケータとして CMA が登場しました。
任意のメモリ領域を「CMA領域」としてブート時に宣言しておけば、
連続メモリを使用しないときは System Memory(ユーザメモリ、キャッシュ、等)として利用します。
CMA の振る舞い
CMA として宣言した領域から連続したアドレスのメモリを確保する場合、
カーネルはまず、メモリを確保するアドレスと範囲を計算します。
計算したその領域に使用中のメモリがあれば、ページマイグレーションを行って別の領域に追い出します。
マイグレーションを行って全ての領域が未使用になった後、その領域を連続メモリとして割り当てます。

注意ポイント
ここで注意が必要なのは、「ページマイグレーションには時間がかかる」ということです。
これは、連続メモリの確保に時間がかかることを意味します。
マイグレーションに時間がかかる理由としては、主に以下があげられます。
- マイグレーションはメモリの強制立ち退き
確保予定の物理メモリが他の用途で使用されている場合、強制立ち退きさせられます。
つまり、立ち退き先にメモリをコピーする必要があり、時間がかかります。 - マイグレーションはカーネル空間でのみ行われる
ユーザ空間で動くプロセスは、物理アドレスを直接扱うことはできません。
そのため、マイグレーションの処理は全てカーネル空間で行われます。
つまり、確保予定の物理メモリを使用しているプロセスがカーネル空間に戻る必要があります。
【おまけ】Linux Kernel の実装
CMA のソースコードは、以下の場所に格納されています。
Source Path
- mm/cma.c
- mm/page_alloc.c
「mm」とは、Memory Management の略で、他のアロケータも含めてこの場所にコードがあります。
カーネル空間に実装されているデバイスドライバーから呼び出す場合は、
以下のような経路をたどって cma_alloc() が呼び出されます。
Call Tree
dma_alloc_coherent()
⇒ dma_alloc_attrs()
⇒ cma_alloc()
CMA のアロケータは、以下のように実装されています。
前節の「CMA の振る舞い」で紹介した通りの流れになっていることが分かります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
struct page *cma_alloc(struct cma *cma, unsigned long count, unsigned int align, bool no_warn) { unsigned long mask, offset; unsigned long pfn = -1; unsigned long start = 0; unsigned long bitmap_maxno, bitmap_no, bitmap_count; unsigned long i; struct page *page = NULL; int ret = -ENOMEM; if (!cma || !cma->count || !cma->bitmap) goto out; pr_debug("%s(cma %p, count %lu, align %d)\n", __func__, (void *)cma, count, align); if (!count) goto out; trace_cma_alloc_start(cma->name, count, align); mask = cma_bitmap_aligned_mask(cma, align); offset = cma_bitmap_aligned_offset(cma, align); bitmap_maxno = cma_bitmap_maxno(cma); bitmap_count = cma_bitmap_pages_to_bits(cma, count); if (bitmap_count > bitmap_maxno) goto out; for (;;) { spin_lock_irq(&cma->lock); bitmap_no = bitmap_find_next_zero_area_off(cma->bitmap, bitmap_maxno, start, bitmap_count, mask, offset); if (bitmap_no >= bitmap_maxno) { spin_unlock_irq(&cma->lock); break; } bitmap_set(cma->bitmap, bitmap_no, bitmap_count); /* * It's safe to drop the lock here. We've marked this region for * our exclusive use. If the migration fails we will take the * lock again and unmark it. */ spin_unlock_irq(&cma->lock); pfn = cma->base_pfn + (bitmap_no << cma->order_per_bit); ret = alloc_contig_range(pfn, pfn + count, MIGRATE_CMA, GFP_KERNEL | (no_warn ? __GFP_NOWARN : 0)); if (ret == 0) { page = pfn_to_page(pfn); break; } cma_clear_bitmap(cma, pfn, count); if (ret != -EBUSY) break; pr_debug("%s(): memory range at %p is busy, retrying\n", __func__, pfn_to_page(pfn)); trace_cma_alloc_busy_retry(cma->name, pfn, pfn_to_page(pfn), count, align); /* try again with a bit different memory target */ start = bitmap_no + mask + 1; } trace_cma_alloc_finish(cma->name, pfn, page, count, align); /* * CMA can allocate multiple page blocks, which results in different * blocks being marked with different tags. Reset the tags to ignore * those page blocks. */ if (page) { for (i = 0; i < count; i++) page_kasan_tag_reset(page + i); } if (ret && !no_warn) { pr_err_ratelimited("%s: %s: alloc failed, req-size: %lu pages, ret: %d\n", __func__, cma->name, count, ret); cma_debug_show_areas(cma); } pr_debug("%s(): returned %p\n", __func__, page); out: if (page) { count_vm_event(CMA_ALLOC_SUCCESS); cma_sysfs_account_success_pages(cma, count); } else { count_vm_event(CMA_ALLOC_FAIL); if (cma) cma_sysfs_account_fail_pages(cma, count); } return page; } |