読者です 読者をやめる 読者になる 読者になる

memory cgroupの初期化処理辺りを読む

linux kernel

誰得なめも。バージョンは4.1.15。

__initがついているのは以下の3関数。

  1. mem_cgroup_init()
  2. enable_swap_account()
  3. mem_cgroup_swap_init()

当たり前だけど、__initがあるのでカーネル起動時の初期化で呼ばれる。

mem_cgroup_init()はこのような関数。

5774 static int __init mem_cgroup_init(void)
5775 {
5776         int cpu, node;
5777 
5778         hotcpu_notifier(memcg_cpu_hotplug_callback, 0);
5779 
5780         for_each_possible_cpu(cpu)
5781                 INIT_WORK(&per_cpu_ptr(&memcg_stock, cpu)->work,
5782                           drain_local_stock);
5783 
5784         for_each_node(node) {
5785                 struct mem_cgroup_tree_per_node *rtpn;
5786                 int zone;
5787 
5788                 rtpn = kzalloc_node(sizeof(*rtpn), GFP_KERNEL,
5789                                     node_online(node) ? node : NUMA_NO_NODE);
5790 
5791                 for (zone = 0; zone < MAX_NR_ZONES; zone++) {
5792                         struct mem_cgroup_tree_per_zone *rtpz;
5793 
5794                         rtpz = &rtpn->rb_tree_per_zone[zone];
5795                         rtpz->rb_root = RB_ROOT;
5796                         spin_lock_init(&rtpz->lock);
5797                 }
5798                 soft_limit_tree.rb_tree_per_node[node] = rtpn;
5799         }
5800 
5801         return 0;
5802 }
5803 subsys_initcall(mem_cgroup_init);

hotcpu_notifier()はcpu hotplugの処理なので無視して、最初のfor_each_possible_cpuマクロはper cpuなデータであるstruct memcg_stock_pcpの初期化。memcg_stock_pcp構造体はこのような構造体。現段階では初期化処理しか追っていないので、この構造体の使用目的は未確認。とりあえず、ワークキューがあるのと(struct work_struct)、nr_pagesというどう考えでもページ数の数を保持するであろうメンバ変数があるのでページ関連の操作で使われるのでしょう。

2055 struct memcg_stock_pcp {
2056         struct mem_cgroup *cached; /* this never be root cgroup */
2057         unsigned int nr_pages;
2058         struct work_struct work;
2059         unsigned long flags;
2060 #define FLUSHING_CACHED_CHARGE  0
2061 };
2062 static DEFINE_PER_CPU(struct memcg_stock_pcp, memcg_stock);

次のfor_each_nodeマクロでメモリノード単位で処理が行われる。ここで出てくるmem_cgroup_tree_per_node構造体はこんな感じなんだけど、mm/memcontrol.cで定義されてるので他では使われない模様。赤黒木なデータ構造なんですね。

 172 struct mem_cgroup_tree_per_node {
 173         struct mem_cgroup_tree_per_zone rb_tree_per_zone[MAX_NR_ZONES];
 174 };

そして、各ゾーン(これはZONE_NORMALとかですね。)毎にrbツリーの初期化をします。

5791                 for (zone = 0; zone < MAX_NR_ZONES; zone++) {
5792                         struct mem_cgroup_tree_per_zone *rtpz;
5793 
5794                         rtpz = &rtpn->rb_tree_per_zone[zone];
5795                         rtpz->rb_root = RB_ROOT;
5796                         spin_lock_init(&rtpz->lock);
5797                 }

初期化したものはsoft_limit_treeという変数のrb_tree_per_node配列に突っ込みます。

5798                 soft_limit_tree.rb_tree_per_node[node] = rtpn;

soft_limit_treeはこう宣言されていて基本は参照だけみたいです。

 180 static struct mem_cgroup_tree soft_limit_tree __read_mostly;

ちなみに、MAX_NR_ZONESはinclude/generated配下のファイルにあるのでビルド時に定義されるようです。

./include/generated/bounds.h:10:#define MAX_NR_ZONES 4 /* __MAX_NR_ZONES        # */

2つ目の初期化処理はenable_swap_account()で、これはカーネルの起動時のコマンドラインで渡されたswapaccountの処理ですね。really_do_swap_account変数に値を設定するだけです。

5877 static int __init enable_swap_account(char *s)
5878 {
5879         if (!strcmp(s, "1"))
5880                 really_do_swap_account = 1;
5881         else if (!strcmp(s, "0"))
5882                 really_do_swap_account = 0;
5883         return 1;
5884 }
5885 __setup("swapaccount=", enable_swap_account);
5886 

このコマンドライン引数がなければ呼ばれないですけど。その場合はデフォルト値があるのでそちらが使われます。

5870 /* for remember boot option*/
5871 #ifdef CONFIG_MEMCG_SWAP_ENABLED
5872 static int really_do_swap_account __initdata = 1;
5873 #else
5874 static int really_do_swap_account __initdata;
5875 #endif

3つ目はmem_cgroup_swap_init()です。memory cgroupがenableで、really_do_swap_accountが1の場合(あえてswapaccount=0を設定しなければ大抵1になってるのでは)に処理があります。

5914 static int __init mem_cgroup_swap_init(void)
5915 {
5916         if (!mem_cgroup_disabled() && really_do_swap_account) {
5917                 do_swap_account = 1;
5918                 WARN_ON(cgroup_add_legacy_cftypes(&memory_cgrp_subsys,
5919                                                   memsw_cgroup_files));
5920         }
5921         return 0;
5922 }
5923 subsys_initcall(mem_cgroup_swap_init);

do_swap_accountを1に設定しているので、swapもアカウンティングするよというのと、cgroup_add_legacy_cftypes()を呼んでいるのでmemory cgroupのファイル作成処理があります。作成するのは以下のファイルです。/sys/kernel/fs/cgroup/memoryに以下のファイルが作られます。

5887 static struct cftype memsw_cgroup_files[] = {
5888         {
5889                 .name = "memsw.usage_in_bytes",
5890                 .private = MEMFILE_PRIVATE(_MEMSWAP, RES_USAGE),
5891                 .read_u64 = mem_cgroup_read_u64,
5892         },
5893         {
5894                 .name = "memsw.max_usage_in_bytes",
5895                 .private = MEMFILE_PRIVATE(_MEMSWAP, RES_MAX_USAGE),
5896                 .write = mem_cgroup_reset,
5897                 .read_u64 = mem_cgroup_read_u64,
5898         },
5899         {
5900                 .name = "memsw.limit_in_bytes",
5901                 .private = MEMFILE_PRIVATE(_MEMSWAP, RES_LIMIT),
5902                 .write = mem_cgroup_write,
5903                 .read_u64 = mem_cgroup_read_u64,
5904         },
5905         {
5906                 .name = "memsw.failcnt",
5907                 .private = MEMFILE_PRIVATE(_MEMSWAP, RES_FAILCNT),
5908                 .write = mem_cgroup_reset,
5909                 .read_u64 = mem_cgroup_read_u64,
5910         },
5911         { },    /* terminate */
5912 };

cgroup_add_legacy_cftypes()に渡しているmemory_cgrp_subsysは以下のようになってます。

  75 struct cgroup_subsys memory_cgrp_subsys __read_mostly;
  76 EXPORT_SYMBOL(memory_cgrp_subsys);

初期化処理はこんな感じですね。