メモリ管理

					2020年01月16日
情報科学類 オペレーティングシステム II

                                       筑波大学 システム情報系 
                                       新城 靖
                                       <yas@cs.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2019/2020-01-16
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~yas/
http://www.cs.tsukuba.ac.jp/~yas/

■今日の大事な話

■補足

◆task_struct構造体

前回資料のtask_struct構造体 では、ptracer_cred ではなくて cred を示したかった。

linux-5.4.7/include/linux/sched.h
 624:	struct task_struct {
...
 869:	        /* Process credentials: */
 870:	
 871:	        /* Tracer's credentials at attach: */
 872:	        const struct cred __rcu         *ptracer_cred;
 873:	
 874:	        /* Objective and real subjective task credentials (COW): */
 875:	        const struct cred __rcu         *real_cred;
 876:	
 877:	        /* Effective (overridable) subjective task credentials (COW): */
 878:	        const struct cred __rcu         *cred;
...
1286:	};

■メモリ管理

◆目標

■物理メモリの管理

struct page page[]、物理メモリ
図? struct page page[]による物理メモリの管理

注意: 物理メモリを読み書きするには、論理アドレスが必要だが、論理アドレ スがない(カーネル空間にマップされていない)こともある。

◆ページ構造体

linux-5.4.7/include/linux/mm_types.h
  68:	struct page {
  69:	        unsigned long flags;            /* Atomic flags, some possibly
...
  84:	                        struct list_head lru;
  85:	                        /* See page-flags.h for PAGE_MAPPING_FLAGS */
  86:	                        struct address_space *mapping;
...
 198:	        atomic_t _refcount;
...
 215:	        void *virtual;                  /* Kernel virtual address (NULL if
 216:	                                           not kmapped, ie. highmem) */
...
 222:	} _struct_page_alignment;

◆ページ構造体のflags(主要部分)

linux-5.4.7/include/linux/page-flags.h
page構造体のflags(主要部分)
PG_locked ページがピン留めされている。ページアウトされない。入出力の処理中に設定され、完了後に解除される。
PG_error このページに対して入出力エラーが生じた。
PG_referenced ディスク入出力のために参照されている。
PG_uptodate ページの内容が有効である。入力処理が完了した。
PG_dirty ページの内容が変更された。
PG_lru ページングのための LRU リストにある。
PG_active ページがアクティブである。
PG_slab スラブ・アロケータで割り当てられた。
PG_arch_1 アーキテクチャ固有のページ状態
PG_reserved ページアウト禁止、または、ブード時のメモリ・アロケータで割り当てられた
PG_writeback 書き戻し中
PG_reclaim 開放すべきページ

◆x86のページ・サイズ

2の12乗(1 << 12)==4096バイト。
linux-5.4.7/arch/x86/include/asm/page_types.h
  10:	#define PAGE_SHIFT              12
  11:	#define PAGE_SIZE               (_AC(1,UL) << PAGE_SHIFT)
  12:	#define PAGE_MASK               (~(PAGE_SIZE-1))

linux-5.4.7/include/uapi/linux/const.h
  20:	#define __AC(X,Y)       (X##Y)
  21:	#define _AC(X,Y)        __AC(X,Y)

◆メモリ・ゾーン

歴史的な都合やハードウェアの制約で、メモリ・ページを「ゾーン」と呼ばれる領域に分割して管理する。

よく使われるゾーンの種類。

ZONE_DMA
(古いデバイスでも) DMA でアクセス可能なページ・フレーム。 x86 では、0-16M。 ISA バスのデバイスで 0-16M しかアクセスできないものがあった。
ZONE_NORMAL
カーネルの仮想アドレス空間に常にマップされている。 古いデバイスのDMA ではアクセスできないが、 新しいデバイスのDMA ではアクセスできる。 x86 (32ビット) では、16MB-896MBまで。
ZONE_HIGMEM
普段はカーネルの仮想アドレス空間にマップされていない。 使うときにはマップして使い、使い終わったらアンマップする。 x86 では、896MB より大きい所。

zone、DMA、NORMAL、HIMEM
図? メモリのゾーンへの分割

x86_64 (64ビット) では、DMA, DMA32, Normal が使われる。

◆DMA (Direct Memory Access)

DMA は、ハードディスクやネットワークデバイス等で使われているの入出力方 法の1つ。通常、メモリは、CPU が制御している。DMA では、周辺デバイスが CPU からメモリの制御を奪い、データの入出力を行う。

DMA の利点

◆ページフレームの割当てと開放

