try_charge()の処理めも(1)

chargeの処理としてはこの辺が重要だろうと思われる(要出典)try_charge()の処理を読みましょう。

早速処理を見ていきますが、まず対象のmemcgがルートのmemcgだった場合は何もしません。このチェックはmem_cgroup_is_root()で行えます。次にconsume_stock()を呼びます。引数としてはtry_charge()に渡されたmemcgを渡します。この関数は何をするかというと、このcpuに課金済みの使用可能なpageがあるか調べて、そういうpageがあればtrueを返します。try_charge()ではconsume_stock()がtrueを返したら処理を終了します。

次にmem_over_limit変数に値をセットするところがあります。swapを対象にしないとか、page_counter_try_charge()が0を返した場合は2238~2242行目の処理になります。do_swap_accountは明示的に無効にしない限りは大抵1になってると思うので、見るべきはpage_counter_try_charge()となります。

2236         if (!do_swap_account ||
2237             !page_counter_try_charge(&memcg->memsw, batch, &counter)) {
2238                 if (!page_counter_try_charge(&memcg->memory, batch, &counter))
2239                         goto done_restock;
2240                 if (do_swap_account)
2241                         page_counter_uncharge(&memcg->memsw, batch);
2242                 mem_over_limit = mem_cgroup_from_counter(counter, memory);
2243         } else {
2244                 mem_over_limit = mem_cgroup_from_counter(counter, memsw);
2245                 may_swap = false;
2246         }

batchという変数は宣言時に以下のように初期化されています。

 unsigned int batch = max(CHARGE_BATCH, nr_pages);

CHARGE_BATCHはmm/memcontrol.cで定義されていて値は32です。

page_counter_try_charge()は自分がいるmemcgの階層からルート階層までの各階層でcharge可能かチェックします。全階層でcharge可能なら0を返し、 一箇所でもchargeできなければ-ENOMEMを返します。ということで、先のif文ではcharge可能な場合にifのブロックが実行されます。 次にもう一回page_counter_try_charge()を呼びますが、このときの1番目の引数は先ほどと違います。最初はmemcg->memswで今度はmemcg->memoryです。どちらもstruct page_counter型の変数です。この型はlinux/page_counter.hで定義されています。これらの使い分けはこれを書いてる現段階ではよく分かってないけど、名前からしてmemswはswap、memoryはrssに使うんじゃないか?という気がします。chargeするものの種別に毎に分かれてそうな気がします。TODO:これら3変数の使われ方から変数の意味を調べよう。

 266         /* Accounted resources */
 267         struct page_counter memory;
 268         struct page_counter memsw;
 269         struct page_counter kmem;
 270 

ここでcharge可能だった場合はdone_restockラベルの場所にジャンプします。このラベルにジャンプする処理はここだけです。なのでどんな処理なのか見てしまいます。

2323         css_get_many(&memcg->css, batch);
2324         if (batch > nr_pages)
2325                 refill_stock(memcg, batch - nr_pages);
2326         if (!(gfp_mask & __GFP_WAIT))
2327                 goto done;
2328         /*
2329          * If the hierarchy is above the normal consumption range,
2330          * make the charging task trim their excess contribution.
2331          */
2332         do {
2333                 if (page_counter_read(&memcg->memory) <= memcg->high)
2334                         continue;
2335                 mem_cgroup_events(memcg, MEMCG_HIGH, 1);
2336                 try_to_free_mem_cgroup_pages(memcg, nr_pages, gfp_mask, true);
2337         } while ((memcg = parent_mem_cgroup(memcg)));

最初にcss_get_many()を呼んでますが、これは簡単に説明するとcssの参照カウンタを増やすだけです。つぎにbatchの値がnr_pagesより大きい場合はrefill_stock()を呼んでます。refill_stock()はさっき使用したconsume_stock()と関連します。refill_stock()でキャッシュして、consume_stock()で消費するような感じです。gfp_maskに__GFP_WAITが設定されていなければここで処理終了となります。そうでない場合は、do-whileの処理になります。

page_counter_read()はpage_counter構造体のcount変数をatomicに読み出してその値を返します。この値がmemcg構造体のhigh変数に設定されている値以下の場合はcontinueします。TODO: この辺の処理の意図は別途調べます。 次のmem_cgroup_events()は以下のような関数で、端的に書くとやってることとしては memcg->stat->events[MEMCG_HIGH] += nr;ですね。this_cpu_add()なのでこのcpuにバインドされてる変数に対してというのはありますが。

