PIDネームスペースを試してみる

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帯域を制限することで、VMWareXen,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;
}