ページ・フレームは、物理メモリ。 Linux カーネル内では、次のような手続きで、割り当てる。 struct pageへのポインタが得られた場合、メモリは割り当てられているが、論 理アドレス不明なので、そのままではプログラムでアクセスできない。論理ア ドレスが必要なら、void *page_address(page)を使って struct pageへのポイ ンタからアクセス可能な論理アドレスを得ることができる。

次のような手続きで、メモリを開放する。

linux-5.4.7/include/linux/gfp.h
 547:	#define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0)

 529:	static inline struct page *
 530:	alloc_pages(gfp_t gfp_mask, unsigned int order)

 553:	extern unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);
 554:	extern unsigned long get_zeroed_page(gfp_t gfp_mask);

 566:	extern void __free_pages(struct page *page, unsigned int order);
 567:	extern void free_pages(unsigned long addr, unsigned int order);

 578:	#define free_page(addr) free_pages((addr), 0)

◆gfp_t gfp_mask

__get_free_pages(), alloc_pages(), alloc_page() や、 後述する kmalloc() では、 gfp_t のフラグ(gfp_mask) として、次のものがよく使われる。特に GFP_KERNEL がよく使われる。gfp は、get free pages に由来する。

linux-5.4.7/include/linux/gfp.h
説明
GFP_ATOMIC 高優先度。スリープ不可。割込み処理の前半(割り込みハンドラ、top half)や後半(bottom half)で使う。
GFP_NOIO スリープ可、入出力不可。
GFP_NOFS スリープ化、入出力可、ファイル操作不可。ファイルシステムの実装で使う(他のファイルシステムの操作を開始しない)。
GFP_KERNEL カーネル用メモリ通常の方法。スリープ可。ユーザ・プロセスのコンテキストで使う。
GFP_USER ユーザ空間用のメモリの通常の方法。スリープ可。
GFP_HIGHUSER HIGHMEMゾーンからの割当て。スリープ可。
GFP_DMADMAゾーンからの割当て。デバイス・ドライバ等が使う。
このようなフラグが存在する最も重要な理由は、スリープする可能性があるか ないかの違い。 その他に、どのゾーンからメモリを割り当てるべきかを表すものがある。

◆外部フラグメンテーション

物理フレームの割当てと開放を繰り返していくと、外部フラグメンテーション (external fragmentation) が生じる。全体としては空きメモリは存在している のに、小さなメモリ・フレームがあちこちに分散していて、大きさのページフ レームが存在しないためにメモリが割り当てられない状態に陥る。

◆Buddyシステム

「Buddy システム」は、Linux で使われている外部フラグメンテーションを起 こしにくいメモリ割当てアルゴリズム。

buddy とは、仲間の意味。

buddyの例
0 1
2 3
4 5
6 7
0,1 2,3
4,5 6,7
0,1,2,3 4,5,6,7

Buddyシステムでのメモリ管理手法

zone、free_area、page
図1(a) Buddyシステムによる空きページの管理(論理的な見方)

zone、free_area、page
図1(b) Buddyシステムによる空きページの管理(線形な見方)

◆Buddyシステムの状態

/proc/buddyinfo を見ると、現在の空きページの状態が分かる。
% cat /proc/buddyinfo [←]
Node 0, zone      DMA      6      5      3      3      4      2      2      0      1      1      2 
Node 0, zone   Normal    476   2577    990    354    174    104     65     34     19      1    135 
Node 0, zone  HighMem   1416   2920   1718   1082    933    504    251    152     87     43     53 
% []
この例では、DMA ゾーンの 2 0 (4KB) に、6 個、 2 1 (8KB) に、5 個、・・・、2 10 に2個の空きがある。 外部フラグメンテーションが起きると、大きな塊が少なくなる。

■kmallocとkfree

物理メモリは、ページ単位(4KBのページフレーム単位)で管理している。 しかし、カーネル内のデータ構造は、4KB にぴったりはまらない。 Linux でページ単位ではない単位でメモリを確保・開放できるには、次の ような方法がある。

◆kmalloc()

C言語のユーザ空間で使えるライブラリ malloc() に似ている。
void *kmalloc(size_t size, gfp_t flags)
引数 結果: 最低限、size 分のメモリを割り当て、その先頭の番地(カーネル内の仮 想アドレス)を返す。割り当てられたメモリは、物理的にも連続になる。割当 てできない時には、NULL を返す。

◆kmalloc()のフラグの選択

状況 フラグ
プロセスのコンテキスト、スリープ可能 GFP_KERNEL
プロセスのコンテキスト、スリープ不可 GFP_ATOMIC
割込みハンドラ GFP_ATOMIC
割込みハンドラ後半(Softirq,Tasklet,後述) GFP_ATOMIC
DMA可能なメモリ、スリープ可能 GFP_DMA|GFP_KERNEL
DMA可能なメモリ、スリープ不可 GFP_DMA|GFP_ATOMIC

