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
が有効になったカーネルが必要である。
新しい名前空間で作成される最初のプロセス
(すなわち、
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_t の
si_pid
フィールドは 0
になる。)
SIGKILL と
SIGSTOP
は例外として扱われ、
これらのシグナルが先祖の
PID
名前空間から送信された場合には強制的に配送される。
これらのシグナルはどちらも
"init"
プロセルが捕捉することはできない。
そのため、これらのシグナルに関連付けられた通常のアクション
(それぞれ、プロセスの終了とプロセスの強制停止)
が実行される。
Linux 3.4 以降では、
reboot(2)
システムコールを呼び出すと、
シグナルがその名前空間の
"init"
プロセスに送信される。
詳細は
reboot(2) を参照。
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
名前空間の変更は一方向の操作である。
PID
名前空間のファイルディスクリプターを指定して
setns(2)
を呼び出したり、
CLONE_NEWPID フラグ付きで
unshare(2)
を呼び出したりすると、
その結果作成された子プロセスは呼び出し元とは異なる
PID
名前空間に置かれる。
しかし、これらの呼び出しでは呼び出し元プロセスの
PID
名前空間は変更されない。
なぜなら、PID
名前空間を変更してしまうと、
呼び出し元が認識する
(
getpid() が返す) 自分の PID
が変わってしまい、
多くのアプリケーションやライブラリが正しく動作しなくなるからである。
別の言い方をすると、
あるプロセスがどの PID
名前空間に所属するかは、
そのプロセスが作成されたときに決定され、
それ以降は変更されることはない。
いろいろあるが、プロセス間の親子関係には、PID
名前空間の親子関係がそのまま反映されるということだ。
プロセスの親プロセスは、同じ名前空間にいるか、もしくは直接の親
PID
名前空間にいるかのいずれかである。
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_THREAD
か
CLONE_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
ファイルシステムは、
/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/
に書かれている。