0. 시작하기전에
- 이번 포스팅에서는 이전까지 배운 프로세스 관련 시스템 콜에 대해서 총정리를 할 예정이다.
- 추가적으로 wait() 시스템 콜에 대해서 깊이있게 다뤄볼 것이다.
1. 프로세스 생성부터 종료까지
- 아래의 그림을 통해서 전체적인 프로세스 생성 및 종료 과정을 복습해보자.
a) 새로운 프로세스를 생성하는 방법
- fork() 시스템 콜을 이용하여 기존의 프로세스를 복사하는 방식으로 새로운 프로세스를 생성한다.
- 복사의 대상이 되는 기존 프로세스를 부모 프로세스라고 부른다.
- 복사를 통해 새로 생성된 프로세스를 자식 프로세스라고 부른다.
- 이를 통해, 프로세스 간의 계층이 형성된다.
- fork() 시스템 콜은 PID 값을 반환하는데, 이를 통해서 부모와 자식 프로세스를 구분할 수 있다.
- 자식 프로세스의 경우 ,exec() 시스템 콜을 사용하여 데이터를 덮어씌워 새로운 작업을 수행하게 할 수 있다.
- PID를 조건문으로 사용하여, 부모 프로세스와 자식 프로세스 각각 수행하는 코드를 다르게 작성할 수 있다.
b) 프로세스에서 wait() 시스템 콜을 사용하는 방식
- 부모 프로세스를 통해서 자식 프로세스를 생성하면, 자식 프로세스를 우선적으로 실행한다.
- 왜냐면 부모 프로세스의 확인하에 자식 프로세스를 완전히 종료할 수 있기 때문이다.
- 여기서 완전히 종료한다는 의미는, 부모 프로세스에 의해 자식 프로세스의 작업이 메모리에서 내려간다는 것이다.
- 자식 프로세스를 우선적으로 실행하기 위해서, 자식 프로세스가 exec() 시스템 콜을 호출하면
- 부모 프로세스는 wait()라는 시스템 콜을 호출하여, 자식 프로세스가 exit() 시스템 콜을 호출할 때까지 대기한다.
- 자식 프로세스가 exit() 시스템 콜을 호출하면, 부모 프로세스에게 자식 프로세스의 작업종료를 알리는 시그널이 전달된다.
- 이 시그널을 SIGCHLD(Signal Child)이며, 부모 프로세스는 대기상태에서 재시작 상태가 된다.
- 만약 자식 프로세스보다 부모 프로세스가 먼저 종료된다면, 자식 프로세스의 작업이 완료되더라도
- 메모리상에 계속해서 남아있는 좀비 프로세스 또는 고아 프로세스 상태가 되어 메모리를 낭비하게 된다.
2. wait() 시스템 콜에 대하여
- wait() 시스템 콜에 대해서 상세하게 알아보자.
a) wait() 시스템 콜의 원형
#include <sys/wait.h>
pid_t wait(int *status) // 종료된 자식 프로세스의 PID가 반환된다.
- wait() 시스템콜의 특이점은 포인터 변수를 인자(parameter)로 사용된다는 것이다.
- 왜냐면 자식 프로세스의 exit() 함수가 호출됨에 따라 부모 프로세스에서 wait()함수가 해제되기 때문이다.
- 자식 프로세스가 exit() 시스템 콜을 함수할 때, 인자로 int status가 들어간다.
- 이 status 변수는 자식 프로세스의 종료 상태값을 의미하는데, 이를 운영체제가 wait() 시스템 콜의 인자로 넣어준다.
- 운영체제가 status 변수가 들어가 있는 메모리의 주소를 참조하기 때문에 wait()에서는 status의 포인터 변수를 사용하는 것이다.
- 자식 프로세스가 정상적으로 종료되어 wait() 프로세스가 실행되면 종료된 자식 프로세스의 PID를 반환한다.
- 운영체제가 자식 프로세스가 종료됨을 확인하기 위해서 사용하는 여러가지 매크로(미리 등록된) 함수가 있다.
- 아래의 함수는 여러가지 매크로 함수 중 하나인데, 이런 매크로 함수를 사용하여 자식 프로세스의 상태를 확인한다.
int WIFEXITED(status); // 자식 프로세스가 정상종료되면 0이 아닌 값을 반환한다.
b) 실습예제
#include <sys/types.h>
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
int child_pid;
int status;
int ret;
pid = fork();
switch(pid) {
case -1: // fork() 함수 실패
perror("fork is failed\n");
break;
case 0: // 자식프로세스
execl("/bin/ls", "ls", "-al", NULL);
perror("execl is faile\n");
break;
default: // 부모프로세스
child_pid = wait(&status); // 포인터 변수를 인자로 사용, 종료된 자식 프로세스의 PID를 반환
printf("Parent PID (%d), Child PID (%d)\n", getpid(), child_pid);
ret = WIFEXITED(status); // 자식 프로세스가 정상종료되면 0이 아닌 수를 반환
if (ret != 0) {
printf("Child process is normally terminated\n");
} else {
printf("Child process is abnormally terminated\n");
}
exit(0); // 일반적으로는 return 0를 입력한다.
}
}
▶ 실행결과
- 결과를 통해 알 수 있듯이, 자식 프로세스가 우선적으로 실행되어 execl() 함수에 의해 덮어씌어진 새로운 작업이 우선 수행된다.
- 이후 부모 프로세스의 작업이 실행됨을 알 수 있다.
- 자식 프로세스가 온전히 종료되어, child_pid에 자식 프로세스의 PID가 잘 반환된 것을 확인할 수 있다.
'컴퓨터공학기초 개념 > 시스템 프로그래밍' 카테고리의 다른 글
26. 프로세스 - IPC 기법(pipe, message queue) (0) | 2021.09.17 |
---|---|
25. 프로세스 - 우선순위 스케줄링 시스템 콜 (0) | 2021.09.17 |
23. 프로세스 - 프로세스 종료(exit) (0) | 2021.09.17 |
22. 프로세스 - copy on write (0) | 2021.09.16 |
21. 프로세스 - wait & 쉘 만들기 (0) | 2021.09.16 |
댓글