◆kfree()

void kfree(const void *objp)
C言語のユーザ空間で使えるライブラリ free() と似ている。 kmalloc() で割り当てたメモリを解放する。

◆vmalloc()とvfree()

kmallc()/kfree() と似ているが、割り当てられるメモリは物理的に連続してい る保証はない。(カーネル空間の仮想アドレスとしては連続している。)

■スラブアロケータ(slab allocator)

同じ大きさの構造体を割り当てる時に使う。 kmalloc(), kfree() よりも、効率がよい。

◆free list方式とその問題点

構造体の割当てには、free list 方式が使われることもある。 この方法では、メモリに空きがあっても、解放できるか簡単にはわからない。

free list、オブジェクト4個
図? フリーリストの例

オブジェクトは、1ページに2個入る。 オブジェクトが次の順番で開放された。
  1. object 2
  2. object 6
  3. object 3
  4. object 1

free list、オブジェクト4個
図? フリーリストの例(ページを意識)

object 2 と object 3 の部分は、1ページ空いている。

◆スラブ・アロケータの目標

スラブ・アロケータ自身は、alloc_pages() 等のページ単位のメモリ割当て 機能を呼出してメモリを確保する。

◆ページ・フレーム、スラブ、オブジェクトの関係

ページフレーム3つ、スラブ1つ、オブジェクト6つ
図? ページ・フレーム、スラブ、オブジェクトの関係

◆kmem_cache_create()

struct kmem_cache *
kmem_cache_create (const char *name, size_t size, size_t align,
        unsigned long flags, void (*ctor)(void *))
引数 結果: 成功した時には、struct kmem_cache へのポインタ。 失敗するとNULL。新しいページが割り当てられた時には、ctor で 指定された関数が呼ばれる。

◆kmem_cache_destroy()

void kmem_cache_destroy(struct kmem_cache *c)
kmem_cache_create() で割り当てた struct kmem_cache *を開放する。 shutdown (電源を切る操作)で呼ばれることがある。

◆kmem_cache_alloc()とkmem_cache_free()

void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)

void *kmem_cache_alloc_node(struct kmem_cache *cachep,gfp_t flags, int node)

void kmem_cache_free(struct kmem_cache *cachep, void *b)
生成した struct kmem_cache *を使ってオブジェクトのメモリを割り当てる。 割り当てたオブジェクトのメモリは、kmem_cache_free()で開放する。

kmem_cache_alloc_node() は、メモリ・アクセスが不均質なマルチプロセッサ 用。メモリを割り当てるという働きは、kmem_cache_alloc() と同じ。ただし、 node で指定されたプロセッサで高速にアクセスできるメモリに割り当てられる。

◆利用例(struct cred)

linux-5.4.7/kernel/cred.c

  33:	static struct kmem_cache *cred_jar;
...
 656:	void __init cred_init(void)
 657:	{
 658:	        /* allocate a slab in which we can store credentials */
 659:	        cred_jar = kmem_cache_create("cred_jar", sizeof(struct cred), 0,
 660:	                        SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_ACCOUNT, NULL);
 661:	}
...
 250:	struct cred *prepare_creds(void)
 251:	{
 252:	        struct task_struct *task = current;
 253:	        const struct cred *old;
 254:	        struct cred *new;
...
 258:	        new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
...
 264:	        old = task->cred;
 265:	        memcpy(new, old, sizeof(struct cred));
...
 288:	        return new;
...
 293:	}

  93:	static void put_cred_rcu(struct rcu_head *rcu)
  94:	{
  95:	        struct cred *cred = container_of(rcu, struct cred, rcu);
...
 123:	        kmem_cache_free(cred_jar, cred);
 124:	}

◆/proc/slabinfo

/proc/slabinfo を見ると、スラブアロケータの状態がわかる。

