名前

pid_namespaces - Linux PID 名前空間の概要

説明

名前空間の概要については namespaces(7) を参照。
 
PID 名前空間はプロセス ID 番号空間を分離する。 これは、異なる PID 名前空間のプロセスは同じ PID を持つことができることを意味する。 PID 名前空間を使うことで、コンテナー内のプロセス群を中断、再開したり、 コンテナー内のプロセスの PID を保持したままコンテナーを新しいホストに移行したりするといった機能をコンテナーが提供することが可能になる。
 
新しい PID 名前空間の PID は、 独立したシステムであるかのように、 1 から始まる。 fork(2), vfork(2), clone(2) を呼び出すと、 その名前空間内で一意な PID でプロセスが生成される。
 
PID 名前空間を使用するには、設定 CONFIG_PID_NS が有効になったカーネルが必要である。

名前空間の init プロセス

新しい名前空間で作成される最初のプロセス (すなわち、 CLONE_NEWPID フラグで clone(2) を使って作成されたプロセスや、 CLONE_NEWPID フラグで unshare(2) を呼び出した後のプロセスによって作成された最初のプロセス) は PID 1 を持ち、 そのプロセスはその名前空間の "init" プロセスとなる ( init(1) 参照)。 名前空間内でみなしごになった (親プロセスがいなくなった) 子プロセスは、 init(1) ではなくこのプロセスが親プロセスになる (ただし、 同じ PID 名前空間内のその子プロセスの先祖が、 prctl(2)PR_SET_CHILD_SUBREAPER コマンドを使って、 自分自身をみなしごとなった子孫のプロセスの引き取り手になっている場合はこの限りではなく)。
 
PID 名前空間の "init" プロセスが終了すると、 カーネルはその名前空間の全プロセスを SIGKILL シグナルで終了する。 この動作は、 PID 名前空間の正しい操作のためには "init" プロセスは不可欠であるという事実を反映したものである。 この場合、 その PID 名前空間へのそれ以降の fork(2) はエラー ENOMEM で失敗する。 "init" プロセスが終了している PID 名前空間に新しいプロセスを作成することはできない。 このような状況は、 例えば、 名前空間にいたプロセスに対応する /proc/[pid]/ns/pid ファイルに対してオープンしたファイルディスクリプターを使って、 "init" プロセスが終了した後にその名前空間に setns(2) を行った場合に起こり得る。 unshare(2) を呼び出した後にも、この状況は起こり得る。 それ以降に fork(2) で作成された最初の子プロセスが終了すると、 それ以降の fork(2) の呼び出しは NOMEM で失敗する。
 
PID 名前空間の他のメンバーは、 "init" プロセスがシグナルハンドラーを設定したシグナルだけを、 "init" プロセスに送信することができる。 この制限は特権プロセスに対しても適用される。 この制限により、 PID 名前空間の他のメンバーがうっかり "init" プロセスを殺してしまうのを防ぐことができる。
 
同様に、 先祖の名前空間のプロセスは、 "init" プロセスがそのシグナルに対するハンドラーを設定している場合にのみ、 kill(2) で説明されている通常のアクセス許可のチェックを経た上で、 子供の PID 名前空間の "init" プロセスにシグナルを送信できる。 (ハンドラー内では、 sigaction(2) に説明がある siginfo_tsi_pid フィールドは 0 になる。) SIGKILLSIGSTOP は例外として扱われ、 これらのシグナルが先祖の PID 名前空間から送信された場合には強制的に配送される。 これらのシグナルはどちらも "init" プロセルが捕捉することはできない。 そのため、これらのシグナルに関連付けられた通常のアクション (それぞれ、プロセスの終了とプロセスの強制停止) が実行される。
 
Linux 3.4 以降では、 reboot(2) システムコールを呼び出すと、 シグナルがその名前空間の "init" プロセスに送信される。 詳細は reboot(2) を参照。

ネストされた PID 名前空間

PID 名前空間は入れ子にすることができる。 最初の ("root") PID 名前空間以外の各 PID 名前空間は親を持つ。 PID 名前空間の親は clone(2)unshare(2) を使ってその名前空間を作成したプロセスの PID 名前空間である。 したがって、 PID 名前空間は木構造を構成し、 すべての名前空間は親を辿って行くと、最終的には root 名前空間に辿り着く。
 
プロセスは、所属する PID 名前空間の他のプロセスから見える。また、 root PID 名前空間に向かう直径の先祖の各 PID 名前空間のプロセスからも見える。 この場合、「見える」とは、 あるプロセスが、 他のプロセスがプロセス ID を指定するシステムコールを使う際に操作の対象にできることを意味する。 逆に、子供 PID 名前空間のプロセスから親や先祖の名前空間のプロセスは見えない。 あるプロセスは自分自身の PID 名前空間とその子孫の名前空間のプロセスだけが見える (例えば、 kill(2) でシグナルを送信したり、 setpriority(2) で nice 値を設定したり、など)。
 
プロセスは、そのプロセスが見える PID 名前空間の階層の各層においてプロセス ID を一つ持ち、 直接の先祖の名前空間を辿ることで通って root PID 名前空間に至ることができる。 プロセス ID に対して操作を行うシステムコールは、常に、呼び出し元プロセスの PID 名前空間で見えるプロセス ID を使って操作を行う。 getpid(2) の呼び出しでは、 常に、 プロセスが作成された名前空間に関連付けられた PID を返す。
 
PID 名前空間内のプロセスは名前空間の外部に親プロセスを持つことができる。 例えば、その名前空間の初期プロセス (すなわち PID 1 を持つ init(1) プロセス) の親プロセスは必然的に別の名前空間に属すことになる。 同様に、 あるプロセスが setns(2) を使って子プロセスを PID 名前空間に参加させた場合、 子プロセスは setns(2) の呼び出し元とは異なる PID 名前空間に属す。 子プロセスで getppid(2) を呼び出すと 0 が返される。
 
