일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- vector
- 오에스
- bfs
- Stack
- Operating System
- 스택
- 브루트포스
- 그래프
- 컴공과
- 북리뷰
- 개발
- 백준
- 문제풀이
- 자료구조
- 코딩
- OS
- 컴퓨터공학과
- 오퍼레이팅시스템
- c++
- DP
- 코테
- cs
- coding
- 정석
- 알고리즘
- 정석학술정보관
- 구현
- 컴공
- Computer science
- 너비우선탐색
- Today
- Total
Little Jay
[CS] Exceptional Control Flow : Process Synchronizing(wait, waitpid) - Computer System 3rd Edition by Bryant 본문
[CS] Exceptional Control Flow : Process Synchronizing(wait, waitpid) - Computer System 3rd Edition by Bryant
Jay, Lee 2022. 7. 1. 16:26Wait
앞서서 Parent Process는 Child Process를 "Reap" 한다고 언급했다. 이는 Parent Process에서 wait 혹은 waitpid 함수 호출을 통해서 진행이된다.
int wait (int *status);
위에 있는 것이 wait 함수의 원형인데, 말 그대로 자식 Process의 종료 상태를 얻어내는 것을 목표로한다. 다시말해 Child Process가 끝날때까지 current process를 progress시키지 않고 suspend한다. 이 함수의 return 값은 끝나기를 기대하는 Child Process의 PID 번호이다. 그것이 가리키는 정수는 자식 프로세스가 종료된 이유와 종료 상태를 나타내는 값으로 설정이 된다. 이를 알아보기 위해서는 wait.h 파일에 들어가보면 지정된 매크도들을 사용하면 된다.
WIFEXITED, WEXITSTATUS, WIFSIGNALLED, WTERMSIG, WIFSTOPPED, WSTOPSIG, WIFCONTINUED 등의 매크도들이 있는데, 대표적으로 WIFEXITED는 자식 프로세스가 종료했는지 여부를 0, 1로 알려주고, WEXITSTATUS는 자식 프로세스에서 exit 함수를 사용할 때 값으로 들어온 것을 다시 return 한다. WIFSIGNALLED는 시그널을 받았는지 여부를 0, 1로 알려준다. 위 매크로에 대한 내용은 책을 참조하거나 아래의 링크에서 확인해볼 수 있다.
https://pubs.opengroup.org/onlinepubs/009696699/basedefs/sys/wait.h.html
주의해야 할 점은 만약 Child Process가 여러개 있을 때 wait 함수는 어떤 먼저 terminate된 Child Process를 받고 return 되어버린다. 따라서 여러 Child Process가 Zombie 혹은 Orphan Process로 남아있을 수 있는 가능성이 있다.
이제 예시를 한번 보자. 아래의 코드를 gcc temp.c 한 후에 ./a.out & 을 해주면 background에서도 실행이 된다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main(int argc, char *argv[]) {
pid_t pid = fork();
printf("process starts, this process' pid is [%d]\n", getpid());
if (pid == 0) {
printf("child process terminated [%d]\n", getpid());
}
else {
pid_t child;
int status;
child = wait(&status);
if (WIFEXITED(status)) {
printf("Child %d terminated with exit status %d\n", child, WEXITSTATUS(status));
}
else {
printf("Child %d terminated abnormally\n", child);
}
sleep(10);
printf("parent process terminated [%d]\n", getpid());
}
return 0;
}
우분투에서 돌리면 아래와같은 결과가 나온다. sleep 하는 동안 프로세스 정보를 확인할 수 있는 ps -a 명령어를 넣었는데 이를통해 pid가 1989로 잘 돌아가는 것을 확인할 수 있었다. 빨간색으로 밑줄을 쳐봐서 확인하기 쉬울 것이다.
Wait Process Graph
이전에 fork에 대해 배우면서 Process Graph를 배웠다. 이번에는 wait을 활용한 Process Graph에 대해 간단히 짚고 넘어가자. 아래의 예시를 보면 알 수 있지만 이제 Parent Process는 Child가 Exit할때까지 기다려야한다. 그래서 다시 Parent쪽으로 돌아오는 간선이 하나 추가로 생겼다. 유향 간선이기 때문에 DAG임은 유지된다.
void main() {
int child_status;
if (fork() == 0) {
printf("Child!\n");
exit(0);
}
else {
printf("Parent!\n");
wait(&child_status);
printf("Child Bye!\n");
}
printf("Finished!");
}
이를 통해서 예측해볼 수 있는 것은 "Child!"가 나오고 나서 "Child Bye!" 라는 것이 출력될 수 있고 뒤이어 "Finished"가 출력될 수 있다. 이 순서는 항상 Feasible하기 때문에 이를 염두하여 제대로된 결과를 도출할 수 있도록하자.
Waitpid
wait같은 경우는 아무 Child Process만 기다리는 것이다. 반면 waitpid같은 경우에는 특정 Process를 wait한다. 여튼 wait이나 waitpid나 모두 Zombie Process를 없애기 위한 방법이라는 것이라고 알아두자. 각설하고 waitpid를 살펴보면,
pid_t waitpid(pid_t pid, int &status, int options)
일반적인 wait과는 달라진 점이 많이 보인다. 당연히 특정 process를 기다려야하기 떄문에 그 Process의 PID가 필요하다.
이때 pid는 그 값에 따라서 동작을 다르게 하는데 이는 아래와 같다.
- pid == -1 일때는 아무런 Child Process를 기다린다. 이 경우에는 wait 함수와 동일하게 동작하게 된다.
- pid < -1 인 경우에는 Process Group ID가 pid의 절대값과 동일한 값을 기다리는 것이다. (사실 이렇게 쓰는걸 못봄)
- pid == 0 일때는 부모 프로세스의 Group ID와 같은 Child Process은 경우를 기다리는 것이다.
- pid > 0 인 경우에는 pid값이 일치하는 Child Process를 기다린다.
상태도 당연히 받아와야한다. wait과 동일하므로 생략하겠다.
또한 waitpid를 통해 옵션을 설정할 수 있다. option을 통해 다양한 동작을 실행시킬 수 있는데 보통 많이 사용되는 것은 0과 WNOHANG이다.
- 0 인 경우에는 자식 프로세스가 종료될 때까지 block 된다. 즉, wait과 동일한 방식으로 동작한다
- WNOHANG인 경우는 반면 Parent Process를 suspend 시키는것이 아니라 진행시킨다. 즉 Non-Blocking 모드로 동작하며 Parent Process는 그대로 진행된다.
간단하게 예시를 보고 넘어가자. WNOHANG 옵션을 주었기 때문에 executing actions needed라는 것이 계속 찍히고 있는 것이 보일 것이다. while 문을 통해 계속 wait하면서 돌고있는 것이다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#define N 10
void nonBlock() {
pid_t pid[N];
pid_t wpid;
int status, i;
for (i = 0; i < N; i++) {
if ((pid[i] = fork()) == 0) {
sleep(3 * i);
printf("bye pid: %d\n", getpid());
exit(50 + i);
}
while((wpid = waitpid(-1, &status, WNOHANG)) >= 0) {
if (wpid > 0) {
if (WIFEXITED(status)) {
printf("Child %d have terminated with exit status %d\n", wpid, WEXITSTATUS(status));
}
}
else if (wpid == 0) {
printf("executing actions needed\n");
sleep(1);
}
}
}
exit(0);
}
int main() {
nonBlock();
return 0;
}
Reference
Randal E. Bryant, & David R. O’Hallaron. (2016). Computer Systems A Programmer’s Perspective Third edition. Carnegie Mellon University: Pearson.