% cat /proc/slabinfo  [←]
slabinfo - version: 2.0
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <batchcount> <limit> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
ip_conntrack_expect      0      0    256   15    1 : tunables  120   60    8 : slabdata      0      0      0
ip_conntrack          22     50    384   10    1 : tunables   54   27    8 : slabdata      5      5      0
nfs_direct_cache       0      0     68   58    1 : tunables  120   60    8 : slabdata      0      0      0
nfs_write_data        36     42    512    7    1 : tunables   54   27    8 : slabdata      6      6      0
...
task_struct           84    115   1408    5    2 : tunables   24   12    8 : slabdata     23     23      0
anon_vma             767   1130     16  226    1 : tunables  120   60    8 : slabdata      5      5      0
pgd                   54    238     32  119    1 : tunables  120   60    8 : slabdata      2      2      0
pmd                  123    123   4096    1    1 : tunables   24   12    8 : slabdata    123    123      0
size-131072(DMA)       0      0 131072    1   32 : tunables    8    4    0 : slabdata      0      0      0
size-131072            0      0 131072    1   32 : tunables    8    4    0 : slabdata      0      0      0
size-65536(DMA)        0      0  65536    1   16 : tunables    8    4    0 : slabdata      0      0      0
size-65536             2      2  65536    1   16 : tunables    8    4    0 : slabdata      2      2      0
...
size-32             8314   8925     32  119    1 : tunables  120   60    8 : slabdata     75     75      0
kmem_cache           150    150    256   15    1 : tunables  120   60    8 : slabdata     10     10      0
% []
スラブ・アロケータには、2種類ある。

■ユーザ・プロセスの仮想メモリの実現

◆OSに求められる機能(オペレーティングシステムI復習)

x86 には、その他、Multics 由来の「セグメント」がある。Linux 等の複数アー キテクチャで動作する OS は、x86 依存の機能には依存しない形で設計される。

◆Unixにおけるメモリに関するシステム・コールとライブラリ

システム・コール ライブラリ その他

◆Unixにおけるプロセスのアドレス空間の基本的な構造

テキスト、データ、BSS、スタック
図? プロセスのアドレス空間の構造

◆実行形式の構造

Linux では、実行形式として ELF(Executable and Linkable Format) が使われ ている。ELF ファイルは、readelf コマンドや objdump で容を観察できる。

ELF ファイルは、ヘッダとセクションの並びからなる。重要なセクションに は、.text, .rodata, .data がある。

$ cat hello.c [←]
main()
{
        printf("hello, %s!\n","world");
}
$ cc -o hello hello.c [←]
$ file hello [←]
hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, not stripped
$ size hello [←]
   text		   data	      bss	    dec	    hex	filename
   1159		       252          8	       1419     58b	hello
$ readelf -S hello [←]
There are 30 section headers, starting at offset 0x7f4:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048134 000134 000013 00   A  0   0  1
...
  [13] .text             PROGBITS        08048310 000310 00018c 00  AX  0   0 16
...
  [15] .rodata           PROGBITS        080484b8 0004b8 00001e 00   A  0   0  4
...
  [24] .data             PROGBITS        080496c8 0006c8 000004 00  WA  0   0  4
  [25] .bss              NOBITS          080496cc 0006cc 000008 00  WA  0   0  4
...
  [28] .symtab           SYMTAB          00000000 000ca4 000410 10     29  45  4
$ []

■Linuxにおけるユーザプロセスのアドレス空間の実装

◆アドレス空間とメモリ・エリア

利用者プロセスのプログラムは、線形な(linear)アドレス空間で仮想アドレス を使って機械語命令を読み出したり、データを読み書きする。 (x86 では、セグメンテーションも使えるので、線形ではないアドレス空間も可 能だが、Linux では、他のアーキテクチャとの兼ね合いもあり、線形な空間を 使う。)

線形なアドレス空間は、メモリ・エリア(memory area)(または、memory region、memory interval)に分割される。

◆task_struct構造体とmm_struct構造体

カーネル内では、プロセスのメモリは、次の構造体で表される。
linux-5.4.7/include/linux/sched.h
 624:	struct task_struct {
...
 727:	        struct mm_struct                *mm;
...
1286:	};
tast_struct の mm フィールド

task_struct、mm_struct、vm_area_struct
図? プロセス関連のメモリの構造体

◆mm_struct構造体

linux-5.4.7/include/linux/mm_types.h
 370:	struct mm_struct {
...
 372:	                struct vm_area_struct *mmap;            /* list of VMAs */
 373:	                struct rb_root mm_rb;
...
 389:	                pgd_t * pgd;
...
 410:	                atomic_t mm_users;
...
 419:	                atomic_t mm_count;
...
 424:	                int map_count;                  /* number of VMAs */
...
 431:	                struct list_head mmlist;
...
 450:	                unsigned long start_code, end_code, start_data, end_data;
 451:	                unsigned long start_brk, brk, start_stack;
 452:	                unsigned long arg_start, arg_end, env_start, env_end;
...
 534:	};

◆vm_area_struct構造体