5400 void mem_cgroup_events(struct mem_cgroup *memcg,
5401                        enum mem_cgroup_events_index idx,
5402                        unsigned int nr)
5403 {
5404         this_cpu_add(memcg->stat->events[idx], nr);
5405 }

次は try_to_free_mem_cgroup_pages(memcg, nr_pages, gfp_mask, true); です。この関数はmm/vmscan.cにあります。この関数の子毎回処理内容は別途調べるとして、処理内容はページ回収です。nr_pages分のページ回収を行います。先のif文でmemcg->highとのチェックがあったので、上限超えてるからページ回収しないといけなということでしょうね。

最後はwhileの条件のところで、これはmemcg = parent_mem_cgroup(memcg)となっています。

この関数は以下のような処理で、mem_cgroup構造体のpage_counter型の変数memoryから上位階層のmem_cgroup構造体を取得して返します。

4418 struct mem_cgroup *parent_mem_cgroup(struct mem_cgroup *memcg)
4419 {
4420         if (!memcg->memory.parent)
4421                 return NULL;
4422         return mem_cgroup_from_counter(memcg->memory.parent, memory);
4423 }

これで自身の階層から上位に向かって処理していってるんですね。

try_charge()は一回で終わらすの大変そうなので、まずは切りの良いところでここまで ( ´ー`)フゥー...

まんがでわかるLinux シス管系女子 2(日経BP Next ICT選書)

まんがでわかるLinux シス管系女子 2(日経BP Next ICT選書)

mem_cgroup_try_charge()の処理めも

commitの前にmem_cgroup_try_charge()を呼んでいるので、commitの処理では重要な関数。

mem_cgroup_try_charge()自体はそんなに処理はなくて、メインなのはtry_charge()関数。mem_cgroup_try_charge()の処理は

memcgが無効なら何もしないで終了。

5468         if (mem_cgroup_disabled())
5469                 goto out;

charge対象のページがswap cacheですでにcharge済みなら何もしないで終了。charge済みならmem_cgroupが設定されているのは前に見たところ。

5471         if (PageSwapCache(page)) {
5472                 /*
5473                  * Every swap fault against a single page tries to charge the
5474                  * page, bail as early as possible.  shmem_unuse() encounters
5475                  * already charged pages, too.  The USED bit is protected by
5476                  * the page lock, which serializes swap cache removal, which
5477                  * in turn serializes uncharging.
5478                  */
5479                 if (page->mem_cgroup)
5480                         goto out;
5481         }
5482 

THPの場合はページ数を再計算。THPじゃない場合は1が設定済み。

5483         if (PageTransHuge(page)) {
5484                 nr_pages <<= compound_order(page);
5485                 VM_BUG_ON_PAGE(!PageTransHuge(page), page);
5486         }

mem_cgroup構造体を取得。pageの使われ方によって取得方法が違います。

5488         if (do_swap_account && PageSwapCache(page))
5489                 memcg = try_get_mem_cgroup_from_page(page);
5490         if (!memcg)
5491                 memcg = get_mem_cgroup_from_mm(mm);

ここがメインの処理。

5493         ret = try_charge(memcg, gfp_mask, nr_pages);
5494

cssの参照数を減らす。

5495         css_put(&memcg->css);
5496

try_charge()の戻り値チェック。css_put()はエラーの有無に関わらず行う模様。try_charge()は整数値を返し、成功時0を返します。戻り値が-EINTRだった場合は、memcgにルートのmemcgを設定しなおしています。

5497         if (ret == -EINTR) {
5498                 memcg = root_mem_cgroup;
5499                 ret = 0;
5500         }

取得したstruct mem_cgroupを引数で渡されたmemcgpにセットして終了。

5501 out:
5502         *memcgp = memcg;
5503         return ret;
5504 }

本編とも言えるtry_charge()はまた次回。

Linux 4.11から入った参照カウンタのAPIめも

security things in Linux v4.11を読んでて参照カウンタ用のAPIが4.11に入ったとのことなので軽くめもです。

利用にはinclude/refcount.hをインクルードします。実装はlib/refcount.cです。

機能的には変数は実態はatomic_t型のrefcount_tという型が作られてます。そして、値の増減時にオーバフローとアンダフローのチェックが行われます。基本的な使い方はatomic_tと同じ感じです。わりと大きな違いとしては、atomic_t型のatomic_inc()は更新前の値が0の場合実行後は1になりますが(期待通りですね)、refcount_inc()は更新前の値が0だと更新は行わず、WARN_ON_ONCEによる警告メッセージが出ます。参照カウンタのカウンタ変数はatomic_tと同じくでunsigned int型です。

atomic_inc()は純粋にinc命令を実行してますね。

static __always_inline void atomic_inc(atomic_t *v)
{
    asm volatile(LOCK_PREFIX "incl %0"
             : "+m" (v->counter));
}

refcount_inc()の場合は、refcount_add_not_zero()が呼ばれます。この関数がfalseを返すと警告がでます。

void refcount_add(unsigned int i, refcount_t *r)
{
    WARN_ONCE(!refcount_add_not_zero(i, r), "refcount_t: addition on 0; use-after-free.\n");

refcount_add_not_zero()は更新前の値が0だったらfalseを返します。このようなチェックはrefcount_inc()だけではなくて他の関数でも行われています。例えば、デクリメントするときに元の値が0かチェックしたり。

refcount_t型の変数の初期化は静的に行う場合はREFCOUNT_INITマクロで、動的に行うならrefcount_set()で行います。

こんな感じで使えます。

gist.github.com

実行結果

masami@saga:~/codes/refcounter_test$ dmesg
[156191.116677] refcounter_test: refcounter_test_init start
[156191.116686] refcounter_test: refcounter_test_run: cpu 2
[156191.116689] refcounter_test: refcounter_test_run: cpu 6
[156191.116692] refcounter_test: refcounter_test_run: cpu 3
[156191.116694] refcounter_test: refcounter_test_run: cpu 0
[156191.116697] refcounter_test: refcounter_test_run: cpu 4
[156191.116699] refcounter_test: refcounter_test_run: cpu 5
[156191.116701] refcounter_test: refcounter_test_run: cpu 11
[156191.116703] refcounter_test: refcounter_test_run: cpu 7
[156191.116705] refcounter_test: refcounter_test_run: cpu 8
[156191.116707] refcounter_test: refcounter_test_run: cpu 9
[156191.116709] refcounter_test: refcounter_test_run: cpu 10
[156191.116713] refcounter_test: refcounter_test_run: cpu 1
[156191.116715] refcounter_test: counter1: 13
[156191.116715] refcounter_test: counter2: 13
[156191.116716] refcounter_test: non_atomic_counter: 12
[156196.491170] refcounter_test: refcounter_test_cleanup bye

( ´ー`)フゥー...

