linux: page allocatorの辺りを読んでいこう

Linuxのpage単位のallocatorを理解したいなーというところで、mm/page_alloc.cの __alloc_pages_nodemask()を起点にコードを読み始める。 __alloc_pages_nodemask()はalloc_pages()なんかから呼ばれるところですね。

__alloc_pages_nodemask()の細かいところを覗いて基本的な流れはこのような形

  • __alloc_pages_nodemask()のパラメーターをstruct alloc_contextインスタンスに保存
     このalloc_context構造体はallocationに関する設定(gfpフラグとか)をひとまとめにしておくもの。

  • gfpフラグとは別にalloc_flagsというローカル変数でallocationのフラグを設定する
     このフラグはmm/internal.hにあるALLOC_で始まる定数を使用する

  • gfpフラグで__GFP_WAITが立っている場合はmight_sleep_if()によって少し待ちがあるかも

  • カーネルの.configでCONFIG_CMAが有効になっていて、gfpフラグの設定で__GFP_MOVABLE__GFP_RECLAIMABLEが立っている場合はでALLOC_CMAというフラグをalloc_flagsに立てる
     CMA(Continuous Memory Allocator )はこのContinuous Memory Allocator - The Linux Foundation(pdf)を参照

  • ここからは各zoneを調べて空きページを探す(コード上だとretry_cpusetラベルというgoto用のラベルが使われる)

  • 最初に read_mems_allowed_begin()を呼んでpageを確保する処理を始めたという状態を設定しておく

  • first_zones_zonelist()でメモリを確保するのに適切なzoneを探す
     適切なzoneが見つからなければどうにもならないので__alloc_page_nodemask()はNULLを返す

  • get_page_from_freelist()でpageを取得する
     ここではgfpフラグを直接使うのではなく、gfpフラグに__GFP_HARDWALL というフラグをセットしたものを使う。このフラグはプロセスが属するcpusetからのみ確保を行うという設定。

  • get_page_from_freelist()でpageを確保できなかったら__alloc_pages_slowpath()で再度pageの確保にトライ

  • 最後に、pageの取得ができていた場合にread_mems_allowed_retry()を呼んでread_mems_allowed_begin()の時と状態が違っていたらpageの確保失敗としてretry_cpusetラベルに戻る

この__alloc_pages_nodemask()で重要なところは read_mems_allowed_begin()read_mems_allowed_retry()によるチェックと、実際にpageを確保しに行くget_page_from_freelist()かな。

前者の2個はcpusets機能のmems_allowedが絡んでいます。Documentation/cgroups/cpusets.txtについて書かれています。 このドキュメントの57〜60行目を見るとこんなことが書かれています。

 57 CPUs or Memory Nodes not in that cpuset.  The scheduler will not
 58 schedule a task on a CPU that is not allowed in its cpus_allowed
 59 vector, and the kernel page allocator will not allocate a page on a
 60 node that is not allowed in the requesting task's mems_allowed vector.

カーネルのpage allocatorはtaskのmems_allowedを見てpageの確保が許可されていないnodeからはメモリを確保しないと言ってますね。 実際に、struct task_structを見てみるとnodemask_tの変数でmems_allowedがありますね。

1562 #ifdef CONFIG_CPUSETS
1563         nodemask_t mems_allowed;        /* Protected by alloc_lock */
1564         seqcount_t mems_allowed_seq;    /* Seqence no to catch updates */
1565         int cpuset_mem_spread_rotor;
1566         int cpuset_slab_spread_rotor;
1567 #endif

mems_allowedの設定値は /proc//statusで確認できます。うちだとこんな感じです。そもそも物理cpuは1個しか無いから面白くないですがw

Cpus_allowed:   ff
Cpus_allowed_list:      0-7
Mems_allowed:   00000000,00000001
Mems_allowed_list:      0

そして、mems_allowedについては__alloc_pages_nodemask()の実行中に他のスレッドが更新しているかも知れないので再度チェックが必要みたいです。

さて、次はget_page_from_freelist()を読んでいきましょう。

プロセッサを支える技術  ??果てしなくスピードを追求する世界 (WEB+DB PRESS plus)

プロセッサを支える技術  ??果てしなくスピードを追求する世界 (WEB+DB PRESS plus)