linux-5.4.7/include/linux/mm_types.h
 292:	struct vm_area_struct {
 293:	        /* The first cache line has the info for VMA tree walking. */
 294:	
 295:	        unsigned long vm_start;         /* Our start address within vm_mm. */
 296:	        unsigned long vm_end;           /* The first byte after our end address
 297:	                                           within vm_mm. */
 298:	
 299:	        /* linked list of VM areas per task, sorted by address */
 300:	        struct vm_area_struct *vm_next, *vm_prev;
 301:	
 302:	        struct rb_node vm_rb;
...
 314:	        struct mm_struct *vm_mm;        /* The address space we belong to. */
 315:	        pgprot_t vm_page_prot;          /* Access permissions of this VMA. */
 316:	        unsigned long vm_flags;         /* Flags, see mm.h. */
...
 338:	        const struct vm_operations_struct *vm_ops;
...
 343:	        struct file * vm_file;          /* File we map to (can be NULL). */
...
 356:	} __randomize_layout;
vm_area_structのvm_flagsの値(include/linux/mm.h)
フラグ説明
VM_READ 読み込み可
VM_WRITE 書き込み可
VM_EXEC 実行可
VM_SHARED 共有されている
VM_GROWSDOWN アドレスが小さい方に伸びる
VM_GROWSUP アドレスが大きい方に伸びる
VM_DENYWRITE 書き込み不可。
VM_EXECUTABLE 実行可能。
VM_LOCKED ロックされている。
VM_DONTCOPY コピー不可
VM_DONTEXPAND 拡張不可。

◆プロセスのアドレス空間の実装

プロセスのアドレス空間 は、次のような領域に分割されて実装されている。

mm_struct、アドレス空間、vm_area、実行形式ファイル
図? プロセスのアドレス空間の実現

各領域は、次のように実装されている。
テキスト
機械語を置く。VM_EXEC 属性と VM_READ属性が付いている。書き込み禁止 で共有可能。mm_struct の start_code と end_code が、開始番地と終了番地 を保持する。
データ(初期値付き)
データを置く。VM_READ|VM_WRITE 属性が付いている(以下同様)。共有不 可。ファイルに初期値が含まている。
BSS(初期値無しデータ)
0 で初期化されるデータを置く。ファイルに初期値が含まれない。
ヒープ
データを置く。malloc() の原資(の1つ)。brk() や sbrk() システム・ コールで大きさが変更される。番地が大きい方に伸びる。mm_struct の start_brk とbrk が開始番地と終了番地を保持する。
スタック
関数呼び出しのスタックが置かれる。スタック・ポインタが指す。局所変 数や関数の戻り番地が置かれる。スタックポインタが下限を越えて小さくなる と、自動拡張されることがあるる

◆プロセスのアドレス空間のレイアウト(動的リンクライブラリ)

元の実行形式に由来するテキスト、データ、スタックの他に、動的リンク・ラ イブラリに由来するテキストやデータのためのメモリ・エリアが作られる。 /proc/PID/maps というファイルを見ると、その様子が分かる。
$ echo $$ [←]
3981
$ ls /proc/$$ [←]
attr             cpuset   fd        maps        oom_adj    smaps   task
auxv             cwd      io        mem         oom_score  stat    wchan
cmdline          environ  limits    mounts      root       statm
coredump_filter  exe      loginuid  mountstats  schedstat  status
$ cat /proc/$$/maps  [←]
00110000-00114000 r-xp 00000000 08:02 490576     /lib/libnss_dns-2.5.so
00114000-00115000 r--p 00003000 08:02 490576     /lib/libnss_dns-2.5.so
00115000-00116000 rw-p 00004000 08:02 490576     /lib/libnss_dns-2.5.so
...
08047000-080f5000 r-xp 00000000 08:02 481554     /bin/bash
080f5000-080fa000 rw-p 000ae000 08:02 481554     /bin/bash
080fa000-080ff000 rw-p 080fa000 00:00 0 
09d66000-09e25000 rw-p 09d66000 00:00 0          [heap]
...
bffdd000-bfff2000 rw-p bffe9000 00:00 0          [stack]
$ wc /proc/$$/maps  [←]
45 263 2920 /proc/3981/maps
$ []
/proc/PID/maps のフィールドの意味
  1. メモリ・セグメントの開始番地と終了番地。
  2. アクセス許可。r(read), w(write), x(executable), p(private), s(shared)
  3. オフセット
  4. ブロック・デバイスのメジャー番号とマイナー番号。 8:2 なら、メジャー番号が、8、マイナー番号が2の意味。 デバイスに結びついていない場合には、00:00 になる。
  5. ファイルのinode番号。
  6. ファイル名。
ブロック・デバイスには、メジャー番号とマイナー番号がある。各ファイルに は、inode 番号がある。これらの3つの番号がわかると、カーネル内ではファイ ルを特定できる。(同じ inode 番号のファイルは、1つのブロック・デバイス内 では、1個しかない。) ファイル名は不要だが、/proc/PID/maps では、人間にとって 分かりやすいようにわざわざ表示している。

