子プロセスの終了確認 - ToguchiYuto/linuxsystemprogram GitHub Wiki

親プロセスはforkで子プロセスを生成したあと、子プロセスの終了を待たずに終了する場合や子プロセスが親プロセスよりも先に終了する場合ある。子プロセスの終了を親プロセスから確認する方法について説明する。

  • 親プロセスが子プロセスよりも先に終了した場合、子プロセスPIDが1のプロセスの子プロセスになる。
  • 子プロセスが親プロセスよりも先に終了した場合、親プロセスから終了が確認されるまでゾンビプロセスになる。

親プロセスが子プロセスよりも先に終了した場合

子プロセスが親プロセスよりも遅く終了するように以下のタイミングでsleepを実行する。 このタイミングでsleepを実行すると子プロセスは100秒間、sleepするので親プロセスが子プロセスよりも早く終了するようになる。

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

void main(){
    int ret;
    pid_t pid;
    pid = fork();

    if (pid == -1){
        perror("error fork");
    }

    if(pid){
        printf("PID by fork %d in Parent Process\n",pid);
    }

    if(!pid){
        printf("PID by fork %d in Child Process\n",pid);
        sleep(1000);
    }
}

以下の結果から子プロセスのPIDは8629であることがわかる。

[debug@localhost linux_system_program]$ ./a.out
PID by fork 8629 in Parent Process
[debug@localhost linux_system_program]$ PID by fork 0 in Child Process

[debug@localhost linux_system_program]$ 

PID 8629のみ表示され、親プロセスのa.outはすでに終了しているため、表示されない。 また、親プロセスのPIDを表すPPIDが1と表示されることが確認できる。

[debug@localhost ~]$ ps -ef | egrep 'a.out|UID'
UID        PID  PPID  C STIME TTY          TIME CMD
debug     8629     1  0 22:41 pts/0    00:00:00 ./a.out
debug     8891  8218  0 22:42 pts/1    00:00:00 grep -E --color=auto a.out|UID
[debug@localhost ~]$

pstree -pの結果からPID 1の子プロセスとして登録されていることが確認できる。

[debug@localhost ~]$ pstree -p
systemd(1)─┬─NetworkManager(860)─┬─dhclient(1050)
           │                     ├─{NetworkManager}(876)
           │                     └─{NetworkManager}(878)
           ├─a.out(8629)
...

子プロセスが親プロセスよりも先に終了した場合

子プロセスが親プロセスよりも先に終了した場合、親プロセスから終了が確認されるまでゾンビプロセスになる。 親プロセスでのも実行される箇所にsleepを実行すると、親プロセスはしばらく待機するため、子プロセスが親プロセスよりも先に終了する。

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

void main(){
    int ret;
    pid_t pid;
    pid = fork();

    if (pid == -1){
        perror("error fork");
    }

    if(pid){
        printf("PID by fork %d in Parent Process\n",pid);
        sleep(100);
    }

    if(!pid){
        printf("PID by fork %d in Child Process\n",pid);
    }
}
[debug@localhost linux_system_program]$ ./a.out
PID by fork 15634 in Parent Process
PID by fork 0 in Child Process
[debug@localhost linux_system_program]$

親プロセスよりも子プロセスが先に終了するため、子プロセスである15634がゾンビになる。 ただし、親プロセスが終了すると子プロセスも終了する。 なぜ、子プロセスが親プロセスよりも先に終了したとき、プロセスがゾンビとなり残されるのかというと子プロセスが終了してプロセスが消えてしまうと親プロセスから子プロセスの状態を確認できなくなるため、ゾンビとしてプロセスが残される。親プロセスはこのゾンビプロセスの状態を確認し、確認するとゾンビプロセスが終了する。 親プロセスが子プロセスの状態を確認するためには、wait()を利用し、返り値として子プロセスのPIDを取得することができる。 wait()は終了したプロセスが存在しない場合、処理をブロックし、そのまま待機して子プロセスが終了するのを待ちます。

[debug@localhost ~]$ ps aux | egrep 'a.out|UID'
debug    15633  0.0  0.0   4216   348 pts/0    S+   23:00   0:00 ./a.out
debug    15634  0.0  0.0      0     0 pts/0    Z+   23:00   0:00 [a.out] <defunct>
debug    15669  0.0  0.0 112728   968 pts/1    R+   23:00   0:00 grep -E --color=auto a.out|UID
[debug@localhost ~]$ 
[debug@localhost ~]$ pstree -p
systemd(1)─┬─NetworkManager(860)─┬─dhclient(1050)
           │                     ├─{NetworkManager}(876)
           │                     └─{NetworkManager}(878)
...
           ├─sshd(1292)─┬─sshd(1599)───sshd(1605)─┬─bash(1607)───a.out(15633)───a.out(15634)
           │            │                         └─bash(15647)───sleep(15693)