memcgで実際に課金してるところめも

mm/memcontorl.cのmem_cgroup_charge_statistics()のとこです。カーネルは毎度ながら4.1.15。

mem_cgroup_charge_statistics()のコードはこんな感じです。見たままですね。

 828 static void mem_cgroup_charge_statistics(struct mem_cgroup *memcg,
 829                                          struct page *page,
 830                                          int nr_pages)
 831 {
 832         /*
 833          * Here, RSS means 'mapped anon' and anon's SwapCache. Shmem/tmpfs is
 834          * counted as CACHE even if it's on ANON LRU.
 835          */
 836         if (PageAnon(page))
 837                 __this_cpu_add(memcg->stat->count[MEM_CGROUP_STAT_RSS],
 838                                 nr_pages);
 839         else
 840                 __this_cpu_add(memcg->stat->count[MEM_CGROUP_STAT_CACHE],
 841                                 nr_pages);
 842 
 843         if (PageTransHuge(page))
 844                 __this_cpu_add(memcg->stat->count[MEM_CGROUP_STAT_RSS_HUGE],
 845                                 nr_pages);
 846 
 847         /* pagein of a big page is an event. So, ignore page size */
 848         if (nr_pages > 0)
 849                 __this_cpu_inc(memcg->stat->events[MEM_CGROUP_EVENTS_PGPGIN]);
 850         else {
 851                 __this_cpu_inc(memcg->stat->events[MEM_CGROUP_EVENTS_PGPGOUT]);
 852                 nr_pages = -nr_pages; /* for event */
 853         }
 854 
 855         __this_cpu_add(memcg->stat->nr_page_events, nr_pages);
 856 }

chargeはmem_cgroup構造体のメンバ変数statに対して行っています。変数はmem_cgroup構造体のこの部分で定義されていて、__percpuが付いているのでcpu毎にデータがあります。この構造体はmm/memcontrol.cで宣言されていてるので他では使わないようです。

 321         /*
 322          * percpu counter.
 323          */
 324         struct mem_cgroup_stat_cpu __percpu *stat;

mem_cgroup_stat_cpu構造体もmm/memcontrol.cにあります。内容はこのようになってます。

 128 struct mem_cgroup_stat_cpu {
 129         long count[MEM_CGROUP_STAT_NSTATS];
 130         unsigned long events[MEMCG_NR_EVENTS];
 131         unsigned long nr_page_events;
 132         unsigned long targets[MEM_CGROUP_NTARGETS];
 133 };