ブロック・デバイスのメジャー番号とマイナー番号は、ls -l でわかる。

$ ls -l /dev/sda2 [←]
brw-r----- 1 root disk 8, 2 Jan 24 12:00 /dev/sda2
$ []
ファイルの inode 番号は、ls -i でわかる。
$ ls -li /bin/bash [←]
481554 -rwxr-xr-x 1 root root 735004 Jan 22  2009 /bin/bash
$ ls -li /lib/libnss_dns-2.5.so [←]
490576 -rwxr-xr-x 1 root root 21948 Oct 26 08:16 /lib/libnss_dns-2.5.so
$ []

■ページテーブル

◆仮想アドレスと物理アドレス

MMU による変換方法は、ページテーブルに保存される。

CPU、MMU、ページテーブル、メモリ
図? MMUによる仮想アドレスから物理アドレスへの変換

◆1段のページ・テーブル

仮想アドレスの構成の 。 1ページが4KB (4096, 0x1000)で、仮想アドレスが32ビットの時。

p(20ビット)+offset
図? 1段のページテーブル

ページテーブルは、次のような配列になる。
unsigned long int page_table[0x100000];
この配列の要素は、ページ・フレームの先頭番地(物理アドレス)。

MMU(ハードウェア) は、このページテーブルを使って、次のようにして仮想ア ドレスから物理アドレスを求める。以下は、MMU の動きを C 言語で説明したも の。

unsigned long int physical_address( unsigned long int virtual v ) {
    unsigned long int p, page, offset;
    p = v >> 12;         // 32中、上位20ビット(32-12==20)の取り出し
    offset = v & 0xfff;  // 下位 12 ビットの取り出し
    page = page_table[p];
    return( page + offset );
}

mm_struct、page_table、page frame
図? 1段のページテーブル

注意: 白い部分は、0 が入っている。0 の部分は、ページ・フレームが割り当 てられていないことを意味する。0 を保持するためにも、メモリが必要である。

page_table[] は、0x100000 個 == 1024 * 1024 個 == 1M 個の要素からなる。 1要素が 4 バイト(32ビット) なら、4MB のメモリが必要になる。

◆多段のページ・テーブル

実際のプロセスでは、使われていない空間が圧倒的に多い。 1段のページテーブルでは、ページテーブルを保持するためのメモリが多くなってしまう。 多くのCPUでは、多段のページテーブルを採用している。 アドレス空間のうち、使われていない部分のポインタを NULL にする。 Linux では、最大5段のページテーブルを想定している。

仮想アドレスの構成の 。 1ページが4KB、仮想アドレスが32ビットの時の分割の例(他の分割方法も考えら れる)

4+4+4+4+4+12
図? 仮想アドレス上位20ビットの5つの部分への分割例

mm_struct、PGD、P4D、PUD、PMD、PTE、page frame
図? 5段のページテーブル

unsigned int pgd[0x10];

unsigned long int physical_address( unsigned long int virtual v ) {
    unsigned int *pud, *p4d, *pmd, *pte, p, q, r, s, t, page, offset;
    p = v >>  (32-4) ;
    q = (v >> (32-8)) & 0xf;
    r = (v >> (32-12)) & 0xf;
    s = (v >> (32-16)) & 0xf;
    t = (v >> (32-20)) & 0xf;
    offset = v & 0xfff;
    p4d = pgd[p];
    pud = p4d[q];
    pmd = pud[r];
    pte = pmd[s];
    page = pte[t]
    return( page + offset );
}

◆x86のページ・テーブル

x86 では、従来、2段のページテーブルを用いている。次のように対応させている。

10+10+12
図? 仮想アドレス位20ビットの2つの部分への分割例

mm_struct、pgd、pte、page frame。
図? x86の2段のページテーブル

◆x86のページ・テーブル(PAE有効)

x86 で PAE(Physical Address Extension)が有効の時には、次のようになる。 PAE を使うと、仮想アドレスは、32ビットであるが、物理アドレスは、36ビットまで使えるようになる。

◆x86_64のページ・テーブル(4レベル)

x86_64 (64ビット) では、仮想アドレスとして 48 ビットつかう。 ページサイズは、4KB、ページテーブルの単数は、4 段である。

◆x86_64のページ・テーブル(5レベル)

x86_64 (64ビット) では、仮想アドレスとして 57 ビットつかう。 ページサイズは、4KB、ページテーブルの単数は、5 段である。

■ページ・フォールト

メモリが割り当てられていない場所をプロセスがアクセスした時には、ページ・ フォールトが発生する。 関数do_page_fault() がこのような処理を行う。この関数は、権限外のアクセ ス、たとえば、書き込み禁止のメモリに書き込みを試みた場合のエラーも処理 する。