プロセスは ( setns(2)CLONE_NEWPID で使うなどで) 子供の PID 名前空間に自由に入ることができるが、 逆の方向には移動できない。 つまり、 プロセスは先祖の名前空間 (親、親の親など) に入ることはできない。 PID 名前空間の変更は一方向の操作である。

setns(2)_と_unshare(2)_の動作">setns(2)_%E3%81%A8_unshare(2)_%E3%81%AE%E5%8B%95%E4%BD%9C">setns(2)unshare(2) の動作

PID 名前空間のファイルディスクリプターを指定して setns(2) を呼び出したり、 CLONE_NEWPID フラグ付きで unshare(2) を呼び出したりすると、 その結果作成された子プロセスは呼び出し元とは異なる PID 名前空間に置かれる。 しかし、これらの呼び出しでは呼び出し元プロセスの PID 名前空間は変更されない。 なぜなら、PID 名前空間を変更してしまうと、 呼び出し元が認識する ( getpid() が返す) 自分の PID が変わってしまい、 多くのアプリケーションやライブラリが正しく動作しなくなるからである。
 
別の言い方をすると、 あるプロセスがどの PID 名前空間に所属するかは、 そのプロセスが作成されたときに決定され、 それ以降は変更されることはない。 いろいろあるが、プロセス間の親子関係には、PID 名前空間の親子関係がそのまま反映されるということだ。 プロセスの親プロセスは、同じ名前空間にいるか、もしくは直接の親 PID 名前空間にいるかのいずれかである。

CLONE_NEWPID の他の CLONE_* フラグとの互換性

CLONE_NEWPID はいくつかの他の CLONE_* フラグと組み合わせることができない。
*
CLONE_THREAD は、 プロセス内のスレッド間で互いにシグナルを送信できるようにするため、 同じ PID 名前空間に属している必要がある。 同様に、 プロセス内の全スレッドが proc(5) ファイルシステムで見える必要がある。
*
CLONE_SIGHAND は、同じ PID 名前空間である必要がある。 さもなければ、 シグナルが送信された際に、シグナルを送信したプロセスのプロセス ID を意味のある形でエンコードすることができない ( sigaction(2)siginfo_t 型の説明を参照)。 複数の PID 名前空間に属するプロセス間で一つのシグナルキューを共有すると、うまく動かなくなる。
*
CLONE_VM は、全スレッドが同じ PID 名前空間に属している必要がある。 なぜなら、 コアダンプの観点から見ると、 2 つのプロセスが同じアドレス空間を共有していれば、 これらはスレッドであり、コアダンプが一緒に行われるからである。 コアダンプが書き込まれる際に、 各スレッドの PID がコアダンプに書き込まれる。 もしプロセス ID のいくつかが親 PID 名前空間に属していたとすると、 プロセス ID の書き込みは意味を持たなくなってしまう。
まとめると、 CLONE_THREAD, CLONE_SIGHAND, CLONE_VM では技術的な要件として PID 名前空間が共有されている点がある。 (さらに clone(2) では CLONE_THREADCLONE_SIGHAND が指定された際には CLONE_VM が指定されている必要がある点にも注意。) したがって、以下のような順序で呼び出しを行うと (エラー EINVAL で) 失敗する。
 
    unshare(CLONE_NEWPID);
    clone(..., CLONE_VM, ...);    /* Fails */
setns(fd, CLONE_NEWPID); clone(..., CLONE_VM, ...); /* Fails */
clone(..., CLONE_VM, ...); setns(fd, CLONE_NEWPID); /* Fails */
clone(..., CLONE_VM, ...); unshare(CLONE_NEWPID); /* Fails */

/proc と PID 名前空間

/proc ファイルシステムは、 /proc のマウントを行ったプロセスの PID 名前空間で見えるプロセスだけを表示する。 たとえ、 その /proc ファイルシステムが他の名前空間のプロセスから参照されたとしても、そうである。
 
新しい PID 名前空間を作成した後、 子プロセスが、自身の root ディレクトリを変更し、新しい procfs インスタンスを /proc にマウントするのは ps(1) などのツールが正しく動作するためにも有用である。 clone(2)flags 引き数に CLONE_NEWNS も指定されて新しいマウント名前空間が同時に作成された場合は、 root ディレクトリを変更する必要はない。 新しい procfs インスタンスを /proc にそのままマウントすることができる。
 
シェルから、コマンドで /proc のマウントを行うには次のようにする。
 

$ mount -t proc proc /proc
 
パス /proc/self に対して readlink(2) を呼び出すと、 procfs のマウントを行ったプロセスの PID 名前空間におけるプロセス ID が得られる。 これは調査目的でプロセスが他の名前空間で自身の PID を知りたい場合などに役立つ。

その他

プロセス ID が UNIX ドメインソケット経由で別の PID 名前空間のプロセスに渡される場合 ( unix(7)SCM_CREDENTIALS の説明を参照)、 プロセス ID は受信プロセスの PID 名前空間での対応する PID 値に翻訳される。

準拠

名前空間は Linux 独自の機能である。

user_namespaces(7) 参照。

関連項目

clone(2), setns(2), unshare(2), proc(5), credentials(7), capabilities(7), user_namespaces(7), switch_root(8)

この文書について

この man ページは Linux man-pages プロジェクトのリリース 3.79 の一部 である。プロジェクトの説明とバグ報告に関する情報は http://www.kernel.org/doc/man-pages/ に書かれている。