LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術

第30回 Linuxカーネルのコンテナ機能[8]― cgroupのpidsサブシステム

この記事を読むのに必要な時間:およそ 5 分

pidsサブシステムのカーネルの実装

今回は,Linux Advent Calendar 2015への参加を兼ねているので,普段の連載とは少し趣向を変えて,pidsサブシステムがカーネルでどのように実装されているかを軽く見てみましょう。

昨年のLinux Advent Calendar 201425日目「システムコールの探しかた」を参考に,fork()を探してみるとkernel/fork.c中に以下が見つかります(見やすくするために必要な行だけ抜き出しています⁠⁠。

  1792 SYSCALL_DEFINE0(fork)
  1793 {
           :(略)
  1795         return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
           :(略)
  1800 }

以上のfork()の定義行の少し下にはvfork()clone()の定義もあります。いずれも_do_forkという関数を呼んでいますので,新しくプロセスを作る場合の処理は_do_fork関数を見れば良いことがわかります。

_do_fork関数も同じくkernel/fork.c中にあります。

  1697 long _do_fork(unsigned long clone_flags,
  1698         unsigned long stack_start,
  1699         unsigned long stack_size,
  1700         int __user *parent_tidptr,
  1701         int __user *child_tidptr,
  1702         unsigned long tls)
  1703 {
           :(略)
  1726         p = copy_process(clone_flags, stack_start, stack_size,
  1727                          child_tidptr, NULL, trace, tls);
           :(略)

_do_fork関数は指定されたcloneフラグにしたがって各種資源のコピーを作成します。このコピーを行うのが、上記のcopy_process関数です。この関数の中では,指定されたcloneフラグの組み合わせをチェックしたり,copy_で始まる名前の関数が多数呼ばれて,いろいろコピーされてるっぽいことがなんとなくわかります。

このcopy_process関数をきちっと追っていけば,pidsサブシステムで制限をかけている部分がわかるはずです。しかし,私の技術力では途中でつまづく可能性が高いので,手っ取り早くpidsサブシステムがカーネルにマージされた際のパッチを見てみました。

pidsサブシステムは比較的シンプルに実装されていて,以下の2つのマージされたパッチで実現されています。

1つ目のパッチを見てみると,ずばりcopy_process()関数内の処理にパッチが当たっていることがわかります。パッチが当たっている部分を見てみると,以下のようなコメントとともにcgroup_can_fork()関数が呼ばれています。

  1526         /*
  1527          * Ensure that the cgroup subsystem policies allow the new process to be
  1528          * forked. It should be noted the the new process's css_set can be changed
  1529          * between here and cgroup_post_fork() if an organisation operation is in
  1530          * progress.
  1531          */
  1532         retval = cgroup_can_fork(p, cgrp_ss_priv);
  1533         if (retval)
  1534                 goto bad_fork_free_pid;

ここでエラーになると,エラーの後処理部分に処理が飛び,新しいプロセスは生成できなさそうなことがわかります。

2つ目のパッチはpidsサブシステム自体の処理を実装してあり,実際のプロセスの生成を制限するような処理はありません。

もう少し詳しく見ると正確に処理がわかるとは思います。しかしパッチをさらっと見るだけでも,fork()vfork()clone()システムコールの実体である関数内にチェックが追加され,新しいプロセスの生成が制御されてるらしいということはわかります。他に同様のチェックが追加されている部分はなさそうです。これを理解した上で,もう少しpidsサブシステムの動きを見てみましょう。

制限以上のプロセスをグループに追加する

pids.maxを設定した上で,グループにプロセスを追加していってみましょう。

$ sudo mkdir /sys/fs/cgroup/pids/test01
$ echo 2 | sudo tee /sys/fs/cgroup/pids/test01/pids.max
2

制限を2に設定したので,test01グループにどんどんプロセスを追加してみましょう。

$ cat /sys/fs/cgroup/pids/test01/tasks  (登録されているプロセスはない)
$ echo 7189 | sudo tee /sys/fs/cgroup/pids/test01/tasks
7189  (1つ目のプロセス追加)
$ echo 7855 | sudo tee /sys/fs/cgroup/pids/test01/tasks
7855  (2つ目のプロセス追加)
$ echo 8885 | sudo tee /sys/fs/cgroup/pids/test01/tasks
8885  (3つ目のプロセス追加。エラーにならない)
$ echo 8992 | sudo tee /sys/fs/cgroup/pids/test01/tasks
8992  (4つ目のプロセス追加。エラーにならない)
$ cat /sys/fs/cgroup/pids/test01/pids.current
4     (pids.max以上のプロセスが登録されている)

pids.maxを2に設定したのに,pids.currentがそれ以上になってもエラーにはなりません。

この動きは,先に紹介したカーネルの実装を考えると納得できます。実装では,新たにプロセスを生成しようとする処理で制限のチェックがされていました。すでに生成されているプロセスを新たにグループに追加しても,そのチェックには引っかからないので,このように制限値以上にtasksにプロセスを追加していっても,既存のプロセスが影響を受けることはありません。

もちろん,上記の状態で新たにプロセスを生成させようとすると,pidsサブシステムにより新たにプロセスは生成できません。

$ echo $$ | sudo tee /sys/fs/cgroup/pids/test01/tasks
10648  (さらにプロセスをtest01に追加)
$ cat /sys/fs/cgroup/pids/test01/tasks
bash: fork: retry: No child processes  (新たにプロセスは起動しない)
    :(略)

プロセスが登録された状態でグループの制限値を変更する

さらに,pids.max < pids.currentとなるようなケースを試してみましょう。

$ cat /sys/fs/cgroup/pids/test01/pids.max
4
$ cat /sys/fs/cgroup/pids/test01/pids.current
4

以上のようにすでに制限値に達しているグループがあります。この状態でpids.maxを減らしてみましょう。

$ echo 2 | sudo tee /sys/fs/cgroup/pids/test01/pids.max
2  (制限値を2に減らす)
$ cat /sys/fs/cgroup/pids/test01/pids.current
4  (グループ内のプロセス数は4のまま)

プロセスが登録された状態で,登録されているプロセス数以下の制限値を設定しても,プロセスはそのままです。

この動きも,先ほどのプロセスをグループに追加していった場合と同様に,カーネルの実装から考えると納得できる動きですね。

まとめ

今回は少し予定を変更して,カーネル4.3で導入されたpidsサブシステムを紹介しました。

  • pidsサブシステムを使うと,グループ内で起動するプロセス数が制限できる
  • 制限はこれから起動しようとするプロセスに対する制限で,既存のプロセスには影響を与えない

サブシステムの動きだけを見ると,なぜこのような動きになるのだろうという疑問がわくかもしれません。実装を少し見ることにより,そのような動きになる理由が理解できたのではないでしょうか。

カーネルのコードはもう少し詳細に追っかけないと,サブシステムの動きが納得できないかもしれません。しかしこれ以上やるとそれだけで連載何回分にもなりそうなので,最小限の紹介にとどめました。興味のある方はこの記事をきっかけに詳細に動きを追ってみてください。

次回は,またLXC 1.1の新機能の紹介に戻る予定です。

著者プロフィール

加藤泰文(かとうやすふみ)

2009年頃にLinuxカーネルのcgroup機能に興味を持って以来,Linuxのコンテナ関連の最新情報を追っかけたり,コンテナの勉強会を開いたりして勉強しています。英語力のない自分用にLXCのmanページを日本語訳していたところ,あっさり本家にマージされてしまい,それ以来日本語訳のパッチを送り続けています。

Plamo Linuxメンテナ

Twitter:@ten_forward
技術系のブログ:http://tenforward.hatenablog.com/