◆x86 do_page_fault()

linux-5.4.7/arch/x86/mm/fault.c

1523:	dotraplinkage void
1524:	do_page_fault(struct pt_regs *regs, unsigned long error_code, unsigned long address)
1525:	{
...
1530:	        __do_page_fault(regs, error_code, address);
...
1532:	}

1493:	static noinline void
1494:	__do_page_fault(struct pt_regs *regs, unsigned long hw_error_code,
1495:	                unsigned long address)
1496:	{
...
1503:	        if (unlikely(fault_in_kernel_space(address)))
1504:	                do_kern_addr_fault(regs, hw_error_code, address);
1505:	        else
1506:	                do_user_addr_fault(regs, hw_error_code, address);
1507:	}

1281:	static inline
1282:	void do_user_addr_fault(struct pt_regs *regs,
1283:	                        unsigned long hw_error_code,
1284:	                        unsigned long address)
1285:	{
1286:	        struct vm_area_struct *vma;
1287:	        struct task_struct *tsk;
1288:	        struct mm_struct *mm;
1289:	        vm_fault_t fault, major = 0;
1290:	        unsigned int flags = FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_KILLABLE;
1291:	
1292:	        tsk = current;
1293:	        mm = tsk->mm;
...
1402:	        vma = find_vma(mm, address);
1403:	        if (unlikely(!vma)) {
1404:	                bad_area(regs, hw_error_code, address);
1405:	                return;
1406:	        }
1407:	        if (likely(vma->vm_start <= address))
1408:	                goto good_area;
1409:	        if (unlikely(!(vma->vm_flags & VM_GROWSDOWN))) {
1410:	                bad_area(regs, hw_error_code, address);
1411:	                return;
1412:	        }
...
1409:	        if (unlikely(!(vma->vm_flags & VM_GROWSDOWN))) {
1410:	                bad_area(regs, hw_error_code, address);
1411:	                return;
1412:	        }
...
1441:	        fault = handle_mm_fault(vma, address, flags);
...
1486:	}

◆handle_mm_fault()

linux-5.4.7/include/linux/mm.h
 417:	struct vm_fault {
...
 453:	};

linux-5.4.7/mm/memory.c
3982:	vm_fault_t handle_mm_fault(struct vm_area_struct *vma, unsigned long address,
3983:	                unsigned int flags)
3984:	{
3985:	        vm_fault_t ret;
...
4010:	                ret = __handle_mm_fault(vma, address, flags);
...
4025:	}

3893:	static vm_fault_t __handle_mm_fault(struct vm_area_struct *vma,
3894:	                unsigned long address, unsigned int flags)
3895:	{
3896:	        struct vm_fault vmf = {
3897:	                .vma = vma,
3898:	                .address = address & PAGE_MASK,
3899:	                .flags = flags,
3900:	                .pgoff = linear_page_index(vma, address),
3901:	                .gfp_mask = __get_fault_gfp_mask(vma),
3902:	        };
...
3904:	        struct mm_struct *mm = vma->vm_mm;
3905:	        pgd_t *pgd;
3906:	        p4d_t *p4d;
...
3909:	        pgd = pgd_offset(mm, address);
3910:	        p4d = p4d_alloc(mm, pgd, address);
...
3914:	        vmf.pud = pud_alloc(mm, p4d, address);
...
3940:	        vmf.pmd = pmd_alloc(mm, vmf.pud, address);
...
3973:	        return handle_pte_fault(&vmf);
3974:	}

◆handle_pte_fault()

linux-5.4.7/mm/memory.c
3805:	static vm_fault_t handle_pte_fault(struct vm_fault *vmf)
3806:	{
...
3827:	                vmf->pte = pte_offset_map(vmf->pmd, vmf->address);
3828:	                vmf->orig_pte = *vmf->pte;
...
3845:	        if (!vmf->pte) {
3846:	                if (vma_is_anonymous(vmf->vma))
3847:	                        return do_anonymous_page(vmf);
3848:	                else
3849:	                        return do_fault(vmf);
3850:	        }
...
3852:	        if (!pte_present(vmf->orig_pte))
3853:	                return do_swap_page(vmf);
...
3885:	}

