ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

cloneのCLONE_PARENTフラグの挙動を観察する

fork(2) したら子プロセスができる、皆さんもそう思っていると思います。fork(2)でプロセスを作って親でwait(2)などをして面倒を見る、というのがUNIXのプロセスの作り方の基本です。

例えばparentというプログラムをforkし、child 1からさらにchild2をforkすると下のような感じになります。 ps axf などのコマンドを発行すると、こういう感じのいわゆるプロセスツリーを随所で観察できます。

parent #-> child 1 をwait
  +--- child 1 #-> child 2 をwait
    +--- child 2

ところがLinuxでは実は「きょうだい(siblings)プロセス」とでもいうべきプロセスを作ることもできると、最近知りました。というか、概念は知ってたんですけど実際に使われている場面を初めて知りました。ということで改めてこの辺りについて手を動かしたメモを残しておきます。

clone(2), CLONE_PARENT

プロセスを新しく作るには普通はfork(2)を使います。すぐexecする場合など特殊な時はvfork(2)を使うこともあります。ここで、より複雑な条件でプロセスを作成したい場合、clone(2)というシステムコールを直接呼ぶことになります。身近なところでは、コンテナ化したプロセスを作成する場合に CLONE_NEWNS などのフラグを指定すると、Linux Namespaceを分離してプロセスを作成して、リソースの隔離を行うことができます。

(詳細な話は TenForwardさんの最新スライド などをどうぞ。unshareは呼び出したプロセスを隔離しますが、cloneは新しいプロセスをいきなり隔離できるという違いです)

他にも、主に親プロセスとのメモリやリソースの共有の条件を細かく指定できたりするのですが(スレッド作成と、fork()との間の条件でプロセスを作るようなフラグがclone(2)がある、というイメージで良いかと思われます)、その中に CLONE_PARENT というフラグがあります。 JM Projectでの翻訳を引用します

CLONE_PARENT が設定された場合、新しい子供の (getppid(2) で返される) 親プロセスは呼び出し元のプロセスの親プロセスと同じになる。

さらっとよくわからないことを言われていますが、どういうことでしょう。

どのような挙動か観察する

clone-tarou.c というプログラムを用意しました。これをチェックアウトしてLinuxのシステムでビルドします。

git clone https://gist.github.com/cee5176635831303be3523b1c573fc08.git
cd cee5176635831303be3523b1c573fc08
make clone-tarou

このclone-tarouを以下のように起動すると、ごく普通の親→子→孫なプロセスを観察できます。

$ ./clone-tarou parent
26620 pts/1    S+     0:00              \_ ./clone-tarou parent
26621 pts/1    S+     0:00                  \_ clone-tarou tarou
26622 pts/1    S+     0:00                      \_ clone-tarou jirou
[!] Jirou (26622) is added under tarou and exit
[!] exit: PID=26621

一方、 --use-clone-parent というフラグをつけると、本来孫であるはずのプロセスがsiblingとして作成されていること、その親が両方をwaitしてくれることがわかります。

$ ./clone-tarou parent --use-clone-parent                                                                          
[!] Hey, maybe a new sibling is added
26627 pts/1    S+     0:00              \_ ./clone-tarou parent --use-clone-parent
26628 pts/1    S+     0:00                  \_ clone-tarou tarou --use-clone-parent
26629 pts/1    S+     0:00                  \_ clone-tarou jirou
[!] exit: PID=26628
[!] exit: PID=26629

この辺 でフラグを見てCLONE_PARENTを付与しているためですね。

どこで使われていたか

最近CRIUの話ばかりで恐縮ですが、まさにCRIUの中で使われていました。

プロセスを再生する際に、必ずしもcriuのコマンドの下(以下criuプロセスと呼ぶ)に再生したくない場合があります。

criu.org

その際に --restore-sibling オプションをつけてプロセスを再生すると、一旦criuプロセスの兄弟、つまりcriuプロセスを呼び出したプロセスを親として再生します。そうしてcriuプロセスが終了することで、再生されたプロセスはそのまま任意の、「criuプロセスを呼び出したプロセス」の子供とすることができます。もちろんwaitなどをして再生されたプロセスを再び管理することも可能です。

ちょっと、自然言語でうまく言えている感じがしないので、上記の公式Wikiの図を見て欲しいですが... --;

で、この仕組みの実現のために、 CLONE_PARENT付きでcloneを呼び出している箇所 があります。


ということでclone(2)と少し仲良くなれました。こんな感じでCRIU、謎の技術をたくさん使っているので勉強になりますね...。

次回は、「どのようにCRIUは再生後のプロセスのPIDを元のものと一致させているか」という話をするかも(しないかも)。