/* glibc ラッパー関数のプロトタイプ */ #include <sched.h> int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ... /* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ ); /* 素のシステムコールのプロトタイプ */ long clone(unsigned long flags, void *child_stack, void *ptid, void *ctid, struct pt_regs *regs);
glibc ラッパー関数の機能検査マクロの要件 (feature_test_macros(7) 参照):
clone():
このページでは、 glibc の clone() ラッパー関数とその裏で呼ばれるシステムコールの両方について説明している。 メインの説明はラッパー関数に関するものである。 素のシステムコールにおける差分はこのページの最後の方で説明する。
fork(2) とは異なり、clone() では、子プロセス (child process) と呼び出し元のプロセスとが、メモリー空間、ファイルディスクリプターのテーブル、シグナルハンドラーのテーブルなどの 実行コンテキストの一部を共有できる。 (このマニュアルにおける「呼び出し元のプロセス」は、通常は 「親プロセス」と一致する。但し、後述の CLONE_PARENT の項も参照のこと)
clone() の主要な使用法はスレッド (threads) を実装することである: 一つのプログラムの中の複数のスレッドは共有されたメモリー空間で 同時に実行される。
clone() で子プロセスが作成された時に、作成された子プロセスは関数 fn(arg) を実行する。 (この点が fork(2) とは異なる。 fork(2) の場合、子プロセスは fork(2) が呼び出された場所から実行を続ける。) fn 引き数は、子プロセスが実行を始める時に子プロセスが呼び出す 関数へのポインターである。 arg 引き数はそのまま fn 関数へと渡される。
fn(arg) 関数が終了すると、子プロセスは終了する。 fn によって返された整数が子プロセスの終了コードとなる。 子プロセスは、 exit(2) を呼んで明示的に終了することもあるし、致命的なシグナルを受信した 場合に終了することもある。
child_stack 引き数は、子プロセスによって使用されるスタックの位置を指定する。 子プロセスと呼び出し元のプロセスはメモリーを共有することがあるため、 子プロセスは呼び出し元のプロセスと同じスタックで実行することができない。 このため、呼び出し元のプロセスは子プロセスのスタックのためのメモリー空間を 用意して、この空間へのポインターを clone() へ渡さなければならない。 (HP PA プロセッサ以外の) Linux が動作する全てのプロセッサでは、 スタックは下方 (アドレスが小さい方向) へと伸びる。このため、普通は child_stack は子プロセスのスタックのために用意したメモリー空間の一番大きい アドレスを指すようにする。
flags の下位 1 バイトは子プロセスが死んだ場合に親プロセスへと送られる 終了シグナル (termination signal) の番号を指定する。このシグナルとして SIGCHLD 以外が指定された場合、親プロセスは、 wait(2) で子プロセスを待つ際に、オプションとして __WALL または __WCLONE を指定しなければならない。 どのシグナルも指定されなかった場合、子プロセスが終了した時に親プロセス にシグナルは送られない。
flags には、以下の定数のうち 0個以上をビット毎の論理和 (bitwise-or) をとったものを指定できる。これらの定数は呼び出し元のプロセスと 子プロセスの間で何を共有するかを指定する:
CLONE_FILES が設定されていない場合、子プロセスは、 clone() が実行された時点で、呼び出し元のプロセスがオープンしている全ての ファイルディスクリプターのコピーを継承する (子プロセスの複製されたファイルディスクリプターは、 対応する呼び出し元のプロセスのファイルディスクリプターと 同じファイル記述 (open(2) 参照) を参照する)。 これ以降に、呼び出し元のプロセスと子プロセスの一方が ファイルディスクリプターの操作 (ファイルディスクリプターの オープン・クローズや、ファイルディスクリプターフラグの変更) を行っても、もう一方のプロセスには影響を与えない。
CLONE_FS が設定されていない場合、子プロセスは、 clone() が実行された時点での、呼び出し元のプロセスのファイルシステム情報のコピーを 使用する。 これ以降は、呼び出し元のプロセスと子プロセスの一方が chroot(2), chdir(2), umask(2) を呼び出しても、もう一方のプロセスには影響を与えない。
I/O コンテキストは、ディスクスケジュールの I/O スコープである (言い換えると、I/O コンテキストは I/O スケジューラがプロセス I/O の スケジューリングをモデル化するのに使用される)。 複数のプロセスが同じ I/O コンテキストを共有する場合、 これらのプロセスは I/O スケジューラからは一つとして扱われる。 結果として、これらのプロセスはディスクアクセスの時間を共有するようになる。 いくつかの I/O スケジューラでは、 二つのプロセスが I/O コンテキストを共有している場合、 これらのプロセスはディスクアクセスを交互に行うことができる。 同じプロセスの複数のスレッドが I/O を実行している場合 (例えば aio_read(3))、 CLONE_IO を利用することで I/O 性能を良くすることができる。
カーネルの設定が CONFIG_BLOCK オプション付きでない場合、 このフラグは何の意味も持たない。
IPC 名前空間は、独立の System V IPC オブジェクト空間 (svipc(7) 参照) を提供する 。 (Linux 2.6.30 以降では) 独立した POSIX メッセージキュー空間 (mq_overview(7) 参照) も提供される。 これらの IPC 機構に共通の特徴として、 IPC オブジェクトはファイルシステムのパス名とは違った仕組みで識別されるという点がある。
ある IPC 名前空間に作成されたオブジェクトは、 その名前空間のメンバーである他のすべてのプロセスからも見えるが、 違う IPC 名前空間のプロセスからは見えない。
IPC 名前空間が破棄される時 (すなわち、その名前空間のメンバーの最後のプロセスが終了する時)、 その名前空間の全ての IPC オブジェクトは自動的に破棄される。
特権プロセス (CAP_SYS_ADMIN) だけが CLONE_NEWIPC を使用できる。 このフラグは CLONE_SYSVSEM と組み合わせて指定することはできない。
IPC 名前空間の詳細は namespaces(7) を参照。
CLONE_NEWNET が設定された場合、新しいネットワーク名前空間 (network namaspace) でプロセスを作成する。 このフラグが設定されていない場合、 (fork(2) の場合と同様) 呼び出し元のプロセスと同じネットワーク名前空間でプロセスが 作成される。 このフラグは、コンテナの実装での使用を意図して用意されたものである。
ネットワーク名前空間は、分離されたネットワークスタックを提供するものである (ネットワークスタックとは、 ネットワークデバイスインターフェース、IPv4 や IPv6 プロトコルスタック、 /proc/net、 /sys/class/net ディレクトリツリー、ソケットなどである)。 物理ネットワークデバイスが所属できるネットワーク名前空間は一つだけである。 仮想ネットワークデバイス ("veth") のペアにより パイプ風の抽象化 (abstraction) が実現されており、 これを使うことで、ネットワーク名前空間間のトンネルを作成したり、 別の名前空間の物理ネットワークデバイスへのブリッジを作成したり することができる。
ネットワーク名前空間が解放される時 (すなわち、その名前空間の最後のプロセスが終了する時)、 物理ネットワークデバイスは初期ネットワーク名前空間 (initial network namespace) に戻される (親プロセスのネットワーク名前空間に戻される訳ではない)。 ネットワーク名前空間のさらなる情報は namespaces(7) を参照。
特権プロセス (CAP_SYS_ADMIN) だけが CLONE_NEWNET を使用できる。
マウント名前空間の詳細は namespaces(7) を参照。
特権プロセス (CAP_SYS_ADMIN) のみが CLONE_NEWNS を指定することができる。 一つの clone() 呼び出しで、 CLONE_NEWNS と CLONE_FS の両方を指定することはできない。
PID 名前空間の詳細は namespaces(7) と pid_namespaces(7) を参照。
特権プロセス (CAP_SYS_ADMIN) だけが CLONE_NEWPID を使用できる。 このフラグは CLONE_THREAD や CLONE_PARENT と組み合わせて指定することはできない。
CLONE_NEWUSER がセットされている場合、新しいユーザー名前空間でプロセスを作成する。 このフラグがセットされていない場合、 (fork(2) の場合と同様に) 呼び出し元のプロセスと同じユーザー名前空間でプロセスが作成される。
ユーザー名前空間の詳細は namespaces(7) と user_namespaces(7) を参照。
Linux 3.8 より前では、 CLONE_NEWUSER を使用するには、 呼び出し元は CAP_SYS_ADMIN, CAP_SETUID, CAP_SETGID の 3 つのケーパリビティを持っている必要があった。 Linux 3.8 以降では、 ユーザー名前空間を作成するのに特権は必要なくなった。
このフラグは CLONE_THREAD や CLONE_PARENT と組み合わせて指定することはできない。 セキュリティ上の理由から、 CLONE_NEWUSER は CLONE_FS と組み合わせて指定することはできない。
ユーザー名前空間の詳細は user_namespaces(7) を参照。
UTS 名前空間は、 uname(2) が返す識別子の集合である。 識別子としてはドメイン名とホスト名があり、 それぞれ setdomainname(2), sethostname(2) で修正することができる。 ある UTS 名前空間における識別子の変更は同じ名前空間の他のすべての プロセスに見えるが、別の UTS 名前空間のプロセスには見えない。
特権プロセス (CAP_SYS_ADMIN) だけが CLONE_NEWUTS を使用できる。
UTS 名前空間の詳細は namespaces(7) を参照。
CLONE_PARENT が設定されていない場合、 (fork(2) と同様に) 呼び出し元のプロセスがその子供の親になる。
子供が終了した時にシグナルが送られるのは getppid(2) が返す親プロセスである点に注意すること。このため CLONE_PARENT が設定された場合、呼び出し元のプロセスではなく呼び出し元のプロセスの 親プロセスにシグナルが送られる。
CLONE_SIGHAND が設定されていない場合、子プロセスは clone() が実行された時点での、呼び出し元のプロセスのシグナルハンドラーの コピーを継承する。これ以降は、一方のプロセスが sigaction(2) を呼び出しても、もう一方のプロセスには影響を与えない。
Linux 2.6.0-test6 以降では、 CLONE_SIGHAND を指定する場合、 CLONE_VM も flags に含めなければならない。
このフラグは Linux 2.6.25 以降では非推奨であり、 Linux 2.6.38 で完全に削除された。
スレッドグループは、 スレッド集合で一つの PID を共有するという POSIX スレッドの概念をサポートするために Linux 2.4 に加えられた機能であった。 内部的には、この共有 PID はいわゆるそのスレッドグループの スレッドグループ識別子 (TGID) である。 Linux 2.4 以降では、 getpid(2) の呼び出しではそのプロセスのスレッドグループ ID を返す。
あるグループに属するスレッドは (システム全体で) 一意なスレッド ID (TID) で区別できる。新しいスレッドの TID は clone() の呼び出し元へ関数の結果として返され、 スレッドは自分自身の TID を gettid(2) で取得できる。
CLONE_THREAD を指定せずに clone() の呼び出しが行われると、 生成されたスレッドはそのスレッドの TID と同じ値の TGID を持つ 新しいスレッドグループに置かれる。このスレッドは 新しいスレッドグループの「リーダー」である。
CLONE_THREAD を指定して作成された新しいスレッドは、 (CLONE_PARENT の場合と同様に) clone() を呼び出し元と同じ親プロセスを持つ。 そのため、 getppid(2) を呼ぶと、一つのスレッドグループに属すスレッドは全て同じ値を返す。 CLONE_THREAD で作られたスレッドが終了した際に、 そのスレッドを clone() を使って生成したスレッドには SIGCHLD (もしくは他の終了シグナル) は送信されない。 また、 wait(2) を使って終了したスレッドの状態を取得することもできない (そのようなスレッドは detached (分離された) といわれる)。
スレッドグループに属す全てのスレッドが終了した後、 そのスレッドグループの親プロセスに SIGCHLD (もしくは他の終了シグナル) が送られる。
スレッドグループに属すいずれかのスレッドが execve(2) を実行すると、スレッドグループリーダー以外の全てのスレッドは 終了され、新しいプロセスがそのスレッドグループリーダーの下で 実行される。
スレッドグループに属すスレッドの一つが fork(2) を使って子プロセスを作成した場合、 スレッドグループのどのスレッドであっても その子供を wait(2) できる。
Linux 2.5.35 以降では、 CLONE_THREAD を指定する場合、 flags に CLONE_SIGHAND も含まれていなければならない (Linux 2.6.0-test6 以降では、 CLONE_SIGHAND を指定する場合 CLONE_VM も指定する必要がある点に注意すること)。
kill(2) を使ってスレッドグループ全体 (つまり TGID) にシグナルを送ることもできれば、 tgkill(2) を使って特定のスレッド (つまり TID) にシグナルを送ることもできる。
シグナルの配送と処理はプロセス全体に影響する: ハンドラーを設定していないシグナルがあるスレッドに配送されると、 そのシグナルはスレッドグループの全メンバーに影響を及ぼす (終了したり、停止したり、動作を継続したり、無視されたりする)。
各々のスレッドは独自のシグナルマスクを持っており、 sigprocmask(2) で設定できる。 だが、処理待ちのシグナルには、 kill(2) で送信されるプロセス全体に対するもの (つまり、スレッドグループの どのメンバーにも配送できるもの) と、 tgkill(2) で送信される個々のスレッドに対するものがありえる。 sigpending(2) を呼び出すと、プロセス全体に対する処理待ちシグナルと呼び出し元の スレッドに対する処理待ちシグナルを結合したシグナル集合が返される。
kill(2) を使ってスレッドグループにシグナルが送られた場合で、 そのスレッドグループがそのシグナルに対するシグナルハンドラーが 登録されていたときには、シグナルハンドラーはスレッドグループの メンバーのうち、ただ一つのスレッドでだけ起動される。ハンドラーが 起動されるスレッドは、そのシグナルを禁止 (block) していない メンバーの中から一つだけが勝手に (arbitrarily) 選ばれる。 スレッドグループに属す複数のスレッドが sigwaitinfo(2) を使って同じシグナルを待っている場合、 これらのスレッドの中から一つをカーネルが勝手に選択し、 そのスレッドが kill (2) を使って送信されたシグナルを受信する。
CLONE_VFORK が設定されていない場合、 clone() 呼び出し後は、呼び出し元のプロセスと子プロセスの 両方がスケジュール対象となり、アプリケーションはこれらのプロセスの 実行順序に依存しないようにすべきである。
CLONE_VM が設定されていない場合、子プロセスは clone() が実行された時点での、親プロセスのメモリー空間をコピーした 別のメモリー空間で実行される。 一方のプロセスが行ったメモリーへの書き込みや ファイルのマップ/アンマップは、 fork(2) の場合と同様、もう一方のプロセスには影響しない。
long clone(unsigned long flags, void *child_stack, void *ptid, void *ctid, struct pt_regs *regs);生のシステムコールのもう一つの違いは、 child_stack 引き数がゼロでも良いことである。この場合には、どちらかのプロセスが スタックを変更した時に、書き込み時コピー (copy-on-write) 方式により 子プロセスがスタックページの独立したコピーを得られることが保証される。 この場合、正常に動作させるためには、 CLONE_VM オプションを指定してはならない。
いくつかのアーキテクチャーでは、システムコールの引き数の順序は上記とは異なっている。 microblaze, ARM, ARM 64, PA-RISC, arc, Power PC, xtensa, MIPS アーキテクチャーでは、 4 番目と 5 番目の引き数の順番が逆である。 cris と s390 アーキテクチャーでは、最初と 2 番目の引き数の順番が逆である。
int __clone2(int (*fn)(void *), void *child_stack_base, size_t stack_size, int flags, void *arg, ... /* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );
上記のプロトタイプは glibc ラッパー関数用のものである。 素のシステムコールのインターフェースには引き数 fn と arg がない。 また、引き数の順序が変わり、 flags が最初の引き数で、 tls が最後の引き数である。
__clone2() は clone() と同じように動作するが、以下の点が異なる: child_stack_base は子プロセスのスタックエリアの最小のアドレスを指し、 stack_size は child_stack_base が指し示すスタックエリアの大きさを示す。
CLONE_DETACHED というフラグが、2.5.32 で導入されて以来しばらくの間存在した。 このフラグは親プロセスが子プロセス終了のシグナルを必要としないことを 表すものである。 2.6.2 で、 CLONE_DETATCHED を CLONE_THREAD と一緒に指定する必要はなくなった。 このフラグはまだ定義されているが、何の効果もない。
i386 上では、 clone() は vsyscall 経由ではなく、直接 int $0x80 経由で呼び出すべきである。
#include <syscall.h> pid_t mypid; mypid = syscall(SYS_getpid);
#define _GNU_SOURCE #include <sys/wait.h> #include <sys/utsname.h> #include <sched.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ } while (0) static int /* clone された子プロセスの開始関数 */ childFunc(void *arg) { struct utsname uts; /* 子プロセスの UTS 名前空間でホスト名を変更する */ if (sethostname(arg, strlen(arg)) == -1) errExit("sethostname"); /* ホスト名を取得し表示する */ if (uname(&uts) == -1) errExit("uname"); printf("uts.nodename in child: %s\n", uts.nodename); /* sleep を使ってしばらく名前空間をオープンされたままにする。 これにより実験を行うことができる -- 例えば、 別のプロセスがこの名前空間に参加するなど。 */ sleep(200); return 0; /* 子プロセスを終了する */ } #define STACK_SIZE (1024 * 1024) /* clone される子プロセスのスタックサイズ */ int main(int argc, char *argv[]) { char *stack; /* スタックバッファーの先頭 */ char *stackTop; /* スタックバッファーの末尾 */ pid_t pid; struct utsname uts; if (argc < 2) { fprintf(stderr, "Usage: %s <child-hostname>\n", argv[0]); exit(EXIT_SUCCESS); } /* 子プロセス用のスタックを割り当てる */ stack = malloc(STACK_SIZE); if (stack == NULL) errExit("malloc"); stackTop = stack + STACK_SIZE; /* スタックは下方向に伸びるものとする */ /* 自分専用の UTS 名前空間を持つ子プロセスを作成する; 子プロセスは childFunc() の実行を開始する */ pid = clone(childFunc, stackTop, CLONE_NEWUTS | SIGCHLD, argv[1]); if (pid == -1) errExit("clone"); printf("clone() returned %ld\n", (long) pid); /* 親プロセスの実行はここに来る */ sleep(1); /* 子プロセスがホスト名を変更する時間を与える */ /* 親プロセスの UTS 名前空間でのホスト名を表示する; これは子プロセスの UTS 名前空間でのホスト名とは異なる */ if (uname(&uts) == -1) errExit("uname"); printf("uts.nodename in parent: %s\n", uts.nodename); if (waitpid(pid, NULL, 0) == -1) /* 子プロセスを待つ */ errExit("waitpid"); printf("child has terminated\n"); exit(EXIT_SUCCESS); }