3582:	static vm_fault_t do_fault(struct vm_fault *vmf)
3583:	{
3584:	        struct vm_area_struct *vma = vmf->vma;
3585:	        struct mm_struct *vm_mm = vma->vm_mm;
3586:	        vm_fault_t ret;
...
3591:	        if (!vma->vm_ops->fault) {
...
3613:	                                ret = VM_FAULT_NOPAGE;
...
3616:	                }
3617:	        } else if (!(vmf->flags & FAULT_FLAG_WRITE))
3618:	                ret = do_read_fault(vmf);
3619:	        else if (!(vma->vm_flags & VM_SHARED))
3620:	                ret = do_cow_fault(vmf);
3621:	        else
3622:	                ret = do_shared_fault(vmf);
...
3629:	        return ret;
3630:	}

3473:	static vm_fault_t do_read_fault(struct vm_fault *vmf)
3474:	{
3475:	        struct vm_area_struct *vma = vmf->vma;
3476:	        vm_fault_t ret = 0;
...
3489:	        ret = __do_fault(vmf);
...
3498:	}

3065:	static vm_fault_t __do_fault(struct vm_fault *vmf)
3066:	{
3067:	        struct vm_area_struct *vma = vmf->vma;
3068:	        vm_fault_t ret;
...
3092:	        ret = vma->vm_ops->fault(vmf);
...
3110:	        return ret;
3111:	}

◆赤黒木(red-black tree (rbtree))

赤黒木(red-black tree) は、平衡二分探索木(self-balancing binary search tree)の一種。 節は、赤と黒に分類される。

二分探索木とは、次のような二分木。 平衡木(balanced tree)、または、高さ平衡木(height-balanced tree)は、任意 の節で左右の高さの差が一定以下木。

Linux では、赤黒木をソートされた要素が並ぶリストを実現するために使っている。

◆Linux red-black treeの基本操作

型定義で、各要素に次の要素を含める。 検索
  1. 現在のノードとキーを比較
  2. 等しいなら見つかった
  3. キーが小さいなら左の枝へ
  4. キーが大きいなら右の枝へ
  5. 枝がなければキーは存在しない
挿入
  1. まず検索する。現在のノードと挿入したいデータのキーを比較する。
  2. キーが小さいなら左の枝を「親」にして検索を続ける。
  3. キーが大きいなら右の枝を「親」にして検索を続ける。
  4. キーが等しいならエラー(エラーにせず、重複を許すこともある)
  5. 子供がいない「親」が見つかる。
  6. 「親」から挿入したいデータへのリンクを作成する(rb_link_node())
  7. 平衡になるようにする(rebalancing, recoloring, rb_insert_color())

■課題2 メモリ管理

★問題(201) Buddyシステム

Buddyシステムで、次のメモリ・ページの buddy メモリ・ページを答えなさい。

★問題(202) kmalloc()とkfree()

以下は、ユーザ空間でメモリを割当て、利用し、開放するプログラムの一部である。
struct s1 {
   /* 省略 */
};
利用
   struct s1 *p;
   p = malloc( sizeof(struct s1) );
   use( p );
   free( p );
このプログラムを、カーネル内で動かすことを想定してkmalloc() と kfree() を使って書き換えなさい。ただし、gfp のフラグとしては、GFP_KERNEL を使いなさい。
利用
   struct s1 *p;
   /*回答*/
   use( p );
   /*回答*/

★問題(203) スラブアロケータ

問題(202) のプログラムを、スラブアロケータを使って書き換 えなさい。すなわち、kmem_cache_create()、kmem_cache_alloc()、および、 kmem_cache_free()を使って書き換えなさい。ただし、kmem_cache_create() の 第3引数のalign としては、0を、第4引数のflagsとしては、SLAB_PANIC、第5引 数のコンストラクタとしては、NULL を指定しなさい。
初期化
   /*回答*/

利用
   struct s1 *p;
   /*回答*/
   use( p );
   /*回答*/

★問題(204) 1段のページテーブル

仮想アドレスのサイズが32ビット、1ページの大きさが4KBとする。 次の3ページが割り当てられてしたとする。 1段のページテーブル を用いていた場合、ページテーブルの形と内容はどうなるか。簡単に図で書きなさい。 また、 ページテーブルに必要なメモリは何バ イトになるか。ページテーブルの1エントリのバイトは、4バイトとする。 なお、末端のページ・フレームに必要なメモリ(この場合は、3ページ、12KB)は、 ページテーブルに必要なメモリではないので、計算に入れない。

★問題(205) 2段のページテーブル

問題(204) で、次のような2段のページテーブル (「x86のページ・テーブル」と同じ) を用いていたとする。 この場合、ページテーブルの形と内容はどうなるか。簡単に図で書きなさい。 また、ページテーブルに必要なメモリは何バイトになるか。ページテーブル の1エントリのバイトは、上位のページテーブルも下位のページテーブルも4バ イトとする。
Last updated: 2020/01/13 17:13:07
Yasushi Shinjo / <yas@cs.tsukuba.ac.jp>