本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私から学べます.
前回を読んでいない方はこちらからどうぞ.
Linuxカーネルの記事一覧はこちらからどうぞ.
LinuxカーネルはC言語で書かれています.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
今回のテーマはメモリ管理です.
メモリ管理を理解することで,カーネルレベルのメモリの確保と解放する方法がわかります.
目次
ページ(Page)とゾーン(Zone)
メモリ管理で必要となるページ(Page)とゾーン(Zone)を紹介します.
ページ
メモリは物理的なページまたはフレームに分割されます(下図).
ページはカーネルにおける基本的な管理単位です.
ページサイズはマシンに依存します.
メモリ管理ユニット(MMU)のサポートによって決定されます.
一般的には4KB,2MBや1GBのものもあります.
私のLinux環境で「getconf PAGESIZE」と入力すると,ページサイズが4096(4KB)であることがわかります.
1 2 |
$ getconf PAGESIZE 4096 |
各物理ページは,linux/include/linux/mm_types.hで定義されているpage構造体で管理されます.
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 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
/* * Each physical page in the system has a struct page associated with * it to keep track of whatever it is we are using the page for at the * moment. Note that we have no way to track which tasks are using * a page, though if it is a pagecache page, rmap structures can tell us * who is mapping it. * * If you allocate the page using alloc_pages(), you can use some of the * space in struct page for your own purposes. The five words in the main * union are available, except for bit 0 of the first word which must be * kept clear. Many users use this word to store a pointer to an object * which is guaranteed to be aligned. If you use the same storage as * page->mapping, you must restore it to NULL before freeing the page. * * If your page will not be mapped to userspace, you can also use the four * bytes in the mapcount union, but you must call page_mapcount_reset() * before freeing it. * * If you want to use the refcount field, it must be used in such a way * that other CPUs temporarily incrementing and then decrementing the * refcount does not cause problems. On receiving the page from * alloc_pages(), the refcount will be positive. * * If you allocate pages of order > 0, you can use some of the fields * in each subpage, but you may need to restore some of their values * afterwards. * * SLUB uses cmpxchg_double() to atomically update its freelist and * counters. That requires that freelist & counters be adjacent and * double-word aligned. We align all struct pages to double-word * boundaries, and ensure that 'freelist' is aligned within the * struct. */ #ifdef CONFIG_HAVE_ALIGNED_STRUCT_PAGE #define _struct_page_alignment __aligned(2 * sizeof(unsigned long)) #else #define _struct_page_alignment #endif struct page { unsigned long flags; /* Atomic flags, some possibly * updated asynchronously */ /* * Five words (20/40 bytes) are available in this union. * WARNING: bit 0 of the first word is used for PageTail(). That * means the other users of this union MUST NOT use the bit to * avoid collision and false-positive PageTail(). */ union { struct { /* Page cache and anonymous pages */ /** * @lru: Pageout list, eg. active_list protected by * lruvec->lru_lock. Sometimes used as a generic list * by the page owner. */ struct list_head lru; /* See page-flags.h for PAGE_MAPPING_FLAGS */ struct address_space *mapping; pgoff_t index; /* Our offset within mapping. */ /** * @private: Mapping-private opaque data. * Usually used for buffer_heads if PagePrivate. * Used for swp_entry_t if PageSwapCache. * Indicates order in the buddy system if PageBuddy. */ unsigned long private; }; struct { /* page_pool used by netstack */ /** * @pp_magic: magic value to avoid recycling non * page_pool allocated pages. */ unsigned long pp_magic; struct page_pool *pp; unsigned long _pp_mapping_pad; unsigned long dma_addr; union { /** * dma_addr_upper: might require a 64-bit * value on 32-bit architectures. */ unsigned long dma_addr_upper; /** * For frag page support, not supported in * 32-bit architectures with 64-bit DMA. */ atomic_long_t pp_frag_count; }; }; struct { /* slab, slob and slub */ union { struct list_head slab_list; struct { /* Partial pages */ struct page *next; #ifdef CONFIG_64BIT int pages; /* Nr of pages left */ int pobjects; /* Approximate count */ #else short int pages; short int pobjects; #endif }; }; struct kmem_cache *slab_cache; /* not slob */ /* Double-word boundary */ void *freelist; /* first free object */ union { void *s_mem; /* slab: first object */ unsigned long counters; /* SLUB */ struct { /* SLUB */ unsigned inuse:16; unsigned objects:15; unsigned frozen:1; }; }; }; struct { /* Tail pages of compound page */ unsigned long compound_head; /* Bit zero is set */ /* First tail page only */ unsigned char compound_dtor; unsigned char compound_order; atomic_t compound_mapcount; unsigned int compound_nr; /* 1 << compound_order */ }; struct { /* Second tail page of compound page */ unsigned long _compound_pad_1; /* compound_head */ atomic_t hpage_pinned_refcount; /* For both global and memcg */ struct list_head deferred_list; }; struct { /* Page table pages */ unsigned long _pt_pad_1; /* compound_head */ pgtable_t pmd_huge_pte; /* protected by page->ptl */ unsigned long _pt_pad_2; /* mapping */ union { struct mm_struct *pt_mm; /* x86 pgds only */ atomic_t pt_frag_refcount; /* powerpc */ }; #if ALLOC_SPLIT_PTLOCKS spinlock_t *ptl; #else spinlock_t ptl; #endif }; struct { /* ZONE_DEVICE pages */ /** @pgmap: Points to the hosting device page map. */ struct dev_pagemap *pgmap; void *zone_device_data; /* * ZONE_DEVICE private pages are counted as being * mapped so the next 3 words hold the mapping, index, * and private fields from the source anonymous or * page cache page while the page is migrated to device * private memory. * ZONE_DEVICE MEMORY_DEVICE_FS_DAX pages also * use the mapping, index, and private fields when * pmem backed DAX files are mapped. */ }; /** @rcu_head: You can use this to free a page by RCU. */ struct rcu_head rcu_head; }; union { /* This union is 4 bytes in size. */ /* * If the page can be mapped to userspace, encodes the number * of times this page is referenced by a page table. */ atomic_t _mapcount; /* * If the page is neither PageSlab nor mappable to userspace, * the value stored here may help determine what this page * is used for. See page-flags.h for a list of page types * which are currently stored here. */ unsigned int page_type; unsigned int active; /* SLAB */ int units; /* SLOB */ }; /* Usage count. *DO NOT USE DIRECTLY*. See page_ref.h */ atomic_t _refcount; #ifdef CONFIG_MEMCG unsigned long memcg_data; #endif /* * On machines where all RAM is mapped into kernel address space, * we can simply calculate the virtual address. On machines with * highmem some memory is mapped into kernel virtual memory * dynamically, so we need a place to store that address. * Note that this field could be 16 bits on x86 ... ;) * * Architectures with slow multiplication can define * WANT_PAGE_VIRTUAL in asm/page.h */ #if defined(WANT_PAGE_VIRTUAL) void *virtual; /* Kernel virtual address (NULL if not kmapped, ie. highmem) */ #endif /* WANT_PAGE_VIRTUAL */ #ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS int _last_cpupid; #endif } _struct_page_alignment; |
カーネルはpage構造体を使って,ページの所有者を追跡します.
ページの所有者は,ユーザ空間プロセス,カーネルが静的/動的割り当てしたデータ,ページキャッシュ等です.
物理メモリページごとに1つのpage構造体のオブジェクトが存在します.
「sizeof(struct page)」は64バイトです.
また,8GBのメモリと4Kサイズのページの場合,128MBがpage構造体オブジェクト用に確保されます.
メモリ全体の使用量の割合は128MB / 8GB ≒ 1.5%,page構造体の要素数は128MB / 64 = 2MBです.
ゾーン
ハードウェアの制限により,特定のコンテキストで特定の物理ページが必要な場合があります.
デバイスによっては,下位16MBの物理メモリ(下位アドレスのメモリ)にしかアクセスできない場合,ハイメモリ(上位アドレスのメモリ)はアクセスする前にマッピングされる必要があります.
そこで,物理メモリを同じ制約を持つゾーンに分割することで解決します.
ゾーンのレイアウトは,アーキテクチャとマシンに依存します.
ページアロケータはページを割り当てる際に制約を考慮します.
ゾーンの名前と説明は下表になります.
ゾーンの名前 | 説明 |
---|---|
ZONE_DMA | ページはDMAに使用可能 |
ZONE_DMA32 | 32ビットDMAデバイス用ページ |
ZONE_NORMAL | 常にアドレス空間にマッピングされるページ |
ZONE_HIGHMEM | アクセスする前にページをマッピングする必要あり |
x86とx86-64のゾーンのレイアウトは下図になります.
各ゾーンは,linux/include/linux/mmzone.hで定義されているzone構造体で管理されます.
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 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
struct zone { /* Read-mostly fields */ /* zone watermarks, access with *_wmark_pages(zone) macros */ unsigned long _watermark[NR_WMARK]; unsigned long watermark_boost; unsigned long nr_reserved_highatomic; /* * We don't know if the memory that we're going to allocate will be * freeable or/and it will be released eventually, so to avoid totally * wasting several GB of ram we must reserve some of the lower zone * memory (otherwise we risk to run OOM on the lower zones despite * there being tons of freeable ram on the higher zones). This array is * recalculated at runtime if the sysctl_lowmem_reserve_ratio sysctl * changes. */ long lowmem_reserve[MAX_NR_ZONES]; #ifdef CONFIG_NUMA int node; #endif struct pglist_data *zone_pgdat; struct per_cpu_pages __percpu *per_cpu_pageset; struct per_cpu_zonestat __percpu *per_cpu_zonestats; /* * the high and batch values are copied to individual pagesets for * faster access */ int pageset_high; int pageset_batch; #ifndef CONFIG_SPARSEMEM /* * Flags for a pageblock_nr_pages block. See pageblock-flags.h. * In SPARSEMEM, this map is stored in struct mem_section */ unsigned long *pageblock_flags; #endif /* CONFIG_SPARSEMEM */ /* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */ unsigned long zone_start_pfn; /* * spanned_pages is the total pages spanned by the zone, including * holes, which is calculated as: * spanned_pages = zone_end_pfn - zone_start_pfn; * * present_pages is physical pages existing within the zone, which * is calculated as: * present_pages = spanned_pages - absent_pages(pages in holes); * * present_early_pages is present pages existing within the zone * located on memory available since early boot, excluding hotplugged * memory. * * managed_pages is present pages managed by the buddy system, which * is calculated as (reserved_pages includes pages allocated by the * bootmem allocator): * managed_pages = present_pages - reserved_pages; * * cma pages is present pages that are assigned for CMA use * (MIGRATE_CMA). * * So present_pages may be used by memory hotplug or memory power * management logic to figure out unmanaged pages by checking * (present_pages - managed_pages). And managed_pages should be used * by page allocator and vm scanner to calculate all kinds of watermarks * and thresholds. * * Locking rules: * * zone_start_pfn and spanned_pages are protected by span_seqlock. * It is a seqlock because it has to be read outside of zone->lock, * and it is done in the main allocator path. But, it is written * quite infrequently. * * The span_seq lock is declared along with zone->lock because it is * frequently read in proximity to zone->lock. It's good to * give them a chance of being in the same cacheline. * * Write access to present_pages at runtime should be protected by * mem_hotplug_begin/end(). Any reader who can't tolerant drift of * present_pages should get_online_mems() to get a stable value. */ atomic_long_t managed_pages; unsigned long spanned_pages; unsigned long present_pages; #if defined(CONFIG_MEMORY_HOTPLUG) unsigned long present_early_pages; #endif #ifdef CONFIG_CMA unsigned long cma_pages; #endif const char *name; #ifdef CONFIG_MEMORY_ISOLATION /* * Number of isolated pageblock. It is used to solve incorrect * freepage counting problem due to racy retrieving migratetype * of pageblock. Protected by zone->lock. */ unsigned long nr_isolate_pageblock; #endif #ifdef CONFIG_MEMORY_HOTPLUG /* see spanned/present_pages for more description */ seqlock_t span_seqlock; #endif int initialized; /* Write-intensive fields used from the page allocator */ ZONE_PADDING(_pad1_) /* free areas of different sizes */ struct free_area free_area[MAX_ORDER]; /* zone flags, see below */ unsigned long flags; /* Primarily protects free_area */ spinlock_t lock; /* Write-intensive fields used by compaction and vmstats. */ ZONE_PADDING(_pad2_) /* * When free pages are below this point, additional steps are taken * when reading the number of free pages to avoid per-cpu counter * drift allowing watermarks to be breached */ unsigned long percpu_drift_mark; #if defined CONFIG_COMPACTION || defined CONFIG_CMA /* pfn where compaction free scanner should start */ unsigned long compact_cached_free_pfn; /* pfn where compaction migration scanner should start */ unsigned long compact_cached_migrate_pfn[ASYNC_AND_SYNC]; unsigned long compact_init_migrate_pfn; unsigned long compact_init_free_pfn; #endif #ifdef CONFIG_COMPACTION /* * On compaction failure, 1<<compact_defer_shift compactions * are skipped before trying again. The number attempted since * last failure is tracked with compact_considered. * compact_order_failed is the minimum compaction failed order. */ unsigned int compact_considered; unsigned int compact_defer_shift; int compact_order_failed; #endif #if defined CONFIG_COMPACTION || defined CONFIG_CMA /* Set to true when the PG_migrate_skip bits should be cleared */ bool compact_blockskip_flush; #endif bool contiguous; ZONE_PADDING(_pad3_) /* Zone statistics */ atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS]; atomic_long_t vm_numa_event[NR_VM_NUMA_EVENT_ITEMS]; } ____cacheline_internodealigned_in_smp; |
メモリアロケータ
上図にメモリアロケータの階層を示します.
本記事では以下を解説します.
- Low-Level Memory Allocator(Buddy System)
- kmalloc/vmalloc関数
- Slab Allocator
Low-Level Memory Allocator(Buddy System)
Low-Level Memory Allocator(Buddy System)は,ページ単位でメモリを確保する低レベルの機構です.
Buddy Systemは,メモリの断片化を防ぐシステムです.
下図のようにBuddy Systemはメモリを管理します.
/proc/buddyinfoでBuddy Systemの情報を表示できます.
私のPC環境での表示結果は以下になります.
1 2 3 4 |
$ cat /proc/buddyinfo Node 0, zone DMA 0 0 0 0 0 0 0 0 0 1 3 Node 0, zone DMA32 1 1 2 1 4 2 4 3 3 1 745 Node 0, zone Normal 636 1112 541 485 508 243 30 11 11 3 735 |
linux/include/linux/gfp.hにあるページ割り当て/解放する関数やマクロは以下になります.
- alloc_pages関数:2のorder乗の個数のページを割り当てて,page構造体のポインタを返す.
- alloc_pageマクロ:2の0乗の個数(1個)のページを割り当てて,page構造体のポインタを返す.
- free_pages関数:2のorder乗の個数のページを解放する.
- free_pageマクロ:2の0乗の個数(1個)のページを解放する.
- __get_free_pages関数:alloc_pages関数と同様に割り当てて,unsigned long型の仮想アドレスを返す.
- __get_free_pageマクロ:alloc_pageマクロと同様に割り当てて,unsigned long型の仮想アドレスを返す.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#ifdef CONFIG_NUMA struct page *alloc_pages(gfp_t gfp, unsigned int order); #else static inline struct page *alloc_pages(gfp_t gfp_mask, unsigned int order) { return alloc_pages_node(numa_node_id(), gfp_mask, order); } #endif #define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0) extern void free_pages(unsigned long addr, unsigned int order); #define free_page(addr) free_pages((addr), 0) extern unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order); #define __get_free_page(gfp_mask) \ __get_free_pages((gfp_mask), 0) |
linux/linux/include/linux/mm.hにあるページアクセス関数は以下になります.
- page_address関数:ページの仮想アドレスを取得する.
1 |
void *page_address(const struct page *page); |
ページデータはデフォルトではクリア(0に初期化)されません.
この場合,ページ割り当てによる情報漏えいの可能性があります.
そこで,情報漏えいを防ぐため,linux/include/linux/gfp.hにあるget_zeroed_page関数を利用してユーザ空間の要求に対して0に初期化されたページを確保します.
1 |
extern unsigned long get_zeroed_page(gfp_t gfp_mask); |
gfp_tはフリーページフラグを管理する型で,メモリ割り当てのためのオプションを指定します.
- メモリの割り当て方法
- どのゾーンからメモリを割り当てるか
- アクションとゾーン修飾子の組み合わせ(これらを直接使用するよりも一般的に好ましい)
linux/incude/linux/gfp.hで定義されている,よく利用するGFPフラグの組み合わせは以下になります.
GFPフラグ | 説明 |
---|---|
GFP_ATOMIC | GFP_ATOMICはスリープできず,割り当てを成功させる必要があります.「アトミックリザーブ」へのアクセスを可能にするために,より低い透かしが適用されます.現在の実装では,NMIと他のいくつかの厳密なノンプリエンプティブコンテキスト (raw_spin_lock等) はサポートされていません. |
GFP_KERNEL | GFP_KERNELは,カーネル内部での割り当ての典型です.呼び出し側には,直接アクセスのためにZONE_NORMALまたはそれより低いゾーンが必要ですが,直接再請求することができます. |
GFP_DMA | GFP_DMAは歴史的な理由で存在しており,可能であれば利用は避けるべきです.GFP_DMAは,呼び出し元が最低ゾーン(x86-64ではZONE_DMAまたは16M)を使用するよう要求していることを示します.理想的には,これは削除されるべきです.一部のユーザはGFP_DMAを利用し,他のユーザはZONE_DMAのローメモリリザーブを回避し,最低ゾーンを緊急リザーブの一種として扱うためにこのフラグを利用します. |
1 2 3 |
#define GFP_ATOMIC (__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM) #define GFP_KERNEL (__GFP_RECLAIM | __GFP_IO | __GFP_FS) #define GFP_DMA __GFP_DMA |
gfp_tの主な使い方は下表になります.
コンテキスト | 利用するgfp_t |
---|---|
プロセスコンテキストでスリープできる場合 | GFP_KERNEL |
プロセスコンテキストでスリープできない場合 | GFP_ATOMIC |
割り込みハンドラ | GFP_ATOMIC |
softirq,tasklet | GFP_ATOMIC |
DMA可能でスリープできる場合 | GFP_DMA | GFP_KERNEL |
DMA可能でスリープできない場合 | GFP_DMA | GFP_ATOMIC |
Low-Level Memory Allocatorのコード一式はこちらからダウンロードして下さい.
Low-Level Memory Allocatorのコードlow_level_memory_allocator_kernel_module.cは以下になります.
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 |
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/gfp.h> #define PAGES_ORDER_REQUESTED 3 #define INTS_IN_PAGE (PAGE_SIZE / sizeof(int)) unsigned long virt_addr; static int __init low_level_memory_allocator_kernel_module_init(void) { int *int_array; int i; printk("low_level_memory_allocator_kernel_module_init()\n"); virt_addr = __get_free_pages(GFP_KERNEL, PAGES_ORDER_REQUESTED); if (!virt_addr) { printk("Error in allocation\n"); return -1; } int_array = (int *) virt_addr; for (i = 0; i < INTS_IN_PAGE; i++) { int_array[i] = i; } for (i = 0; i < INTS_IN_PAGE; i++) { printk("array[%d] = %d\n", i, int_array[i]); } return 0; } static void __exit low_level_memory_allocator_kernel_module_exit(void) { printk("low_level_memory_allocator_kernel_module_exit()\n"); free_pages(virt_addr, PAGES_ORDER_REQUESTED); } module_init(low_level_memory_allocator_kernel_module_init); module_exit(low_level_memory_allocator_kernel_module_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Hiroyuki Chishiro"); MODULE_DESCRIPTION("Low-Level Memory Allocator Kernel Module"); |
実行結果は以下になります.
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 |
$ make make -C /lib/modules/5.15.0-48-generic/build M=/home/chishiro/c-language/low_level_memory_allocator_kernel_module modules make[1]: Entering directory '/usr/src/linux-headers-5.15.0-48-generic' CC [M] /home/chishiro/c-language/low_level_memory_allocator_kernel_module/low_level_memory_allocator_kernel_module.o MODPOST /home/chishiro/c-language/low_level_memory_allocator_kernel_module/Module.symvers CC [M] /home/chishiro/c-language/low_level_memory_allocator_kernel_module/low_level_memory_allocator_kernel_module.mod.o LD [M] /home/chishiro/c-language/low_level_memory_allocator_kernel_module/low_level_memory_allocator_kernel_module.ko BTF [M] /home/chishiro/c-language/low_level_memory_allocator_kernel_module/low_level_memory_allocator_kernel_module.ko Skipping BTF generation for /home/chishiro/c-language/low_level_memory_allocator_kernel_module/low_level_memory_allocator_kernel_module.ko due to unavailability of vmlinux make[1]: Leaving directory '/usr/src/linux-headers-5.15.0-48-generic' $ sudo insmod low_level_memory_allocator_kernel_module.ko $ sudo rmmod low_level_memory_allocator_kernel_module.ko $ sudo dmesg ... [19310.077690] low_level_memory_allocator_kernel_module_init() [19310.077696] array[0] = 0 [19310.077697] array[1] = 1 [19310.077698] array[2] = 2 [19310.077698] array[3] = 3 [19310.077698] array[4] = 4 [19310.077699] array[5] = 5 [19310.077699] array[6] = 6 [19310.077700] array[7] = 7 [19310.077700] array[8] = 8 [19310.077700] array[9] = 9 ... [19310.078211] array[1020] = 1020 [19310.078211] array[1021] = 1021 [19310.078212] array[1022] = 1022 [19310.078212] array[1023] = 1023 [19314.134562] low_level_memory_allocator_kernel_module_exit() |
Buddy Systemの解説は以下の動画がわかりやすいです.
High Memory
x86では,896MBを超える物理メモリ(High Memory)はカーネルアドレス空間内に恒久的にマッピングされません.
アドレス空間のサイズに制限があり,最大4GBのカーネル/ユーザ空間のメモリがカーネル空間は約1GB(正確には896MB),ユーザ空間は3GBに分割されているためです.
なので,使用する前に,ハイメモリからのページをアドレス空間にマップする必要があります.
linux/include/linux/highmem.hにメモリを恒久的にマップ/アンマップするkmap/kunmap関数があります.
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 |
/** * kmap - Map a page for long term usage * @page: Pointer to the page to be mapped * * Returns: The virtual address of the mapping * * Can only be invoked from preemptible task context because on 32bit * systems with CONFIG_HIGHMEM enabled this function might sleep. * * For systems with CONFIG_HIGHMEM=n and for pages in the low memory area * this returns the virtual address of the direct kernel mapping. * * The returned virtual address is globally visible and valid up to the * point where it is unmapped via kunmap(). The pointer can be handed to * other contexts. * * For highmem pages on 32bit systems this can be slow as the mapping space * is limited and protected by a global lock. In case that there is no * mapping slot available the function blocks until a slot is released via * kunmap(). */ static inline void *kmap(struct page *page); /** * kunmap - Unmap the virtual address mapped by kmap() * @addr: Virtual address to be unmapped * * Counterpart to kmap(). A NOOP for CONFIG_HIGHMEM=n and for mappings of * pages in the low memory area. */ static inline void kunmap(struct page *page); |
linux/include/linux/highmem-internal.hにメモリを一時的にマップ/アンマップするkmap_atomic/kunmap_atomic関数があります.
※kmap_atomic関数ではなくlinux/include/linux/highmem.hにあるkmap_local_page関数を推奨しているので注意して下さい.将来的にはkmap_atomic関数は廃止予定だと思われます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
static inline void *kmap_atomic(struct page *page) { if (IS_ENABLED(CONFIG_PREEMPT_RT)) migrate_disable(); else preempt_disable(); pagefault_disable(); return page_address(page); } /* * Prevent people trying to call kunmap_atomic() as if it were kunmap() * kunmap_atomic() should get the return value of kmap_atomic, not the page. */ #define kunmap_atomic(__addr) \ do { \ BUILD_BUG_ON(__same_type((__addr), struct page *)); \ __kunmap_atomic(__addr); \ } while (0) |
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 |
/** * kmap_local_page - Map a page for temporary usage * @page: Pointer to the page to be mapped * * Returns: The virtual address of the mapping * * Can be invoked from any context. * * Requires careful handling when nesting multiple mappings because the map * management is stack based. The unmap has to be in the reverse order of * the map operation: * * addr1 = kmap_local_page(page1); * addr2 = kmap_local_page(page2); * ... * kunmap_local(addr2); * kunmap_local(addr1); * * Unmapping addr1 before addr2 is invalid and causes malfunction. * * Contrary to kmap() mappings the mapping is only valid in the context of * the caller and cannot be handed to other contexts. * * On CONFIG_HIGHMEM=n kernels and for low memory pages this returns the * virtual address of the direct mapping. Only real highmem pages are * temporarily mapped. * * While it is significantly faster than kmap() for the higmem case it * comes with restrictions about the pointer validity. Only use when really * necessary. * * On HIGHMEM enabled systems mapping a highmem page has the side effect of * disabling migration in order to keep the virtual address stable across * preemption. No caller of kmap_local_page() can rely on this side effect. */ static inline void *kmap_local_page(struct page *page); |
kmalloc/kfree/vmalloc/vfree関数
1 2 3 4 |
void * kmalloc(size_t size, gfp_t flags); void kfree(const void *objp); void * vmalloc(unsigned long size); void vfree(const void *addr); |
kmalloc関数は,malloc関数のカーネル版で,(主に)カーネルでページサイズより小さいオブジェクトのために物理的に連続したメモリを割り当てます.
kmalloc関数は後述するvmalloc関数よりオーバーヘッドが低いため,カーネル内でよく利用されます.
kfree関数は,free関数のカーネル版で,カーネルで割り当てられたメモリを解放します.
vmalloc関数は,仮想的に連続したメモリを割り当てます.
物理的に連続したメモリでない可能性がありますので注意して下さい.
また,物理的に連続したメモリを必要とするI/Oバッファには使用できません.
kmalloc関数で確保するメモリはページサイズが上限なので,vmalloc関数はより大きなメモリを割り当てるために使用されます.
vfree関数は,vmalloc関数で割り当てたメモリを解放します.
kmalloc/kfree関数を利用するコード
kmalloc/kfree関数の利用するコード一式はこちらからダウンロードして下さい.
kmalloc/kfree関数を利用するkmalloc_kernel_module.cは以下になります.
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 |
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/slab.h> static int __init kmalloc_kernel_module_init(void) { size_t i; void *ptr; printk("kmalloc_kernel_module_init()\n"); for (i = 1;; i *= 2) { ptr = kmalloc(i, GFP_KERNEL); if (!ptr) { printk("Error: cannot allocate %zu bytes\n", i); break; } printk("allocate %zu bytes\n", i); kfree(ptr); } return 0; } static void __exit kmalloc_kernel_module_exit(void) { printk("kmalloc_kernel_module_exit()\n"); } module_init(kmalloc_kernel_module_init); module_exit(kmalloc_kernel_module_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Hiroyuki Chishiro"); MODULE_DESCRIPTION("Kmalloc Kernel Module"); |
実行結果は以下になります.
38行目で「[ 423.932226] allocate 4194304 bytes」と表示されているので,「4194304 bytes = 4MB」までメモリ確保できていますが,8MBでメモリ確保に失敗していることがわかります.
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 |
$ make make -C /lib/modules/5.15.0-48-generic/build M=/home/chishiro/c-language/kmalloc_kernel_module modules make[1]: Entering directory '/usr/src/linux-headers-5.15.0-48-generic' CC [M] /home/chishiro/c-language/kmalloc_kernel_module/kmalloc_kernel_module.o MODPOST /home/chishiro/c-language/kmalloc_kernel_module/Module.symvers CC [M] /home/chishiro/c-language/kmalloc_kernel_module/kmalloc_kernel_module.mod.o LD [M] /home/chishiro/c-language/kmalloc_kernel_module/kmalloc_kernel_module.ko BTF [M] /home/chishiro/c-language/kmalloc_kernel_module/kmalloc_kernel_module.ko Skipping BTF generation for /home/chishiro/c-language/kmalloc_kernel_module/kmalloc_kernel_module.ko due to unavailability of vmlinux make[1]: Leaving directory '/usr/src/linux-headers-5.15.0-48-generic' $ sudo insmod kmalloc_kernel_module.ko $ sudo rmmod kmalloc_kernel_module.ko $ sudo dmesg ... [ 423.918611] kmalloc_kernel_module_init() [ 423.918613] allocate 1 bytes [ 423.918613] allocate 2 bytes [ 423.918614] allocate 4 bytes [ 423.918614] allocate 8 bytes [ 423.918614] allocate 16 bytes [ 423.918615] allocate 32 bytes [ 423.918615] allocate 64 bytes [ 423.918616] allocate 128 bytes [ 423.918617] allocate 256 bytes [ 423.918617] allocate 512 bytes [ 423.918618] allocate 1024 bytes [ 423.918618] allocate 2048 bytes [ 423.918619] allocate 4096 bytes [ 423.918621] allocate 8192 bytes [ 423.918624] allocate 16384 bytes [ 423.918627] allocate 32768 bytes [ 423.918930] allocate 65536 bytes [ 423.919362] allocate 131072 bytes [ 423.919379] allocate 262144 bytes [ 423.919411] allocate 524288 bytes [ 423.919495] allocate 1048576 bytes [ 423.919628] allocate 2097152 bytes [ 423.932226] allocate 4194304 bytes [ 423.932233] ------------[ cut here ]------------ [ 423.932234] WARNING: CPU: 3 PID: 4914 at mm/page_alloc.c:5358 __alloc_pages+0x2b0/0x330 [ 423.932238] Modules linked in: kmalloc_kernel_module(OE+) rfcomm bnep intel_rapl_msr vsock_loopback intel_rapl_common vmw_vsock_virtio_transport_common vmw_vsock_vmci_transport vsock crct10dif_pclmul ghash_clmulni_intel aesni_intel snd_ens1371 binfmt_misc crypto_simd snd_ac97_codec cryptd gameport rapl nls_iso8859_1 ac97_bus vmw_balloon snd_pcm snd_seq_midi snd_seq_midi_event snd_rawmidi btusb joydev input_leds btrtl btbcm snd_seq serio_raw btintel bluetooth snd_seq_device snd_timer ecdh_generic ecc snd soundcore vmw_vmci mac_hid sch_fq_codel vmwgfx ttm drm_kms_helper cec rc_core fb_sys_fops syscopyarea sysfillrect sysimgblt ipmi_devintf ipmi_msghandler msr parport_pc ppdev lp parport ramoops pstore_blk reed_solomon pstore_zone drm efi_pstore ip_tables x_tables autofs4 hid_generic mptspi usbhid mptscsih crc32_pclmul psmouse hid ahci mptbase libahci e1000 scsi_transport_spi i2c_piix4 pata_acpi [ 423.932273] CPU: 3 PID: 4914 Comm: insmod Tainted: G OE 5.15.0-48-generic #54-Ubuntu [ 423.932275] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 04/13/2018 [ 423.932276] RIP: 0010:__alloc_pages+0x2b0/0x330 [ 423.932277] Code: 48 8b 04 25 c0 fb 01 00 48 05 10 0d 00 00 41 be 01 00 00 00 48 89 45 b0 e9 45 fe ff ff 41 81 e4 00 20 00 00 0f 85 60 ff ff ff <0f> 0b e9 59 ff ff ff 31 c0 e9 19 fe ff ff f7 c2 00 00 08 00 75 53 [ 423.932278] RSP: 0018:ffff95f8c58f7af8 EFLAGS: 00010246 [ 423.932280] RAX: 0000000000000000 RBX: ffffffff8a9185a0 RCX: 0000000000000000 [ 423.932281] RDX: 0000000000000000 RSI: 000000000000000b RDI: 0000000000040cc0 [ 423.932281] RBP: ffff95f8c58f7b50 R08: 0000000000000370 R09: 0000000000000000 [ 423.932282] R10: fffff153c5a30000 R11: dead000000000122 R12: 0000000000000000 [ 423.932283] R13: 000000000000000b R14: 0000000000000000 R15: ffffffffc07c3000 [ 423.932284] FS: 00007efc20358c40(0000) GS:ffff88edf5ec0000(0000) knlGS:0000000000000000 [ 423.932285] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 423.932285] CR2: 00005615576f6c50 CR3: 0000000109d68005 CR4: 00000000003706e0 [ 423.932307] Call Trace: [ 423.932308] <TASK> [ 423.932311] alloc_pages+0x9e/0x1e0 [ 423.932314] kmalloc_order+0x2f/0xd0 [ 423.932316] kmalloc_order_trace+0x1d/0x90 [ 423.932318] __kmalloc+0x2b1/0x330 [ 423.932320] kmalloc_kernel_module_init+0x2c/0x1000 [kmalloc_kernel_module] [ 423.932322] ? 0xffffffffc07ef000 [ 423.932323] do_one_initcall+0x46/0x1e0 [ 423.932326] ? kmem_cache_alloc_trace+0x19e/0x2e0 [ 423.932328] do_init_module+0x52/0x260 [ 423.932329] load_module+0xacd/0xbc0 [ 423.932331] __do_sys_finit_module+0xbf/0x120 [ 423.932332] __x64_sys_finit_module+0x18/0x20 [ 423.932333] do_syscall_64+0x59/0xc0 [ 423.932335] ? syscall_exit_to_user_mode+0x27/0x50 [ 423.932337] ? __x64_sys_mmap+0x33/0x50 [ 423.932338] ? do_syscall_64+0x69/0xc0 [ 423.932339] ? exit_to_user_mode_prepare+0x37/0xb0 [ 423.932341] ? syscall_exit_to_user_mode+0x27/0x50 [ 423.932343] ? __x64_sys_newfstatat+0x1c/0x30 [ 423.932345] ? do_syscall_64+0x69/0xc0 [ 423.932346] ? irqentry_exit+0x1d/0x30 [ 423.932347] ? exc_page_fault+0x89/0x170 [ 423.932349] entry_SYSCALL_64_after_hwframe+0x61/0xcb [ 423.932351] RIP: 0033:0x7efc20478a3d [ 423.932352] Code: 5b 41 5c c3 66 0f 1f 84 00 00 00 00 00 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d c3 a3 0f 00 f7 d8 64 89 01 48 [ 423.932353] RSP: 002b:00007fff500e3fa8 EFLAGS: 00000246 ORIG_RAX: 0000000000000139 [ 423.932355] RAX: ffffffffffffffda RBX: 0000561557c517c0 RCX: 00007efc20478a3d [ 423.932355] RDX: 0000000000000000 RSI: 00005615576ffcd2 RDI: 0000000000000003 [ 423.932356] RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000 [ 423.932356] R10: 0000000000000003 R11: 0000000000000246 R12: 00005615576ffcd2 [ 423.932357] R13: 0000561557c51760 R14: 00005615576fe888 R15: 0000561557c518e0 [ 423.932358] </TASK> [ 423.932359] ---[ end trace 51dfb29ac802883a ]--- [ 423.932360] Error: cannot allocate 8388608 bytes [ 426.603660] kmalloc_kernel_module_exit() |
vmalloc/vfree関数を利用するコード
vmalloc/vfree関数の利用するコード一式はこちらからダウンロードして下さい.
vmalloc/vfree関数を利用するvmalloc_kernel_module.cは以下になります.
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 |
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/vmalloc.h> static int __init vmalloc_kernel_module_init(void) { unsigned long i; void *ptr; printk("vmalloc_kernel_module_init()\n"); for (i = 1;; i *= 2) { ptr = vmalloc(i); if (!ptr) { printk("Error: cannot allocate %lu bytes\n", i); break; } printk("allocate %lu bytes\n", i); vfree(ptr); } return 0; } static void __exit vmalloc_kernel_module_exit(void) { printk("vmalloc_kernel_module_exit()\n"); } module_init(vmalloc_kernel_module_init); module_exit(vmalloc_kernel_module_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Hiroyuki Chishiro"); MODULE_DESCRIPTION("Vmalloc Kernel Module"); |
実行結果は以下になります.
48行目で「[ 230.475101] allocate 4294967296 bytes」と表示されているので,「4294967296 bytes = 4GB」までメモリ確保できていますが,8GBでメモリ確保に失敗していることがわかります.
※私のLinux環境のメモリが8GBなので,この結果になりましたが,メモリサイズによって結果が変動します.
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 102 103 104 105 106 107 108 109 110 111 112 113 114 |
$ make make -C /lib/modules/5.15.0-48-generic/build M=/home/chishiro/c-language/vmalloc_kernel_module modules make[1]: Entering directory '/usr/src/linux-headers-5.15.0-48-generic' CC [M] /home/chishiro/c-language/vmalloc_kernel_module/vmalloc_kernel_module.o MODPOST /home/chishiro/c-language/vmalloc_kernel_module/Module.symvers CC [M] /home/chishiro/c-language/vmalloc_kernel_module/vmalloc_kernel_module.mod.o LD [M] /home/chishiro/c-language/vmalloc_kernel_module/vmalloc_kernel_module.ko BTF [M] /home/chishiro/c-language/vmalloc_kernel_module/vmalloc_kernel_module.ko Skipping BTF generation for /home/chishiro/c-language/vmalloc_kernel_module/vmalloc_kernel_module.ko due to unavailability of vmlinux make[1]: Leaving directory '/usr/src/linux-headers-5.15.0-48-generic' $ sudo insmod vmalloc_kernel_module.ko $ sudo rmmod vmalloc_kernel_module.ko $ sudo dmesg ... [ 216.167036] vmalloc_kernel_module_init() [ 216.167040] allocate 1 bytes [ 216.167042] allocate 2 bytes [ 216.167043] allocate 4 bytes [ 216.167045] allocate 8 bytes [ 216.167046] allocate 16 bytes [ 216.167047] allocate 32 bytes [ 216.167049] allocate 64 bytes [ 216.167050] allocate 128 bytes [ 216.167051] allocate 256 bytes [ 216.167052] allocate 512 bytes [ 216.167053] allocate 1024 bytes [ 216.167054] allocate 2048 bytes [ 216.167055] allocate 4096 bytes [ 216.167056] allocate 8192 bytes [ 216.167060] allocate 16384 bytes [ 216.167064] allocate 32768 bytes [ 216.167069] allocate 65536 bytes [ 216.167079] allocate 131072 bytes [ 216.167108] allocate 262144 bytes [ 216.167155] allocate 524288 bytes [ 216.167229] allocate 1048576 bytes [ 216.167392] allocate 2097152 bytes [ 216.167737] allocate 4194304 bytes [ 216.168943] allocate 8388608 bytes [ 216.171960] allocate 16777216 bytes [ 216.178542] allocate 33554432 bytes [ 216.191226] allocate 67108864 bytes [ 216.248936] allocate 134217728 bytes [ 216.681547] allocate 268435456 bytes [ 217.546665] allocate 536870912 bytes [ 219.358693] allocate 1073741824 bytes [ 222.911646] allocate 2147483648 bytes [ 230.475101] allocate 4294967296 bytes [ 230.541470] insmod: vmalloc error: size 8589934592, exceeds total pages, mode:0xcc0(GFP_KERNEL), nodemask=(null),cpuset=/,mems_allowed=0 [ 230.541480] CPU: 13 PID: 3421 Comm: insmod Tainted: G OE 5.15.0-48-generic #54-Ubuntu [ 230.541482] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 04/13/2018 [ 230.541483] Call Trace: [ 230.541485] <TASK> [ 230.541487] show_stack+0x52/0x5c [ 230.541491] dump_stack_lvl+0x4a/0x63 [ 230.541494] dump_stack+0x10/0x16 [ 230.541496] warn_alloc+0x138/0x160 [ 230.541501] __vmalloc_node_range+0xa5/0xf0 [ 230.541504] __vmalloc_node+0x4e/0x70 [ 230.541507] ? vmalloc_kernel_module_init+0x27/0x1000 [vmalloc_kernel_module] [ 230.541509] vmalloc+0x21/0x30 [ 230.541511] vmalloc_kernel_module_init+0x27/0x1000 [vmalloc_kernel_module] [ 230.541513] ? 0xffffffffc0785000 [ 230.541514] do_one_initcall+0x46/0x1e0 [ 230.541517] ? kmem_cache_alloc_trace+0x19e/0x2e0 [ 230.541520] do_init_module+0x52/0x260 [ 230.541522] load_module+0xacd/0xbc0 [ 230.541524] __do_sys_finit_module+0xbf/0x120 [ 230.541526] __x64_sys_finit_module+0x18/0x20 [ 230.541527] do_syscall_64+0x59/0xc0 [ 230.541529] ? syscall_exit_to_user_mode+0x27/0x50 [ 230.541531] ? __x64_sys_lseek+0x18/0x20 [ 230.541533] ? do_syscall_64+0x69/0xc0 [ 230.541534] entry_SYSCALL_64_after_hwframe+0x61/0xcb [ 230.541537] RIP: 0033:0x7f18ce249a3d [ 230.541539] Code: 5b 41 5c c3 66 0f 1f 84 00 00 00 00 00 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d c3 a3 0f 00 f7 d8 64 89 01 48 [ 230.541541] RSP: 002b:00007ffc50fc2bd8 EFLAGS: 00000246 ORIG_RAX: 0000000000000139 [ 230.541543] RAX: ffffffffffffffda RBX: 0000563f7f3c67c0 RCX: 00007f18ce249a3d [ 230.541544] RDX: 0000000000000000 RSI: 0000563f7dbebcd2 RDI: 0000000000000003 [ 230.541545] RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000 [ 230.541545] R10: 0000000000000003 R11: 0000000000000246 R12: 0000563f7dbebcd2 [ 230.541546] R13: 0000563f7f3c6760 R14: 0000563f7dbea888 R15: 0000563f7f3c68e0 [ 230.541548] </TASK> [ 230.541548] Mem-Info: [ 230.541551] active_anon:592 inactive_anon:104913 isolated_anon:0 active_file:65251 inactive_file:126818 isolated_file:0 unevictable:0 dirty:65 writeback:0 slab_reclaimable:19101 slab_unreclaimable:28027 mapped:68079 shmem:3930 pagetables:3677 bounce:0 kernel_misc_reclaimable:0 free:1612207 free_pcp:11295 free_cma:0 [ 230.541554] Node 0 active_anon:2368kB inactive_anon:419652kB active_file:261004kB inactive_file:507272kB unevictable:0kB isolated(anon):0kB isolated(file):0kB mapped:272316kB dirty:260kB writeback:0kB shmem:15720kB shmem_thp: 0kB shmem_pmdmapped: 0kB anon_thp: 0kB writeback_tmp:0kB kernel_stack:13568kB pagetables:14708kB all_unreclaimable? no [ 230.541558] Node 0 DMA free:14336kB min:128kB low:160kB high:192kB reserved_highatomic:0KB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:0kB writepending:0kB present:15988kB managed:15360kB mlocked:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB [ 230.541561] lowmem_reserve[]: 0 2887 7831 7831 7831 [ 230.541563] Node 0 DMA32 free:3013988kB min:24868kB low:31084kB high:37300kB reserved_highatomic:0KB active_anon:0kB inactive_anon:864kB active_file:0kB inactive_file:120kB unevictable:0kB writepending:4kB present:3129152kB managed:3027308kB mlocked:0kB bounce:0kB free_pcp:6992kB local_pcp:276kB free_cma:0kB [ 230.541567] lowmem_reserve[]: 0 0 4944 4944 4944 [ 230.541569] Node 0 Normal free:3420504kB min:42584kB low:53228kB high:63872kB reserved_highatomic:0KB active_anon:2368kB inactive_anon:418788kB active_file:261004kB inactive_file:507152kB unevictable:0kB writepending:256kB present:5242880kB managed:5063136kB mlocked:0kB bounce:0kB free_pcp:38188kB local_pcp:2792kB free_cma:0kB [ 230.541572] lowmem_reserve[]: 0 0 0 0 0 [ 230.541574] Node 0 DMA: 0*4kB 0*8kB 0*16kB 0*32kB 0*64kB 0*128kB 0*256kB 0*512kB 0*1024kB 1*2048kB (M) 3*4096kB (M) = 14336kB [ 230.541580] Node 0 DMA32: 14*4kB (UM) 11*8kB (UM) 13*16kB (UM) 11*32kB (UME) 15*64kB (UM) 8*128kB (UM) 18*256kB (UME) 18*512kB (UME) 11*1024kB (UME) 6*2048kB (UE) 726*4096kB (UM) = 3013760kB [ 230.541590] Node 0 Normal: 826*4kB (UME) 519*8kB (UM) 266*16kB (UME) 232*32kB (UME) 222*64kB (UME) 56*128kB (UME) 16*256kB (UM) 12*512kB (UME) 11*1024kB (UM) 10*2048kB (UM) 815*4096kB (UM) = 3420736kB [ 230.541599] Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=1048576kB [ 230.541600] Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=2048kB [ 230.541601] 194920 total pagecache pages [ 230.541602] 0 pages in swap cache [ 230.541602] Swap cache stats: add 0, delete 0, find 0/0 [ 230.541603] Free swap = 2097148kB [ 230.541604] Total swap = 2097148kB [ 230.541604] 2097005 pages RAM [ 230.541604] 0 pages HighMem/MovableOnly [ 230.541605] 70554 pages reserved [ 230.541605] 0 pages hwpoisoned [ 230.541606] Error: cannot allocate 8589934592 bytes [ 241.443350] vmalloc_kernel_module_exit() |
「kmalloc and vmalloc : Linux kernel memory allocation API Limits」の記事でも同様の実験が行われていますので,是非読みましょう!
Slab Allocator
Slab Allocatorは,オブジェクトの効率的なメモリ割り当てを目的としたメモリ管理機構です.
従来の機構と比較して,割り当てと解放に起因する断片化を軽減することができます.
Slab Allocatorは,ある型のデータオブジェクト(Slab Object)を含む割り当てられたメモリを保持し,同じ型のSlab Objectを次に割り当てる際に再利用するために使用されます.
Object Poolのデザインパターンに似ているが,メモリにのみ適用され,他のリソースには適用されません.
データ構造の割り当て/解放は,カーネルで非常に頻繁に行われます.
メモリ割り当てを高速化する方法としてフリーリストを使ったSlab Cacheが採用されています.
フリーリストは,特定の種類のデータ構造に対してあらかじめ割り当てられたメモリのブロックです.
フリーリストから割り当てることはフリーリストの中の要素(element)を選ぶこと,フリーリストの要素を解放することは要素をフリーリストに追加することを意味します(下図).
アドホックなフリーリストの問題点は, グローバルな制御ができないことです.
いつ,どのようにフリーリストを解放するかを管理するのがSlab Allocatorです.
Slab Allocatorは,一般的なアロケーションキャッシュインタフェースを提供し,データ構造型(例:task_struct構造体)のSlabオブジェクトをキャッシュします.
また,データ構造のサイズ,ページサイズ,NUMA,Cache Coloringを考慮します.
上図のようにSlab Cacheは1つまたは複数のSlabを持ちます.
Slabは,物理的に連続した1ページまたは数ページです.
SlabにはSlab Objectが含まれます.
Slabは,empty,partially full,またはfullの場合があります.
メモリの断片化を防ぐために,partially fullのSlab Objectを割り当てます.
/proc/slabinfoにSlabの情報があります.
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 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
$ sudo cat /proc/slabinfo slabinfo - version: 2.1 # name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail> AF_VSOCK 75 75 1280 25 8 : tunables 0 0 0 : slabdata 3 3 0 ext4_groupinfo_4k 1050 1050 192 42 2 : tunables 0 0 0 : slabdata 25 25 0 fsverity_info 0 0 256 32 2 : tunables 0 0 0 : slabdata 0 0 0 fscrypt_info 0 0 136 30 1 : tunables 0 0 0 : slabdata 0 0 0 MPTCPv6 0 0 2048 16 8 : tunables 0 0 0 : slabdata 0 0 0 ip6-frags 0 0 184 44 2 : tunables 0 0 0 : slabdata 0 0 0 PINGv6 0 0 1216 26 8 : tunables 0 0 0 : slabdata 0 0 0 RAWv6 936 936 1216 26 8 : tunables 0 0 0 : slabdata 36 36 0 UDPv6 264 264 1344 24 8 : tunables 0 0 0 : slabdata 11 11 0 tw_sock_TCPv6 0 0 248 33 2 : tunables 0 0 0 : slabdata 0 0 0 request_sock_TCPv6 0 0 304 26 2 : tunables 0 0 0 : slabdata 0 0 0 TCPv6 130 130 2432 13 8 : tunables 0 0 0 : slabdata 10 10 0 kcopyd_job 0 0 3240 10 8 : tunables 0 0 0 : slabdata 0 0 0 dm_uevent 0 0 2888 11 8 : tunables 0 0 0 : slabdata 0 0 0 scsi_sense_cache 1536 1536 128 32 1 : tunables 0 0 0 : slabdata 48 48 0 mqueue_inode_cache 34 34 960 34 8 : tunables 0 0 0 : slabdata 1 1 0 fuse_request 338 338 152 26 1 : tunables 0 0 0 : slabdata 13 13 0 fuse_inode 312 312 832 39 8 : tunables 0 0 0 : slabdata 8 8 0 ecryptfs_inode_cache 0 0 1024 32 8 : tunables 0 0 0 : slabdata 0 0 0 ecryptfs_file_cache 0 0 16 256 1 : tunables 0 0 0 : slabdata 0 0 0 ecryptfs_auth_tok_list_item 0 0 832 39 8 : tunables 0 0 0 : slabdata 0 0 0 fat_inode_cache 84 84 776 42 8 : tunables 0 0 0 : slabdata 2 2 0 fat_cache 0 0 40 102 1 : tunables 0 0 0 : slabdata 0 0 0 squashfs_inode_cache 1794 1794 704 46 8 : tunables 0 0 0 : slabdata 39 39 0 jbd2_journal_head 544 544 120 34 1 : tunables 0 0 0 : slabdata 16 16 0 jbd2_revoke_table_s 256 256 16 256 1 : tunables 0 0 0 : slabdata 1 1 0 ext4_fc_dentry_update 0 0 80 51 1 : tunables 0 0 0 : slabdata 0 0 0 ext4_inode_cache 10017 10017 1176 27 8 : tunables 0 0 0 : slabdata 371 371 0 ext4_allocation_context 448 448 144 28 1 : tunables 0 0 0 : slabdata 16 16 0 ext4_io_end 1024 1024 64 64 1 : tunables 0 0 0 : slabdata 16 16 0 ext4_pending_reservation 2048 2048 32 128 1 : tunables 0 0 0 : slabdata 16 16 0 ext4_extent_status 12138 12138 40 102 1 : tunables 0 0 0 : slabdata 119 119 0 mbcache 1168 1168 56 73 1 : tunables 0 0 0 : slabdata 16 16 0 kioctx 0 0 576 28 4 : tunables 0 0 0 : slabdata 0 0 0 userfaultfd_ctx_cache 0 0 192 42 2 : tunables 0 0 0 : slabdata 0 0 0 dnotify_struct 0 0 32 128 1 : tunables 0 0 0 : slabdata 0 0 0 pid_namespace 0 0 136 30 1 : tunables 0 0 0 : slabdata 0 0 0 UNIX 1050 1050 1088 30 8 : tunables 0 0 0 : slabdata 35 35 0 ip4-frags 0 0 200 40 2 : tunables 0 0 0 : slabdata 0 0 0 MPTCP 0 0 1920 17 8 : tunables 0 0 0 : slabdata 0 0 0 request_sock_subflow 0 0 376 43 4 : tunables 0 0 0 : slabdata 0 0 0 xfrm_dst_cache 0 0 320 25 2 : tunables 0 0 0 : slabdata 0 0 0 xfrm_state 0 0 768 42 8 : tunables 0 0 0 : slabdata 0 0 0 ip_fib_trie 425 425 48 85 1 : tunables 0 0 0 : slabdata 5 5 0 ip_fib_alias 438 438 56 73 1 : tunables 0 0 0 : slabdata 6 6 0 PING 0 0 1024 32 8 : tunables 0 0 0 : slabdata 0 0 0 RAW 1312 1472 1024 32 8 : tunables 0 0 0 : slabdata 46 46 0 tw_sock_TCP 33 33 248 33 2 : tunables 0 0 0 : slabdata 1 1 0 request_sock_TCP 52 52 304 26 2 : tunables 0 0 0 : slabdata 2 2 0 TCP 140 140 2240 14 8 : tunables 0 0 0 : slabdata 10 10 0 hugetlbfs_inode_cache 48 48 664 24 4 : tunables 0 0 0 : slabdata 2 2 0 dquot 512 512 256 32 2 : tunables 0 0 0 : slabdata 16 16 0 ep_head 4096 4096 16 256 1 : tunables 0 0 0 : slabdata 16 16 0 dax_cache 39 39 832 39 8 : tunables 0 0 0 : slabdata 1 1 0 bio_crypt_ctx 306 306 40 102 1 : tunables 0 0 0 : slabdata 3 3 0 request_queue 150 150 2128 15 8 : tunables 0 0 0 : slabdata 10 10 0 biovec-max 192 224 4096 8 8 : tunables 0 0 0 : slabdata 28 28 0 biovec-128 192 192 2048 16 8 : tunables 0 0 0 : slabdata 12 12 0 biovec-64 512 512 1024 32 8 : tunables 0 0 0 : slabdata 16 16 0 khugepaged_mm_slot 0 0 112 36 1 : tunables 0 0 0 : slabdata 0 0 0 user_namespace 26 26 624 26 4 : tunables 0 0 0 : slabdata 1 1 0 dmaengine-unmap-256 15 15 2112 15 8 : tunables 0 0 0 : slabdata 1 1 0 dmaengine-unmap-128 30 30 1088 30 8 : tunables 0 0 0 : slabdata 1 1 0 sock_inode_cache 4290 4290 832 39 8 : tunables 0 0 0 : slabdata 110 110 0 skbuff_ext_cache 672 672 192 42 2 : tunables 0 0 0 : slabdata 16 16 0 skbuff_fclone_cache 512 512 512 32 4 : tunables 0 0 0 : slabdata 16 16 0 skbuff_head_cache 3360 3520 256 32 2 : tunables 0 0 0 : slabdata 110 110 0 file_lock_cache 592 592 216 37 2 : tunables 0 0 0 : slabdata 16 16 0 file_lock_ctx 1168 1168 56 73 1 : tunables 0 0 0 : slabdata 16 16 0 fsnotify_mark_connector 2048 2048 32 128 1 : tunables 0 0 0 : slabdata 16 16 0 buffer_head 21372 21372 104 39 1 : tunables 0 0 0 : slabdata 548 548 0 x86_lbr 0 0 800 40 8 : tunables 0 0 0 : slabdata 0 0 0 taskstats 736 736 352 46 4 : tunables 0 0 0 : slabdata 16 16 0 proc_dir_entry 1428 1428 192 42 2 : tunables 0 0 0 : slabdata 34 34 0 pde_opener 1632 1632 40 102 1 : tunables 0 0 0 : slabdata 16 16 0 proc_inode_cache 3680 3864 712 46 8 : tunables 0 0 0 : slabdata 84 84 0 seq_file 544 544 120 34 1 : tunables 0 0 0 : slabdata 16 16 0 sigqueue 867 867 80 51 1 : tunables 0 0 0 : slabdata 17 17 0 bdev_cache 180 180 1600 20 8 : tunables 0 0 0 : slabdata 9 9 0 shmem_inode_cache 3053 3053 760 43 8 : tunables 0 0 0 : slabdata 71 71 0 kernfs_node_cache 67936 67936 128 32 1 : tunables 0 0 0 : slabdata 2123 2123 0 mnt_cache 3850 3850 320 25 2 : tunables 0 0 0 : slabdata 154 154 0 filp 9501 10560 256 32 2 : tunables 0 0 0 : slabdata 330 330 0 inode_cache 50775 50775 640 25 4 : tunables 0 0 0 : slabdata 2031 2031 0 dentry 92316 92316 192 42 2 : tunables 0 0 0 : slabdata 2198 2198 0 names_cache 128 128 4096 8 8 : tunables 0 0 0 : slabdata 16 16 0 net_namespace 28 28 4352 7 8 : tunables 0 0 0 : slabdata 4 4 0 iint_cache 0 0 120 34 1 : tunables 0 0 0 : slabdata 0 0 0 lsm_file_cache 78805 79220 24 170 1 : tunables 0 0 0 : slabdata 466 466 0 uts_namespace 222 222 432 37 4 : tunables 0 0 0 : slabdata 6 6 0 nsproxy 896 896 72 56 1 : tunables 0 0 0 : slabdata 16 16 0 vm_area_struct 40755 40755 208 39 2 : tunables 0 0 0 : slabdata 1045 1045 0 mm_struct 510 510 1088 30 8 : tunables 0 0 0 : slabdata 17 17 0 files_cache 828 828 704 46 8 : tunables 0 0 0 : slabdata 18 18 0 signal_cache 1036 1036 1152 28 8 : tunables 0 0 0 : slabdata 37 37 0 sighand_cache 840 840 2112 15 8 : tunables 0 0 0 : slabdata 56 56 0 task_struct 881 995 6528 5 8 : tunables 0 0 0 : slabdata 199 199 0 cred_jar 2772 2772 192 42 2 : tunables 0 0 0 : slabdata 66 66 0 anon_vma_chain 25664 25664 64 64 1 : tunables 0 0 0 : slabdata 401 401 0 anon_vma 15422 15778 88 46 1 : tunables 0 0 0 : slabdata 343 343 0 pid 2528 2528 128 32 1 : tunables 0 0 0 : slabdata 79 79 0 Acpi-Operand 12544 12544 72 56 1 : tunables 0 0 0 : slabdata 224 224 0 Acpi-ParseExt 546 546 104 39 1 : tunables 0 0 0 : slabdata 14 14 0 Acpi-State 1632 1632 80 51 1 : tunables 0 0 0 : slabdata 32 32 0 numa_policy 155 155 264 31 2 : tunables 0 0 0 : slabdata 5 5 0 perf_event 27 27 1192 27 8 : tunables 0 0 0 : slabdata 1 1 0 trace_event_file 4002 4002 88 46 1 : tunables 0 0 0 : slabdata 87 87 0 ftrace_event_field 12920 12920 48 85 1 : tunables 0 0 0 : slabdata 152 152 0 pool_workqueue 764 1024 256 32 2 : tunables 0 0 0 : slabdata 32 32 0 radix_tree_node 9044 9044 584 28 4 : tunables 0 0 0 : slabdata 323 323 0 task_group 400 400 640 25 4 : tunables 0 0 0 : slabdata 16 16 0 vmap_area 5056 5056 64 64 1 : tunables 0 0 0 : slabdata 79 79 0 dma-kmalloc-8k 0 0 8192 4 8 : tunables 0 0 0 : slabdata 0 0 0 dma-kmalloc-4k 0 0 4096 8 8 : tunables 0 0 0 : slabdata 0 0 0 dma-kmalloc-2k 0 0 2048 16 8 : tunables 0 0 0 : slabdata 0 0 0 dma-kmalloc-1k 0 0 1024 32 8 : tunables 0 0 0 : slabdata 0 0 0 dma-kmalloc-512 0 0 512 32 4 : tunables 0 0 0 : slabdata 0 0 0 dma-kmalloc-256 0 0 256 32 2 : tunables 0 0 0 : slabdata 0 0 0 dma-kmalloc-128 0 0 128 32 1 : tunables 0 0 0 : slabdata 0 0 0 dma-kmalloc-64 0 0 64 64 1 : tunables 0 0 0 : slabdata 0 0 0 dma-kmalloc-32 0 0 32 128 1 : tunables 0 0 0 : slabdata 0 0 0 dma-kmalloc-16 0 0 16 256 1 : tunables 0 0 0 : slabdata 0 0 0 dma-kmalloc-8 0 0 8 512 1 : tunables 0 0 0 : slabdata 0 0 0 dma-kmalloc-192 0 0 192 42 2 : tunables 0 0 0 : slabdata 0 0 0 dma-kmalloc-96 0 0 96 42 1 : tunables 0 0 0 : slabdata 0 0 0 kmalloc-rcl-8k 0 0 8192 4 8 : tunables 0 0 0 : slabdata 0 0 0 kmalloc-rcl-4k 0 0 4096 8 8 : tunables 0 0 0 : slabdata 0 0 0 kmalloc-rcl-2k 0 0 2048 16 8 : tunables 0 0 0 : slabdata 0 0 0 kmalloc-rcl-1k 0 0 1024 32 8 : tunables 0 0 0 : slabdata 0 0 0 kmalloc-rcl-512 0 0 512 32 4 : tunables 0 0 0 : slabdata 0 0 0 kmalloc-rcl-256 0 0 256 32 2 : tunables 0 0 0 : slabdata 0 0 0 kmalloc-rcl-192 42 42 192 42 2 : tunables 0 0 0 : slabdata 1 1 0 kmalloc-rcl-128 832 832 128 32 1 : tunables 0 0 0 : slabdata 26 26 0 kmalloc-rcl-96 966 966 96 42 1 : tunables 0 0 0 : slabdata 23 23 0 kmalloc-rcl-64 1920 1920 64 64 1 : tunables 0 0 0 : slabdata 30 30 0 kmalloc-rcl-32 0 0 32 128 1 : tunables 0 0 0 : slabdata 0 0 0 kmalloc-rcl-16 0 0 16 256 1 : tunables 0 0 0 : slabdata 0 0 0 kmalloc-rcl-8 0 0 8 512 1 : tunables 0 0 0 : slabdata 0 0 0 kmalloc-cg-8k 60 60 8192 4 8 : tunables 0 0 0 : slabdata 15 15 0 kmalloc-cg-4k 128 128 4096 8 8 : tunables 0 0 0 : slabdata 16 16 0 kmalloc-cg-2k 304 304 2048 16 8 : tunables 0 0 0 : slabdata 19 19 0 kmalloc-cg-1k 960 960 1024 32 8 : tunables 0 0 0 : slabdata 30 30 0 kmalloc-cg-512 672 672 512 32 4 : tunables 0 0 0 : slabdata 21 21 0 kmalloc-cg-256 512 512 256 32 2 : tunables 0 0 0 : slabdata 16 16 0 kmalloc-cg-192 672 672 192 42 2 : tunables 0 0 0 : slabdata 16 16 0 kmalloc-cg-128 512 512 128 32 1 : tunables 0 0 0 : slabdata 16 16 0 kmalloc-cg-96 588 588 96 42 1 : tunables 0 0 0 : slabdata 14 14 0 kmalloc-cg-64 1536 1536 64 64 1 : tunables 0 0 0 : slabdata 24 24 0 kmalloc-cg-32 2048 2048 32 128 1 : tunables 0 0 0 : slabdata 16 16 0 kmalloc-cg-16 9472 9472 16 256 1 : tunables 0 0 0 : slabdata 37 37 0 kmalloc-cg-8 8192 8192 8 512 1 : tunables 0 0 0 : slabdata 16 16 0 kmalloc-8k 216 216 8192 4 8 : tunables 0 0 0 : slabdata 54 54 0 kmalloc-4k 1732 1736 4096 8 8 : tunables 0 0 0 : slabdata 217 217 0 kmalloc-2k 2720 2720 2048 16 8 : tunables 0 0 0 : slabdata 170 170 0 kmalloc-1k 2538 2592 1024 32 8 : tunables 0 0 0 : slabdata 81 81 0 kmalloc-512 28529 28736 512 32 4 : tunables 0 0 0 : slabdata 898 898 0 kmalloc-256 7079 7104 256 32 2 : tunables 0 0 0 : slabdata 222 222 0 kmalloc-192 2394 2394 192 42 2 : tunables 0 0 0 : slabdata 57 57 0 kmalloc-128 2400 2400 128 32 1 : tunables 0 0 0 : slabdata 75 75 0 kmalloc-96 3360 3360 96 42 1 : tunables 0 0 0 : slabdata 80 80 0 kmalloc-64 13824 13824 64 64 1 : tunables 0 0 0 : slabdata 216 216 0 kmalloc-32 46592 46592 32 128 1 : tunables 0 0 0 : slabdata 364 364 0 kmalloc-16 16640 16640 16 256 1 : tunables 0 0 0 : slabdata 65 65 0 kmalloc-8 16384 16384 8 512 1 : tunables 0 0 0 : slabdata 32 32 0 kmem_cache_node 384 384 64 64 1 : tunables 0 0 0 : slabdata 6 6 0 kmem_cache 256 256 256 32 2 : tunables 0 0 0 : slabdata 8 8 0 |
Slab Allocatorのバリエーションは以下になります.
- SLOB(Simple List Of Blocks):初期のLinux(1991年以降)で使用された機構で,メモリフットプリントが小さく,組込みシステムに向いている.
- SLAB:1999年にマージされた機構で,Cache-Friendlyな(キャッシュと親和性が高い)設計である.
- SLUB:2008年にマージされた機構で,メニーコアシステムでSLABよりスケーラビリティが向上する.
linux/include/linux/slab_def.hにSlabを管理するkmem_cache構造体があります.
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 |
/* * Definitions unique to the original Linux SLAB allocator. */ struct kmem_cache { struct array_cache __percpu *cpu_cache; /* 1) Cache tunables. Protected by slab_mutex */ unsigned int batchcount; unsigned int limit; unsigned int shared; unsigned int size; struct reciprocal_value reciprocal_buffer_size; /* 2) touched by every alloc & free from the backend */ slab_flags_t flags; /* constant flags */ unsigned int num; /* # of objs per slab */ /* 3) cache_grow/shrink */ /* order of pgs per slab (2^n) */ unsigned int gfporder; /* force GFP flags, e.g. GFP_DMA */ gfp_t allocflags; size_t colour; /* cache colouring range */ unsigned int colour_off; /* colour offset */ struct kmem_cache *freelist_cache; unsigned int freelist_size; /* constructor func */ void (*ctor)(void *obj); /* 4) cache creation/removal */ const char *name; struct list_head list; int refcount; int object_size; int align; /* 5) statistics */ #ifdef CONFIG_DEBUG_SLAB unsigned long num_active; unsigned long num_allocations; unsigned long high_mark; unsigned long grown; unsigned long reaped; unsigned long errors; unsigned long max_freeable; unsigned long node_allocs; unsigned long node_frees; unsigned long node_overflow; atomic_t allochit; atomic_t allocmiss; atomic_t freehit; atomic_t freemiss; /* * If debugging is enabled, then the allocator can add additional * fields and/or padding to every object. 'size' contains the total * object size including these internal fields, while 'obj_offset' * and 'object_size' contain the offset to the user object and its * size. */ int obj_offset; #endif /* CONFIG_DEBUG_SLAB */ #ifdef CONFIG_KASAN struct kasan_cache kasan_info; #endif #ifdef CONFIG_SLAB_FREELIST_RANDOM unsigned int *random_seq; #endif unsigned int useroffset; /* Usercopy region offset */ unsigned int usersize; /* Usercopy region size */ struct kmem_cache_node *node[MAX_NUMNODES]; }; |
linux/include/linux/slab.hにSlabの操作関数があります.
- kmem_cache_create関数:Slab Cacheを作成します.
- kmem_cache_destroy関数:Slab Cacheを削除します.
- kmem_cache_alloc関数:Slab Objectのメモリを割り当てます.
- kmem_cache_free関数:Slab Objectのメモリを削除します.
1 2 3 4 5 6 7 |
struct kmem_cache *kmem_cache_create(const char *name, unsigned int size, unsigned int align, slab_flags_t flags, void (*ctor)(void *)); void kmem_cache_destroy(struct kmem_cache *); void *kmem_cache_alloc(struct kmem_cache *, gfp_t flags) __assume_slab_alignment __malloc; void kmem_cache_free(struct kmem_cache *, void *); |
Slab Allocatorのコード一式はこちらからダウンロードして下さい.
Slab Allocatorのコードslab_allocator_kernel_module.cは以下になります.
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 |
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/slab.h> struct mydata { int int_param; long long_param; }; static int __init slab_allocator_kernel_module_init(void) { int ret = 0; struct mydata *ptr1, *ptr2; struct kmem_cache *mycache; printk("slab_allocator_kernel_module_init()\n"); mycache = kmem_cache_create("lkp-cache", sizeof(struct mydata), 0, 0, NULL); if (!mycache) { return -1; } ptr1 = kmem_cache_alloc(mycache, GFP_KERNEL); if (!ptr1) { ret = -ENOMEM; goto destroy_cache; } ptr2 = kmem_cache_alloc(mycache, GFP_KERNEL); if (!ptr2) { ret = -ENOMEM; goto freeptr1; } ptr1->int_param = 1; ptr1->long_param = 2; ptr2->int_param = 3; ptr2->long_param = 4; printk("ptr1 = {%d, %ld}, ptr2 = {%d, %ld}\n", ptr1->int_param, ptr1->long_param, ptr2->int_param, ptr2->long_param); kmem_cache_free(mycache, ptr2); freeptr1: kmem_cache_free(mycache, ptr1); destroy_cache: kmem_cache_destroy(mycache); return ret; } static void __exit slab_allocator_kernel_module_exit(void) { printk("slab_allocator_kernel_module_exit()\n"); } module_init(slab_allocator_kernel_module_init); module_exit(slab_allocator_kernel_module_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Hiroyuki Chishiro"); MODULE_DESCRIPTION("Slab Allocator Kernel Module"); |
実行結果は以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$ make make -C /lib/modules/5.15.0-48-generic/build M=/home/chishiro/c-language/slab_allocator_kernel_module modules make[1]: Entering directory '/usr/src/linux-headers-5.15.0-48-generic' CC [M] /home/chishiro/c-language/slab_allocator_kernel_module/slab_allocator_kernel_module.o MODPOST /home/chishiro/c-language/slab_allocator_kernel_module/Module.symvers CC [M] /home/chishiro/c-language/slab_allocator_kernel_module/slab_allocator_kernel_module.mod.o LD [M] /home/chishiro/c-language/slab_allocator_kernel_module/slab_allocator_kernel_module.ko BTF [M] /home/chishiro/c-language/slab_allocator_kernel_module/slab_allocator_kernel_module.ko Skipping BTF generation for /home/chishiro/c-language/slab_allocator_kernel_module/slab_allocator_kernel_module.ko due to unavailability of vmlinux make[1]: Leaving directory '/usr/src/linux-headers-5.15.0-48-generic' $ sudo insmod slab_allocator_kernel_module.ko $ sudo rmmod slab_allocator_kernel_module.ko $ sudo dmesg | tail ... [10230.186590] slab_allocator_kernel_module_init() [10230.186601] ptr1 = {1, 2}, ptr2 = {3, 4} [10233.164662] slab_allocator_kernel_module_exit() |
Slab Allocatorの解説動画はこちらがわかりやすいです.
その他のメモリ管理:CPU毎のデータ構造,スタック
その他のメモリ管理を紹介します.
CPU毎のデータ構造
CPU毎のデータ構造では,各コアが独自の値を持つことが可能です.
ロックが不要で,キャッシュのスラッシングを低減します.
また,各インデックスがCPUのコアIDに対応する配列で実装されます.
linux/include/linux/smp.hに以下が定義されています.
- smp_processor_idマクロ:コアIDを取得する.
- get_cpuマクロ:プリエンプションを無効にしてコアIDを取得する.
- set_cpuマクロ:プリエンプションを有効にする.
1 2 3 4 5 6 7 8 9 |
#ifdef CONFIG_DEBUG_PREEMPT extern unsigned int debug_smp_processor_id(void); # define smp_processor_id() debug_smp_processor_id() #else # define smp_processor_id() __smp_processor_id() #endif #define get_cpu() ({ preempt_disable(); __smp_processor_id(); }) #define put_cpu() preempt_enable() |
スタック
各プロセスは実行用のユーザ空間のスタックとカーネル内実行のためのカーネルスタックを持ちます.
ユーザ空間スタックは大きく,動的に増加します.
カーネルスタックは小さく固定サイズ(2ページ,多くの場合で8KB)です.
割り込みスタックは割り込みハンドラ用で,各コアで1ページです.
カーネルスタックの使用量を最小限に抑えるためには,ローカル変数と関数パラメータが重要になります.
まとめ
今回はメモリ管理を紹介しました.
メモリ管理に関してまとめると以下になります.
- 物理的に連続したメモリが必要な場合は,kmalloc関数またはalloc_pageファミリー関数を利用する.
- 仮想的に連続したメモリが必要な場合は,vmalloc関数を利用する.
- 同じデータ構造を大量に作成/破棄することが多い場合は,Slab Allocatorを利用する.
- ハイメモリ(上位アドレスのメモリ)から割り当てる必要がある場合は,alloc_pageファミリー関数の後にkmap/kmap_atomic関数を利用する.
メモリ管理を深掘りしたいあなたは,以下の記事を読みましょう!
- Linux Memory Management Documentation
- kmalloc and vmalloc : Linux kernel memory allocation API Limits
- The Slab Allocator in the Linux kernel
- セグメント方式
LinuxカーネルはC言語で書かれています.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
次回はこちらからどうぞ.