targetsについては今回のコード中では使用されてません。 MEM_CGROUP_STAT_NSTATSなどはenum型でinclude/linux/memcontrol.hにて定義されています。

  32 /*
  33  * The corresponding mem_cgroup_stat_names is defined in mm/memcontrol.c,
  34  * These two lists should keep in accord with each other.
  35  */
  36 enum mem_cgroup_stat_index {
  37         /*
  38          * For MEM_CONTAINER_TYPE_ALL, usage = pagecache + rss.
  39          */
  40         MEM_CGROUP_STAT_CACHE,          /* # of pages charged as cache */
  41         MEM_CGROUP_STAT_RSS,            /* # of pages charged as anon rss */
  42         MEM_CGROUP_STAT_RSS_HUGE,       /* # of pages charged as anon huge */
  43         MEM_CGROUP_STAT_FILE_MAPPED,    /* # of pages charged as file rss */
  44         MEM_CGROUP_STAT_WRITEBACK,      /* # of pages under writeback */
  45         MEM_CGROUP_STAT_SWAP,           /* # of pages, swapped out */
  46         MEM_CGROUP_STAT_NSTATS,
  47 };

例えば、RSSページの場合は↓なので、インデックス1番目の要素にnr_pagesを足してます。

__this_cpu_add(memcg->stat->count[MEM_CGROUP_STAT_RSS], nr_pages);

nr_pagesはmem_cgroup_commit_charge()で設定していて、THPでない場合は1、THPの場合は以下の部分で計算してます。

5542         if (PageTransHuge(page)) {
5543                 nr_pages <<= compound_order(page);
5544                 VM_BUG_ON_PAGE(!PageTransHuge(page), page);
5545         }

mem_cgroup_charge_statistics()の前半はページの種別に応じたchargeを行っていて、後半のところはpagein/pageoutのどちらのイベントかで処理が変わりますが、基本はそれぞれのイベント発生回数を増やしてますね。pageoutの場合はnr_pagesの符号を負にしてます。 そして、最後にnr_page_eventsにnr_pagesを足します。pageout時は引く感じですね。

 855         __this_cpu_add(memcg->stat->nr_page_events, nr_pages);

