2015-05-21
FreeBSDの posix_spawnの実装
freebsd |
http://blog.kazuhooku.com/2015/05/how-to-properly-spawn-external-command.html
を見て, 再現コードを FreeBSDで実行してみたとき, 期待される挙動になったので FreeBSDでの実装を確認してみました.
コード
FreeBSD r282608のソースコードを対象にしています.
posix_spawnは lib/libc/gen/posix_spawn.cで定義される.
int posix_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *fa, const posix_spawnattr_t *sa, char * const argv[], char * const envp[]) { return do_posix_spawn(pid, path, fa, sa, argv, envp, 0); }
do_posix_spawn関数を呼び出すだけ.
static int do_posix_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *fa, const posix_spawnattr_t *sa, char * const argv[], char * const envp[], int use_env_path) { pid_t p; volatile int error = 0; p = vfork(); switch (p) { case -1: return (errno); case 0: if (sa != NULL) { error = process_spawnattr(*sa); if (error) _exit(127); } if (fa != NULL) { error = process_file_actions(*fa); if (error) _exit(127); } if (use_env_path) _execvpe(path, argv, envp != NULL ? envp : environ); else _execve(path, argv, envp != NULL ? envp : environ); error = errno; _exit(127); default: if (error != 0) _waitpid(p, NULL, WNOHANG); else if (pid != NULL) *pid = p; return (error); } }
初めに vforkを呼び出しています. vforkは forkと違いメモリ空間がコピーされない,
かつ親プロセスは子プロセスが execするか exitするまで待つという性質があります.
このため子プロセスが先に動作し, _execveが呼ばれます(posix_spawnpの場合は _execvpe).
このとき実在しないコマンドが指定されている等で execが失敗したとき, error変数に
errnoを書き込み _exitします.
execが成功 or execが失敗+exitの後, 親プロセスが動き出します. switchの defaultの
部分が実行され, はじめに errorがチェックされます. 前述の通り vforkではメモリ空間が
コピーされないので, 親プロセスから子プロセスでの変更が確認できます. (vforkでなく,
forkを使う場合, h2oの実装のように pipe等を使う必要があります)
で, errorが発生していれば waitpidをして, errorを返します. このような実装になって
いるので, 子プロセスで execが失敗した場合エラーを返すことができるということになります.
(字面だけ見ると, default節の ifが真になることはないので, コンパイラに削除される可能性が
ある. そうならないように volatileがつけられている. コンパイラは fork/vforkの挙動など
知らない)
glibcでの実装
sysdeps/posix/spawni.cの __spawni関数です. 親プロセスが戻るところだけ示します.
/* Generate the new process. */ if ((flags & POSIX_SPAWN_USEVFORK) != 0 /* If no major work is done, allow using vfork. Note that we might perform the path searching. But this would be done by a call to execvp(), too, and such a call must be OK according to POSIX. */ || ((flags & (POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSCHEDPARAM | POSIX_SPAWN_SETSCHEDULER | POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_RESETIDS)) == 0 && file_actions == NULL)) new_pid = __vfork (); else new_pid = __fork (); if (new_pid != 0) { if (new_pid < 0) return errno; /* The call was successful. Store the PID if necessary. */ if (pid != NULL) *pid = new_pid; return 0; }
vfork/forkが失敗した場合は errnoを返しますが, それ以外は 0を返しています.
なので子プロセスが execに失敗した場合でも 0(成功)が返っていたわけです.
vfork
vforkを使っている理由はわかりませんが, マルチスレッド下で呼ばれる可能性がある場合を
考慮しているためと思われます(すぐに execすることが確定しているのでパフォーマンスを
考えてのことかもしれませんが...)
マルチスレッド + forkの問題についてはこちらを参照してください
- 19 http://t.co/Y0yYJugtZI
- 10 https://www.google.co.jp/
- 5 http://b.hatena.ne.jp/
- 2 http://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=3&ved=0CCwQFjAC&url=http://d.hatena.ne.jp/syohex/20110908/1315494911&ei=yvxdVcqMO8nd8AXI0YDAAw&usg=AFQjCNGAbQA6QqWEJnI1aLb4Sq91YFczNQ
- 1 http://aoe-tk.hatenablog.com/entry/20130210/1360506829
- 1 http://api.twitter.com/1/statuses/show/601412456771031044.json
- 1 http://api.twitter.com/1/statuses/show/601518233397501953.json
- 1 http://b.hatena.ne.jp/entrylist?sort=hot
- 1 http://b.hatena.ne.jp/entrylist?url=http://blog.kentarok.org/
- 1 http://b.hatena.ne.jp/maeda1/?with_favorites=1&of=20
- 2015-05-20 全力わはー 3/52 5%