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プロセスと呼ぶ)に再生したくない場合があります。
その際に --restore-sibling
オプションをつけてプロセスを再生すると、一旦criuプロセスの兄弟、つまりcriuプロセスを呼び出したプロセスを親として再生します。そうしてcriuプロセスが終了することで、再生されたプロセスはそのまま任意の、「criuプロセスを呼び出したプロセス」の子供とすることができます。もちろんwaitなどをして再生されたプロセスを再び管理することも可能です。
ちょっと、自然言語でうまく言えている感じがしないので、上記の公式Wikiの図を見て欲しいですが... --;
で、この仕組みの実現のために、 CLONE_PARENT付きでcloneを呼び出している箇所 があります。
ということでclone(2)と少し仲良くなれました。こんな感じでCRIU、謎の技術をたくさん使っているので勉強になりますね...。
次回は、「どのようにCRIUは再生後のプロセスのPIDを元のものと一致させているか」という話をするかも(しないかも)。