( ´ー`)フゥー...

IoTエンジニア養成読本

IoTエンジニア養成読本

commit_charge()の処理

mem_cgroup_commit_charge()から呼ばれるcommit_charge()の処理のめも。

呼び出しはこうですね。mem_cgroup_commit_charge()で渡された引数がそのまま渡ります。

5522 void mem_cgroup_commit_charge(struct page *page, struct mem_cgroup *memcg,
5523                               bool lrucare)
~~~略~~~
5540         commit_charge(page, memcg, lrucare);

commit_charge()はmem_cgroup_commit_charge()と同じくmm/memcontrol.cにあります。処理自体は数行しかありません。

2419 static void commit_charge(struct page *page, struct mem_cgroup *memcg,
2420                           bool lrucare)
2421 {
2422         int isolated;
2423 
2424         VM_BUG_ON_PAGE(page->mem_cgroup, page);
2425 

page->mem_cgroup変数が設定されているとエラーになります。commit_charge()はこの変数を設定するのが役割なので、先に設定されているということはバグということなんでしょう。

2426         /*
2427          * In some cases, SwapCache and FUSE(splice_buf->radixtree), the page
2428          * may already be on some other mem_cgroup's LRU.  Take care of it.
2429          */
2430         if (lrucare)
2431                 lock_page_lru(page, &isolated);
2432 

pageがすでにLRUリストにある場合はロックを取ってます。

2433         /*
2434          * Nobody should be changing or seriously looking at
2435          * page->mem_cgroup at this point:
2436          *
2437          * - the page is uncharged
2438          *
2439          * - the page is off-LRU
2440          *
2441          * - an anonymous fault has exclusive page access, except for
2442          *   a locked page table
2443          *
2444          * - a page cache insertion, a swapin fault, or a migration
2445          *   have the page locked
2446          */
2447         page->mem_cgroup = memcg;
2448

ここでpage->mem_cgroupにmem_cgroup_commit_charge()を呼んだところが渡したmemcgの構造体をpage構造体に設定します。

2449         if (lrucare)
2450                 unlock_page_lru(page, isolated);
2451 }

最後にロックを取っていた場合はアンロックします。

ここで設定したmem_cgroup構造体はinclude/linux/mm_types.hにあるpage構造体で以下のように定義されています。

 169 #ifdef CONFIG_MEMCG
 170         struct mem_cgroup *mem_cgroup;
 171 #endif

commit_charge()は処理が全然ないので今日はこれにて終了

Raspberry Piで学ぶ ROSロボット入門

Raspberry Piで学ぶ ROSロボット入門

mem_cgroup_commit_charge()の処理

カーネルは4.1.15。mem_cgroup_commit_charge()はchargeの処理で呼ばれる関数。

今回はここで使われる関数の詳細は調べないで、大まかな流れを把握するのが目標。

LXRで検索するとmm/filemap.cとかmm/swapfile.c、mm/memory.cなんかから呼ばれてます。 mem_cgroup_commit_charge()自体は40行ほどの関数。引数はcharge対象のpage構造体、所属するmemcgの構造体、最後のlrucareはpageがすでにLRUで管理されているかを示すbool値です。

5522 void mem_cgroup_commit_charge(struct page *page, struct mem_cgroup *memcg,
5523                               bool lrucare)
5524 {
5525         unsigned int nr_pages = 1;
5526 

処理を見ていきます。

5527         VM_BUG_ON_PAGE(!page->mapping, page);
5528         VM_BUG_ON_PAGE(PageLRU(page) && !lrucare, page);
5529 

最初にassertがあります。VM_BUG_ON_PAGEマクロは1番目の引数がif文に使われる条件で、2番目に渡しているpage構造体は値をdumpするのに使います。 最初のチェックは見たままで、2番めのほうはPageLRUマクロではpageがLRUに乗ってるのに、lrucareはfalseっていう矛盾のチェックです。

5530         if (mem_cgroup_disabled())
5531                 return;

memcgが使われてないなら何もしないですよね。

5532         /*
5533          * Swap faults will attempt to charge the same page multiple
5534          * times.  But reuse_swap_page() might have removed the page
5535          * from swapcache already, so we can't check PageSwapCache().
5536          */
5537         if (!memcg)
5538                 return;
5539

swapの処理でcharge済みのページを再度chargeしないようにらしいです。 コメントによるとreuse_swap_page()の場合、swapcacheからpageがなくなっているのでPageSwapCache()を使ってチェックできないと。

5540         commit_charge(page, memcg, lrucare);
5541

commit_charge()は名前の割にchargeの処理してないように見えるけど?今回はここの実装は見ないのでこんな関数を呼ぶよという程度で。

5542         if (PageTransHuge(page)) {
5543                 nr_pages <<= compound_order(page);
5544                 VM_BUG_ON_PAGE(!PageTransHuge(page), page);
5545         }
5546

THP(Transparent Huge Pages)の場合のチェックですね。

5547         local_irq_disable();
5548         mem_cgroup_charge_statistics(memcg, page, nr_pages);
5549         memcg_check_events(memcg, page);
5550         local_irq_enable();
5551

割り込みを無効にして、mem_cgroup_charge_statistics()で実際に値を増やしてますね。↓のような処理があるので。

 836         if (PageAnon(page))
 837                 __this_cpu_add(memcg->stat->count[MEM_CGROUP_STAT_RSS],
 838                                 nr_pages);

次にmemcg_check_events()を呼んで、chargeしたことによりsoft limitを超えるかチェックして、超えたら何か処理してます。ここまでやったら割り込みを有効に戻します。

5552         if (do_swap_account && PageSwapCache(page)) {
5553                 swp_entry_t entry = { .val = page_private(page) };
5554                 /*
5555                  * The swap entry might not get freed for a long time,
5556                  * let's not wait for it.  The page already received a
5557                  * memory+swap charge, drop the swap entry duplicate.
5558                  */
5559                 mem_cgroup_uncharge_swap(entry);
5560         }
5561 }

chargeしたのがswap時の場合ですね。if文の条件が真の場合はすでにcharge済みなのでmem_cgroup_uncharge_swap()でunchargeして今回のcharge分を戻します。

( ´ー`)フゥー...

ガベージコレクション

ガベージコレクション

mem_cgroup_charge_common()はだいぶ前に消えていた (; ・`д・´) ナ、ナンダッテー !! (`・д´・ (`・д´・ ;)

昨日の続きです。

4.10にあるドキュメントには課金の処理ではmem_cgroup_charge_common()を使うよーなんて書いてあったんですが、この関数はだいぶ昔になくなってました。

3.15で消えたようです。さらに言うと、このパッチで使用している__mem_cgroup_commit_charge()は3.17で消えたようです。 github.com

3.17の開発時に課金周りの処理が書き換えられたみたいで、このパッチで__mem_cgroup_commit_charge()からcommit_charge()に変わってました。 github.com

commit_charge()は4.10にもありました。やったね!

IoTエンジニア養成読本

IoTエンジニア養成読本