wait()を利用して親プロセスにて子プロセスの終了確認を実施

親プロセスは子プロセスの終了を待つためにwait()を実施し、子プロセスは処理が終了すると100を返し、親プロセスで実行されているwait()の戻り値としてstatusが返される。 WEXITSTATUSを実行すると子プロセスの終了コードを確認することができる。

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>

int main(){
    int ret;
    int status;
    pid_t pid;
    pid = fork();

    if (pid == -1){
        perror("error fork");
    }

    if(pid){
        printf("PID by fork %d in Parent Process\n",pid);
    }

    if(!pid){
        printf("PID by fork %d in Child Process\n",pid);
        return(100);
    }
    pid = wait(&status);
    printf("Wait PID %d and Status %d\n",pid,WEXITSTATUS(status));
    return(0);
}

Statusに100と表示されていることから子プロセスの終了コードが表示されることがわかる。

[debug@localhost linux_system_program]$ ./a.out
PID by fork 27697 in Parent Process
PID by fork 0 in Child Process
Wait PID 27697 and Status 100
[debug@localhost linux_system_program]$ 

ltraceに-fオプションをつけると子プロセスについもトレースすることができる。 wait()が実行され、子プロセスの処理が完了するまで、処理が停止させられていることがわかる。

[debug@localhost linux_system_program]$ ltrace -f ./a.out
[pid 31528] __libc_start_main(0x40060d, 1, 0x7ffe1a876348, 0x4006b0 <unfinished ...>
[pid 31528] fork()                                                                                                                            = 31529
[pid 31528] printf("PID by fork %d in Parent Process"..., 31529PID by fork 31529 in Parent Process
)                                                                              = 36
[pid 31528] wait(0x7ffe1a87624c <unfinished ...>
[pid 31529] <... fork resumed> )                                                                                                              = 0
[pid 31529] printf("PID by fork %d in Child Process\n"..., 0PID by fork 0 in Child Process
)                                                                                 = 31
[pid 31529] +++ exited (status 100) +++
[pid 31528] --- SIGCHLD (Child exited) ---
[pid 31528] <... wait resumed> )                                                                                                              = 31529
[pid 31528] printf("Wait PID %d and Status %d\n", 31529, 100Wait PID 31529 and Status 100
)                                                                                 = 30
[pid 31528] +++ exited (status 0) +++
[debug@localhost linux_system_program]$

straceに-fオプションをつけると子プロセスについもトレースすることができる。 forkが実行されると実際はcloneというシステムコールがよびだされていることがわかる。 また、wait4というシステムコールが呼ばれていることも確認できる。

[debug@localhost linux_system_program]$ strace -f ./a.out
execve("./a.out", ["./a.out"], 0x7ffd5acca1d8 /* 26 vars */) = 0
brk(NULL)                               = 0x173a000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe1ef738000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (そのようなファイルやディレクトリはありません)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=23339, ...}) = 0
mmap(NULL, 23339, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fe1ef732000
close(3)                                = 0
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20&\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2156160, ...}) = 0
mmap(NULL, 3985888, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fe1ef14a000
mprotect(0x7fe1ef30d000, 2097152, PROT_NONE) = 0
mmap(0x7fe1ef50d000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c3000) = 0x7fe1ef50d000
mmap(0x7fe1ef513000, 16864, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fe1ef513000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe1ef731000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe1ef72f000
arch_prctl(ARCH_SET_FS, 0x7fe1ef72f740) = 0
mprotect(0x7fe1ef50d000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ)     = 0
mprotect(0x7fe1ef739000, 4096, PROT_READ) = 0
munmap(0x7fe1ef732000, 23339)           = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fe1ef72fa10) = 31100
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe1ef737000
write(1, "PID by fork 31100 in Parent Proc"..., 36PID by fork 31100 in Parent Process
) = 36
wait4(-1, strace: Process 31100 attached
 <unfinished ...>
[pid 31100] fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
[pid 31100] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe1ef737000
[pid 31100] write(1, "PID by fork 0 in Child Process\n", 31PID by fork 0 in Child Process
) = 31
[pid 31100] exit_group(100)             = ?
[pid 31100] +++ exited with 100 +++
<... wait4 resumed>[{WIFEXITED(s) && WEXITSTATUS(s) == 100}], 0, NULL) = 31100
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=31100, si_uid=1000, si_status=100, si_utime=0, si_stime=0} ---
write(1, "Wait PID 31100 and Status 100\n", 30Wait PID 31100 and Status 100
) = 30
exit_group(0)                           = ?
+++ exited with 0 +++
[debug@localhost linux_system_program]$
⚠️ **GitHub.com Fallback** ⚠️