[C]forkで子供を作る時の注意
2012/06/19
forkの動作
fork()を実行するプロセスのコピーを作る、とあります。man pageの版によっては、glibcの特定バージョンから、clone()による実装に変わった*1とある。
そこには、ファイルデスクリプタもコピーされます。とあるではありませんか!。
ただし、0,1,2は一端クローズしてから開き直します、とありました。
これちょっとハマった気がするので、実際に検証してみましょう。
platformが異なりますが、とりあえずPC-Linux(VMですが)で検証です。
glibc 2.3.3 以降では、 NPTL スレッド実装の一部として提供されている glibc のfork() ラッパー関数は、 カーネルの fork() システムコールを起動するのではなく、clone(2) を起動する。 clone(2) に渡すフラグとして、伝統的な fork() システムコールと同じ効果が得られるようなフラグが指定される (fork() の呼び出しは、 flags に SIGCHLD だけを指定して clone(2) を呼び出すのと等価である)。 glibc のラッパー関数は pthread_atfork(3) を使って設定されている任意の fork ハンドラを起動する。via http://linuxjm.sourceforge.jp/html/LDP_man-pages/man2/fork.2.html
確認コード例
少々長いですが、コードを貼り付けます。エイヤっと作っているので、エラー処理とバッファ量などもテキトウです。
検証GCC version
gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-51) Copyright (C) 2006 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
fork_exec.c
#include <stdio.h> #include <string.h> #include <error.h> #include <errno.h> #include <sys/types.h> #include <unistd.h> #include <sys/stat.h> #include <fcntl.h> #define DEBUG_PRINT(fmt, ...) \ printf("%s(): "fmt, __func__, ## __VA_ARGS__) const char* pExec_PG = "./infty.sh" ; int proc_exec(char* argv[]) { char* arg_nul[] = {NULL,} ; int rc; DEBUG_PRINT( "%s\n", pExec_PG ); if (argv == NULL) { rc = execvp( pExec_PG, arg_nul ); } else { rc = execvp( pExec_PG, argv ); } return rc ; } int proc_dup(int* pid_child, char* argv[]) { pid_t id; id = fork(); if (id==-1) { /* parent - error */ DEBUG_PRINT( "%s", strerror(errno) ); } else if( id==0 ) { /* child - ok */ DEBUG_PRINT( "created pid=%d, let's execv!", (int)getpid() ); proc_exec( argv ); // ここにはこないはず. DEBUG_PRINT( "Oops... Where is here ?" ); } else { /* parent - get child process */ DEBUG_PRINT( "created success.\n"); } if(pid_child) *pid_child = id ; return id; } int main ( int argc, char*argv[]) { int id1, id2 ; int fd; char buf[512]; DEBUG_PRINT( "I am %d.\n", (int)getpid() ); proc_dup(&id1,NULL); printf("create %d\n", id1); system("ps"); sleep(5); fd = open("test", O_CREAT); if(fd<0) { printf("file open error"); } proc_dup(&id2,NULL); system("ps"); sleep(5); printf("create %d\n", id2); sprintf(buf, "lsof -c %d", (int)getpid()); system(buf); sprintf(buf, "lsof -c %d", id1); system(buf); sprintf(buf, "lsof -c %d", id2); system(buf); close(fd); printf(" **** After close(fd) ****\n"); sprintf(buf, "lsof -c %d", (int)getpid()); system(buf); sprintf(buf, "lsof -c %d", id1); system(buf); sprintf(buf, "lsof -c %d", id2); system(buf); printf("run kill %d %d \n", id1, id2); sleep(60); return 0; }
infty.sh
#!/bin/sh while true; do lsof -p $$ sleep 10; done
実行結果
lsofコマンドを使っているので、特権ユーザで実行しましょう。一般ユーザが使えるようなら、それでも構いません(自プロセスだけだから大丈夫かな?)
# ./fork_exe main(): I am 7630. proc_dup(): created pid=7631, let's execv!proc_exec(): ./infty.sh proc_dup(): created success. create 7631 PID TTY TIME CMD 7371 pts/0 00:00:00 su 7374 pts/0 00:00:00 bash 7630 pts/0 00:00:00 fork_exe 7631 pts/0 00:00:00 infty.sh 7632 pts/0 00:00:00 ps 7633 pts/0 00:00:00 lsof COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME infty.sh 7631 root cwd DIR 8,3 4096 3110061 /home/yyy/tst_fork infty.sh 7631 root rtd DIR 8,3 4096 2 / infty.sh 7631 root txt REG 8,3 735804 13323587 /bin/bash infty.sh 7631 root mem REG 8,3 129900 7791196 /lib/ld-2.5.so infty.sh 7631 root mem REG 8,3 1693812 7791364 /lib/libc-2.5.so infty.sh 7631 root mem REG 8,3 20668 7791420 /lib/libdl-2.5.so infty.sh 7631 root mem REG 8,3 13084 7792317 /lib/libtermcap.so.2.0.8 infty.sh 7631 root mem REG 8,3 25462 8872255 /usr/lib/gconv/gconv-modules.cache infty.sh 7631 root mem REG 8,3 56454576 8749247 /usr/lib/locale/locale-archive infty.sh 7631 root 0u CHR 136,0 2 /dev/pts/0 infty.sh 7631 root 1u CHR 136,0 2 /dev/pts/0 infty.sh 7631 root 2u CHR 136,0 2 /dev/pts/0 infty.sh 7631 root 255r REG 8,3 55 3110065 /home/yyy/tst_fork/infty.sh proc_dup(): created pid=7636, let's execv!proc_exec(): ./infty.sh proc_dup(): created success. PID TTY TIME CMD 7371 pts/0 00:00:00 su 7374 pts/0 00:00:00 bash 7630 pts/0 00:00:00 fork_exe 7631 pts/0 00:00:00 infty.sh 7635 pts/0 00:00:00 sleep 7636 pts/0 00:00:00 infty.sh 7637 pts/0 00:00:00 ps 7638 pts/0 00:00:00 lsof COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME infty.sh 7636 root cwd DIR 8,3 4096 3110061 /home/yyy/tst_fork infty.sh 7636 root rtd DIR 8,3 4096 2 / infty.sh 7636 root txt REG 8,3 735804 13323587 /bin/bash infty.sh 7636 root mem REG 8,3 129900 7791196 /lib/ld-2.5.so infty.sh 7636 root mem REG 8,3 1693812 7791364 /lib/libc-2.5.so infty.sh 7636 root mem REG 8,3 20668 7791420 /lib/libdl-2.5.so infty.sh 7636 root mem REG 8,3 13084 7792317 /lib/libtermcap.so.2.0.8 infty.sh 7636 root mem REG 8,3 25462 8872255 /usr/lib/gconv/gconv-modules.cache infty.sh 7636 root mem REG 8,3 56454576 8749247 /usr/lib/locale/locale-archive infty.sh 7636 root 0u CHR 136,0 2 /dev/pts/0 infty.sh 7636 root 1u CHR 136,0 2 /dev/pts/0 infty.sh 7636 root 2u CHR 136,0 2 /dev/pts/0 infty.sh 7636 root 3r REG 8,3 0 3110063 /home/yyy/tst_fork/test infty.sh 7636 root 255r REG 8,3 55 3110065 /home/yyy/tst_fork/infty.sh create 7636 COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME infty.sh 7631 root cwd DIR 8,3 4096 3110061 /home/yyy/tst_fork infty.sh 7631 root rtd DIR 8,3 4096 2 / infty.sh 7631 root txt REG 8,3 735804 13323587 /bin/bash infty.sh 7631 root mem REG 8,3 129900 7791196 /lib/ld-2.5.so infty.sh 7631 root mem REG 8,3 1693812 7791364 /lib/libc-2.5.so infty.sh 7631 root mem REG 8,3 20668 7791420 /lib/libdl-2.5.so infty.sh 7631 root mem REG 8,3 13084 7792317 /lib/libtermcap.so.2.0.8 infty.sh 7631 root mem REG 8,3 25462 8872255 /usr/lib/gconv/gconv-modules.cache infty.sh 7631 root mem REG 8,3 56454576 8749247 /usr/lib/locale/locale-archive infty.sh 7631 root 0u CHR 136,0 2 /dev/pts/0 infty.sh 7631 root 1u CHR 136,0 2 /dev/pts/0 infty.sh 7631 root 2u CHR 136,0 2 /dev/pts/0 infty.sh 7631 root 255r REG 8,3 55 3110065 /home/yyy/tst_fork/infty.sh **** After close(fd) **** run kill 7631 7636 COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME infty.sh 7636 root cwd DIR 8,3 4096 3110061 /home/yyy/tst_fork infty.sh 7636 root rtd DIR 8,3 4096 2 / infty.sh 7636 root txt REG 8,3 735804 13323587 /bin/bash infty.sh 7636 root mem REG 8,3 129900 7791196 /lib/ld-2.5.so infty.sh 7636 root mem REG 8,3 1693812 7791364 /lib/libc-2.5.so infty.sh 7636 root mem REG 8,3 20668 7791420 /lib/libdl-2.5.so infty.sh 7636 root mem REG 8,3 13084 7792317 /lib/libtermcap.so.2.0.8 infty.sh 7636 root mem REG 8,3 25462 8872255 /usr/lib/gconv/gconv-modules.cache infty.sh 7636 root mem REG 8,3 56454576 8749247 /usr/lib/locale/locale-archive infty.sh 7636 root 0u CHR 136,0 2 /dev/pts/0 infty.sh 7636 root 1u CHR 136,0 2 /dev/pts/0 infty.sh 7636 root 2u CHR 136,0 2 /dev/pts/0 infty.sh 7636 root 3r REG 8,3 0 3110063 /home/yyy/tst_fork/test infty.sh 7636 root 255r REG 8,3 55 3110065 /home/yyy/tst_fork/infty.sh COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME infty.sh 7631 root cwd DIR 8,3 4096 3110061 /home/yyy/tst_fork infty.sh 7631 root rtd DIR 8,3 4096 2 / infty.sh 7631 root txt REG 8,3 735804 13323587 /bin/bash infty.sh 7631 root mem REG 8,3 129900 7791196 /lib/ld-2.5.so infty.sh 7631 root mem REG 8,3 1693812 7791364 /lib/libc-2.5.so infty.sh 7631 root mem REG 8,3 20668 7791420 /lib/libdl-2.5.so infty.sh 7631 root mem REG 8,3 13084 7792317 /lib/libtermcap.so.2.0.8 infty.sh 7631 root mem REG 8,3 25462 8872255 /usr/lib/gconv/gconv-modules.cache infty.sh 7631 root mem REG 8,3 56454576 8749247 /usr/lib/locale/locale-archive infty.sh 7631 root 0u CHR 136,0 2 /dev/pts/0 infty.sh 7631 root 1u CHR 136,0 2 /dev/pts/0 infty.sh 7631 root 2u CHR 136,0 2 /dev/pts/0 infty.sh 7631 root 255r REG 8,3 55 3110065 /home/yyy/tst_fork/infty.sh COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME infty.sh 7636 root cwd DIR 8,3 4096 3110061 /home/yyy/tst_fork infty.sh 7636 root rtd DIR 8,3 4096 2 / infty.sh 7636 root txt REG 8,3 735804 13323587 /bin/bash infty.sh 7636 root mem REG 8,3 129900 7791196 /lib/ld-2.5.so infty.sh 7636 root mem REG 8,3 1693812 7791364 /lib/libc-2.5.so infty.sh 7636 root mem REG 8,3 20668 7791420 /lib/libdl-2.5.so infty.sh 7636 root mem REG 8,3 13084 7792317 /lib/libtermcap.so.2.0.8 infty.sh 7636 root mem REG 8,3 25462 8872255 /usr/lib/gconv/gconv-modules.cache infty.sh 7636 root mem REG 8,3 56454576 8749247 /usr/lib/locale/locale-archive infty.sh 7636 root 0u CHR 136,0 2 /dev/pts/0 infty.sh 7636 root 1u CHR 136,0 2 /dev/pts/0 infty.sh 7636 root 2u CHR 136,0 2 /dev/pts/0 infty.sh 7636 root 3r REG 8,3 0 3110063 /home/yyy/tst_fork/test infty.sh 7636 root 255r REG 8,3 55 3110065 /home/yyy/tst_fork/infty.sh COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME infty.sh 7631 root cwd DIR 8,3 4096 3110061 /home/yyy/tst_fork infty.sh 7631 root rtd DIR 8,3 4096 2 / infty.sh 7631 root txt REG 8,3 735804 13323587 /bin/bash infty.sh 7631 root mem REG 8,3 129900 7791196 /lib/ld-2.5.so infty.sh 7631 root mem REG 8,3 1693812 7791364 /lib/libc-2.5.so infty.sh 7631 root mem REG 8,3 20668 7791420 /lib/libdl-2.5.so infty.sh 7631 root mem REG 8,3 13084 7792317 /lib/libtermcap.so.2.0.8 infty.sh 7631 root mem REG 8,3 25462 8872255 /usr/lib/gconv/gconv-modules.cache infty.sh 7631 root mem REG 8,3 56454576 8749247 /usr/lib/locale/locale-archive infty.sh 7631 root 0u CHR 136,0 2 /dev/pts/0 infty.sh 7631 root 1u CHR 136,0 2 /dev/pts/0 infty.sh 7631 root 2u CHR 136,0 2 /dev/pts/0 infty.sh 7631 root 255r REG 8,3 55 3110065 /home/yyy/tst_fork/infty.sh COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME infty.sh 7636 root cwd DIR 8,3 4096 3110061 /home/yyy/tst_fork infty.sh 7636 root rtd DIR 8,3 4096 2 / infty.sh 7636 root txt REG 8,3 735804 13323587 /bin/bash infty.sh 7636 root mem REG 8,3 129900 7791196 /lib/ld-2.5.so infty.sh 7636 root mem REG 8,3 1693812 7791364 /lib/libc-2.5.so infty.sh 7636 root mem REG 8,3 20668 7791420 /lib/libdl-2.5.so infty.sh 7636 root mem REG 8,3 13084 7792317 /lib/libtermcap.so.2.0.8 infty.sh 7636 root mem REG 8,3 25462 8872255 /usr/lib/gconv/gconv-modules.cache infty.sh 7636 root mem REG 8,3 56454576 8749247 /usr/lib/locale/locale-archive infty.sh 7636 root 0u CHR 136,0 2 /dev/pts/0 infty.sh 7636 root 1u CHR 136,0 2 /dev/pts/0 infty.sh 7636 root 2u CHR 136,0 2 /dev/pts/0 infty.sh 7636 root 3r REG 8,3 0 3110063 /home/yyy/tst_fork/test infty.sh 7636 root 255r REG 8,3 55 3110065 /home/yyy/tst_fork/infty.sh'test'ファイルを、子プロセスが握っていることが見て取れます。
比較用に、open - close前後の状態を見せています。
対策
man 2 openによるとデフォルトでは、新しいファイルディスクリプタは execve(2) を実行した後もオープンされたままとなる (つまり、 fcntl(2) に説明がある FD_CLOEXEC ファイルディスクリプタフラグは最初は無効である; 後述の O_CLOEXEC フラグを使うとこのデフォルトを変更することができる)。とあり、
O_CLOEXEC (Linux 2.6.23 以降)と、コレを使えというふうに読めますね。
新しいファイルディスクリプタに対して close-on-exec フラグを有効にする。 このフラグを指定することで、プログラムは FD_CLOEXEC フラグをセットするための fcntl(2) F_SETFD 操作を別途呼び出す必要がなくなる。また、ある種のマルチスレッドのプログラムはこのフラグの使用は 不可欠である。なぜなら、個別に FD_CLOEXEC フラグを設定する fcntl(2) F_SETFD 操作を呼び出したとしても、あるスレッドがファイルディスクリプタを オープンするのと同時に別のスレッドが fork(2) と execve(2) を実行するという競合条件を避けるのには十分ではないからである。
これって、システム全体で、デフォルトで有効にしたい、とか、できないものなのか・・・。
関連事項
mutexなんかも気をつけないといけない話。memologue