child process가 terminate되면 컴퓨터 시스템에서 사용하는 resource들이 있다. 프로세스에 해당하는 Exit status나 여러가지 OS table이라는 것이 있다. 이것을 정상적으로 OS에서 제거해 주어야 한다. 하지만 여전히 리소스를 사용하고 있는 것을 보고 zombie라고 한다.
이런것을 제거하는 것을 보고 Reaping이라고 한다.
리눅스에서는 메모리를 두가지로 나눈다. kernel, user process가 사용하는 address space로 나뉜다. 프로세스 실행시 커널에는 PA, PB에 해당하는 자료구조가 따로 있다.(PB는 PA의 자식 프로세스이다.)
만약 PB가 fork가 끝나서 종료되면 커널에서 사용하는 PB의 메모리를 커널이 제거해주어야 한다. 근데 이걸 지워주지 않은 상태로 PB를 종료하면 PB에 대한 정보가 메모리에 살아있게 된다. 이것을 보고 zombie 상태라고 부른다.
이것을 해결하기 위해 PB가 제거시 PA에 알려주어서 커널에 PB 메모리 해제를 요청하게 된다. 이것을 보고 Reaping이라고 부른다.
만약 부모 process가 요청을 안하면 memory 누수가 발생한다.
알려주는 시스템 콜은 wait, waitpid이다.
만약에 child가 reaping 하지않고 parent가 제거되면 고아가 되었다고 한다.(orphan) child process는 나중에 PA의 조상인 init process가 orphan process를 reaping해준다.
void fork7() {
if (fork() == 0) {
/* Child */
printf("Terminating Child, PID = %d\n", getpid());
exit(0);
} else {
printf("Running Parent, PID = %d\n", getpid());
while (1)
; /* Infinite loop */
}
} forks.c
잘보면 6640은 defunct로 현재 zombie process인것을 확인할 수 있다.
그래서 부모 프로세스를 제거해주면 init process가 orphan process를 제거해준다.
int fork8()
{
if (fork() == 0) {
/* Child */
printf("Running Child, PID = %d\n",getpid());
while (1)
; /* Infinite loop */
} else {
printf("Terminating Parent, PID = %d\n",
getpid());
exit(0);
}
}
wait라는 시스템 콜을 이용해서 reaping할 수 있다.
parent가 wait를 이용해서 child를 reap할 수 있다.
wait 함수는 현재 부모가 child가 끝날때까지 자기는 suspend되어 있는 상태이다. 즉 내가(parent가) cpu사용중 cpu제어권에 대해 나는 그냥 suspend(wait) 상태이다. child가 terminate될때까지 부모는 계속 기다리고 있는 것이다.
그리고 child가 정상적으로 terminate되면 child process pid가 반환된다. 인자로 넘어오는 child_status는 null이 아니면 integer의 왜 child가 terminate되었는지에 대한 표시하기 위한 값이 들어가 있다.
아래의 코드가 정상적인 코드이다.
Hello Child가 정상적으로 exit하면 wait에 signal을 보낸다.
void fork9() {
int child_status;
if (fork() == 0) {
printf("HC: hello from child\n");
exit(0);
} else {
printf("HP: hello from parent\n");
wait(&child_status);
printf("CT: child has terminated\n");
}
printf("Bye\n");
}
void fork10() {
pid_t pid[N];
int i, child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0) {
exit(100+i); /* Child */
}
for (i = 0; i < N; i++) { /* Parent */
pid_t wpid = wait(&child_status);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminate abnormally\n", wpid);
}
}
10개의 child process를 띄운다음에 각각의 프로세스를 exit한다. 그다음에 부모 프로세스에서 자식 프로세스를 받아서 각각 reaping해준다.
얘는 프로세스를 지정할 수 있다.
void fork11() {
pid_t pid[N];
int i;
int child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0)
exit(100+i); /* Child */
for (i = N-1; i >= 0; i--) {
pid_t wpid = waitpid(pid[i], &child_status, 0);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminate abnormally\n", wpid);
}
}
process id는 pid 배열에 일단 저장해 놓는다.
waitpid(pid[i] ... ) 형태인데 child에 해당하는 pid를 보고 wait하고 기다린다.
어떤 시행하는 binary program을 loading해서 running하는 프로그램이다.
쉘에서 > ls -al 과 같은 것을 실행하면 >
프로세스가 ls -al
프로세스를 fork해서 실행한다.
execve를 실행하면 code, data, stack을 모두 overwrite하게 된다. 하지만 PID, file, signal은 그냥 유지한다.