/proc/<pid>にファイルをおいてデータを読みたい

/proc/<pid>/ にファイルを作ってデータを読めると便利なときがあったりするのでめも。

今回はcgroup.cで処理を実装して、その関数をcgroup.hにて宣言。fs/proc/base.cでcgroupのほうに追加した関数を登録する形です。/proc/<pid> にあるファイルに対する関数はここで登録されてます。 差分はこのような感じです。

git masami@kerntest:~/linux-kernel (ktest *=)$ git diff --stat
 fs/proc/base.c         | 2 ++
 include/linux/cgroup.h | 2 ++
 kernel/cgroup/cgroup.c | 7 +++++++
 3 files changed, 11 insertions(+)

最小限のコードはこのようになります。

masami@kerntest:~/linux-kernel (ktest *=)$ git diff
diff --git a/fs/proc/base.c b/fs/proc/base.c
index 45f6bf6..db65c94 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -2909,6 +2909,7 @@ static const struct pid_entry tgid_base_stuff[] = {
 #endif
 #ifdef CONFIG_CGROUPS
        ONE("cgroup",  S_IRUGO, proc_cgroup_show),
+       ONE("cg_list",  S_IRUGO, proc_cg_list_show),
 #endif
        ONE("oom_score",  S_IRUGO, proc_oom_score),
        REG("oom_adj",    S_IRUGO|S_IWUSR, proc_oom_adj_operations),
@@ -3301,6 +3302,7 @@ static const struct pid_entry tid_base_stuff[] = {
 #endif
 #ifdef CONFIG_CGROUPS
        ONE("cgroup",  S_IRUGO, proc_cgroup_show),
+       ONE("cg_list",  S_IRUGO, proc_cg_list_show),
 #endif
        ONE("oom_score", S_IRUGO, proc_oom_score),
        REG("oom_adj",   S_IRUGO|S_IWUSR, proc_oom_adj_operations),
diff --git a/include/linux/cgroup.h b/include/linux/cgroup.h
index ed2573e..41ae434 100644
--- a/include/linux/cgroup.h
+++ b/include/linux/cgroup.h
@@ -101,6 +101,8 @@ int task_cgroup_path(struct task_struct *task, char *buf, size_t buflen);
 int cgroupstats_build(struct cgroupstats *stats, struct dentry *dentry);
 int proc_cgroup_show(struct seq_file *m, struct pid_namespace *ns,
                     struct pid *pid, struct task_struct *tsk);
+int proc_cg_list_show(struct seq_file *m, struct pid_namespace *ns,
+                     struct pid *pid, struct task_struct *task);

 void cgroup_fork(struct task_struct *p);
 extern int cgroup_can_fork(struct task_struct *p);
diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
index c3c9a0e..ab5094f 100644
--- a/kernel/cgroup/cgroup.c
+++ b/kernel/cgroup/cgroup.c
@@ -4600,6 +4600,13 @@ static int __init cgroup_wq_init(void)
 }
 core_initcall(cgroup_wq_init);

+int proc_cg_list_show(struct seq_file *m, struct pid_namespace *ns,
+                     struct pid *pid, struct task_struct *task)
+{
+       seq_printf(m, "%s\n", __func__);
+       return 0;
+}
+
 /*
  * proc_cgroup_show()
  *  - Print task's cgroup paths into seq_file, one line for each hierarchy

実行するとこうなります。

masami@kerntest:~$ ls /proc/self/
./     autogroup  cgroup      comm             cwd@     fd/      io       loginuid      maps       mounts      ns/        oom_score      patch_state  root@      sessionid  stack  status   timers         wchan
../    auxv       clear_refs  coredump_filter  environ  fdinfo/  latency  make-it-fail  mem        mountstats  numa_maps  oom_score_adj  personality  sched      setgroups  stat   syscall  timerslack_ns
attr/  cg_list    cmdline     cpuset           exe@     gid_map  limits   map_files/    mountinfo  net/        oom_adj    pagemap        projid_map   schedstat  smaps      statm  task/    uid_map
masami@kerntest:~$ cat /proc/self/cg_list
proc_cg_list_show
masami@kerntest:~$

try_charge()めも2

前回の続き

前回はここの2238行目の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         }
2247 

2回目のpage_counter_try_charge()で0が返ればcharge可能ということなのでdone_restockラベルにジャンプしてchargeの処理に入ってました。 では、2回目の呼び出しで0以外が帰った場合は2240行目のところになります。ここのif文はdo_swap_accountが真かどうかなので2236行目のチェックとは逆になってます。通常はdo_swap_accountは0でないでしょうから、この場合はmemswの課金を取り消してます。 そして、mem_cgroup_from_counterマクロでmem_cgroup構造体を取得します。 mem_cgroup_from_counterマクロはみんな大好きcontainer_ofマクロのラッパーです。どの変数を使うかmemory/memswはif文の結果次第です。if文が偽だったときはmay_swapをfalseにしてますが、これはcharge対象がswapじゃないよーということでしょう。この変数はtrueで初期化されています。

やっとif文を抜けて次は下のif文になります。chargeしようとしていたページ数(batch)がnr_pagesより大きい場合はbatchの数をnr_pagesに設定して、retryラベルにジャンプしてcharge処理を再チャレンジします。retryラベルはconsume_stock()を実行する手前にあります。

2248         if (batch > nr_pages) {
2249                 batch = nr_pages;
2250                 goto retry;
2251         }

retryラベルにジャンプしてもchargeが出来なかった場合や、if文が偽だった場合はここより下の処理が実行されます。これより先の処理はchargeできかった場合の処理になります。charge可能なら2239行目のgoto文でdone_restockラベルにジャンプしています。この後も色々な試みを行ってchargeしようとします。

コメントにあるようにここまで着たからといってメモリが無いわけでは無いということです。たしかに、ページの取得に失敗しているわけではないですからね。で、チェックしていることとしては、プロセスが終了に向かってるならbypassラベルにジャンプします。

2253         /*
2254          * Unlike in global OOM situations, memcg is not in a physical
2255          * memory shortage.  Allow dying and OOM-killed tasks to
2256          * bypass the last charges so that they can exit quickly and
2257          * free their memory.
2258          */
2259         if (unlikely(test_thread_flag(TIF_MEMDIE) ||
2260                      fatal_signal_pending(current) ||
2261                      current->flags & PF_EXITING))
2262                 goto bypass;

bypassラベルは-EINTRを返して終了です。

2319 bypass:
2320         return -EINTR;

プロセスがOOM中の場合はnomemラベルにジャンプします。

2264         if (unlikely(task_in_memcg_oom(current)))
2265                 goto nomem;

gfp_maskに__GFP_NOFAILが設定されている場合は -ENOMEMは返らずに、その直後にあるbypassラベルのところで-EINTRを返します。

2316 nomem:
2317         if (!(gfp_mask & __GFP_NOFAIL))
2318                 return -ENOMEM;

OOM中でなければこちらの処理が行われます。これはmemcg->stat->events[MEMCG_MAX]++的な処理です。

2270         mem_cgroup_events(mem_over_limit, MEMCG_MAX, 1);

次にもう一回ページ回収を試みます。

2272         nr_reclaimed = try_to_free_mem_cgroup_pages(mem_over_limit, nr_pages,
2273                                                     gfp_mask, may_swap);

そして、nr_pages分のchargeができそうならretryラベルに戻ってchargeの再チャレンジです。

2275         if (mem_cgroup_margin(mem_over_limit) >= nr_pages)
2276                 goto retry;

drainedはローカル変数で、falseで初期化されているため1回目は必ずこの条件は真になります。ここまではプロセスが動いているcpuからchargeできるか試みていましたが、drain_all_stock()でオンラインなcpu全てチェックしてchargeできるか調べます。そしてdrainedをtrueに返るので2度目はありません。

2278         if (!drained) {
2279                 drain_all_stock(mem_over_limit);
2280                 drained = true;
2281                 goto retry;
2282         }
2283 

次に以下のチェックが有り、リトライしなくて良いと言われていたらnomemラベルに飛びます。

2284         if (gfp_mask & __GFP_NORETRY)
2285                 goto nomem;

先程のページ回収処理で回収できたページ数がありかつ、charge対象のページ数が8ページ以下なら再チャレンジします。PAGE_ALLOC_COSTLY_ORDERの値は3です。

2286         /*
2287          * Even though the limit is exceeded at this point, reclaim
2288          * may have been able to free some pages.  Retry the charge
2289          * before killing the task.
2290          *
2291          * Only for regular pages, though: huge pages are rather
2292          * unlikely to succeed so close to the limit, and we fall back
2293          * to regular pages anyway in case of failure.
2294          */
2295         if (nr_reclaimed && nr_pages <= (1 << PAGE_ALLOC_COSTLY_ORDER))
2296                 goto retry;

もし、移動中のcgroupがあればそれの移動完了を待ってchargeのリトライがあります。これはかなり最後の手段っぽいですね。

2297         /*
2298          * At task move, charge accounts can be doubly counted. So, it's
2299          * better to wait until the end of task_move if something is going on.
2300          */
2301         if (mem_cgroup_wait_acct_move(mem_over_limit))
2302                 goto retry;

まだ頑張ってみます。nr_retriesはMEM_CGROUP_RECLAIM_RETRIESで初期化されています。

2304         if (nr_retries--)
2305                 goto retry;
2306

これはmm/memcontrol.cで5と定義されているので、gfp_maskに__GFP_NORETRYが設定されていなければ5回リトライを試みるようになっています。

  78 #define MEM_CGROUP_RECLAIM_RETRIES      5

リトライ回数を使い切った場合、gfp_maskに__GFP_NOFAILが設定されていなければ、bypassラベルにジャンプします。

2307         if (gfp_mask & __GFP_NOFAIL)
2308                 goto bypass;
2309 

シグナル待ちならbypassラベルにジャンプ。

2310         if (fatal_signal_pending(current))
2311                 goto bypass;
2312

最後にindexがMEMCG_OOMのstatusを1増やして、mem_cgroup_oom()を実行。

2313         mem_cgroup_events(mem_over_limit, MEMCG_OOM, 1);
2314 
2315         mem_cgroup_oom(mem_over_limit, gfp_mask, get_order(nr_pages));

mem_cgroup_oom()はOOMを発生させるものではなくて、OOM用に設定をしているだけです。

1862 static void mem_cgroup_oom(struct mem_cgroup *memcg, gfp_t mask, int order)
1863 {
1864         if (!current->memcg_oom.may_oom)
1865                 return;
1866         /*
1867          * We are in the middle of the charge context here, so we
1868          * don't want to block when potentially sitting on a callstack
1869          * that holds all kinds of filesystem and mm locks.
1870          *
1871          * Also, the caller may handle a failed allocation gracefully
1872          * (like optional page cache readahead) and so an OOM killer
1873          * invocation might not even be necessary.
1874          *
1875          * That's why we don't do anything here except remember the
1876          * OOM context and then deal with it at the end of the page
1877          * fault when the stack is unwound, the locks are released,
1878          * and when we know whether the fault was overall successful.
1879          */
1880         css_get(&memcg->css);
1881         current->memcg_oom.memcg = memcg;
1882         current->memcg_oom.gfp_mask = mask;
1883         current->memcg_oom.order = order;
1884 }

これでtry_charge()の処理を一通り読み終わり( ´ー`)フゥー...

Professional Linux Kernel Architecture (Wrox Programmer to Programmer)

Professional Linux Kernel Architecture (Wrox Programmer to Programmer)

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ロボット入門