Linux 2.6.24以降には、PIDネームスペースを複数持てるという機能が入っています。(PID = process ID。)
詳しい説明は Process IDs in a multi-namespace world [LWN.net] などに譲りますが、おおまかには、
- cloneシステムコールでプロセスを生成する際、CLONE_NEWPIDというフラグを指定
- すると、新たな「PIDネームスペース」が作られる
- cloneされた子プロセスは新たなPIDネームスペースに所属し、その中でPID=1になる。
- 同時に、親プロセス側のPIDネームスペースでは、そのプロセスに連続したPIDが振られる。
- 結果的に、子プロセスは各空間で1つずつ、計2種類のPIDを持った状態になる。
というような機能です。
分かりにくいので、具体例を出してみます。
1. PID=1000のプロセスがcloneをCLONE_NEWPIDフラグ付きでコール
2. 親プロセスがコピーされ、子プロセスが出来る。
このとき作られるプロセスは、親プロセスの空間では PID=1001、新たな子プロセスの空間では PID=1 に見える。
3. さらに子プロセスが(普通に)forkすると、親プロセスの空間では PID=1002、新たな子プロセスの空間では PID=2 が振られる。
新しいネームスペースからclone with CLONE_NEWPIDをコールすると、さらにツリー状に新しいネームスペースが作られていきます。
実際にプログラムを書いて試してみます。
以下のプログラムでは、clone with CLONE_NEWPIDをコールして新しいPIDネームスペースの子プロセスを作り、シェルをspawnします。
このとき、新たなネームスペース内で/procファイルシステムをマウントし直すようになっています(そうしないと、元のネームスペースの/procが見えるため、psコマンド等が親のネームスペースを表示してしまうためです)。
これでpsコマンドを実行すると、次のように新しいPIDネームスペースが見えます。
% sudo ./newpid_ns child - PID: 1 parent - PID 2832 => 2833 sh-3.2# ps ax PID TTY STAT TIME COMMAND 1 pts/2 S 0:00 ./newpid_ns <= 子プロセス 2 pts/2 R 0:00 /bin/sh <= spawnされたシェル 3 pts/2 R+ 0:00 ps ax <= 実行中のpsコマンド sh-3.2#
新たなネームスペースに属さないプロセスは見えなくなります。
逆に外のネームスペースからpsをコマンドを実行すると、上記のプロセスが異なるPIDで見えることが確認できます。
% ps ax PID TTY STAT TIME COMMAND ... 2832 pts/2 S 0:00 ./newpid_ns <= 親プロセス 2833 pts/2 S 0:00 ./newpid_ns <= 子プロセス(上記の1) 2834 pts/2 S+ 0:00 /bin/sh <= spawnされたシェル(上記の2) ...
この機能に加えて、cgroups機能でメモリ使用量やCPU帯域を制限することで、VMWareやXen,KVM等の仮想化を使うよりも緩やかに、かつchroot jailよりも強力に、OSやリソースを分割して使うことが可能になります。
特に、単一のシステムイメージ(ファイルの配置)で複数の空間を作れるので、複数のユーザにサービスを提供する必要のあるレンタルサーバのような環境では、ソフトウェアのインストールやアップグレードといったメンテナンスコストを下げられるため、有用かもしれません。
以下newpid_ns.cのソースコード。
#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/mount.h> #include <sched.h> #include <signal.h> int fork_shell() { printf("child - PID: %d\n", getpid()); mount("procfs", "/proc", "proc", 0, NULL); system("/bin/sh"); umount("/proc"); } int main(int argc, char *argv[]) { void *mem = malloc(8192); void *stack = mem +8192; int ret = clone(fork_shell, stack, SIGCHLD|CLONE_NEWNS|CLONE_NEWPID, NULL); if (ret < 0) { perror("clone"); exit(1); } printf("parent - PID %d => %d\n", getpid(), ret); wait(NULL); return 0; }