fork() 시스템 호출을 프로세스 구조와 연관지어 자세히 살펴보자. 다음은 C 언어를 처음 배울 때 접하게 되는 코드이다.
int main() {
printf("Hello \n");
exit(0);
}
main()
함수의 맨 마지막 줄에 exit()
혹은 return()
문을 사용하는 것은 자식 프로세스가 끝났음을 부모 프로세스에 알려주기 위함이다. exit()
함수는 작업의 종료를 알려주는 시스템 호출이다.
모든 프로세스는 부모-자식 관계를 가진다. UNIX에서 프로세스의 맨 위를 init 프로세스로 하여 부모-자식 관계를 만드는 이유는 사용하던 자원을 회수하기가 용이하기 때문이다.
exit()
함수를 선언함으로써 부모 프로세스는 자식 프로세스가 사용하던 자원을 빨리 거둬갈 수 있다.
또한 exit()
함수는 전달하는 인자를 확인하여 자식 프로세스가 어떤 상태로 종료되었는지를 알려주는데, 인자가 0이면 정상 종료이고 -1이면 비정상 종료이다.
다음은 exit()
함수를 설명하기 위한 예제 코드이다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int pid;
pid = fork();
if (pid < 0){
printf("Error ");
exit(-1);
}
else if (pid == 0){
printf("Child");
exit(0);
}
else {
printf("Parent");
exit(0);
}
}
./procTest
ParentChild
위 코드의 결과로 ‘Parent’와 ‘Child’가 출력되는 것은 분명하지만 어떤 것이 먼저 출력될지는 알 수 없다. 두 프로세스가 독립적으로 움직이기 때문에 둘 중 먼저 실행 상태에 들어간 프로세스의 결과가 먼저 출력되는데, 대부분 'Parent'가 먼저 출력되지만 어쩌다 한 번씩 'Child'가 먼저 출력되기도 한다.
사실 위 코드는 좋은 코드가 아니다. 자식 프로세스의 exit()
함수를 부모 프로세스가 받지 않고 'Parent'만 출력한 채 먼저 종료되기 때문이다. 자식의 자원을 정리해야 할 부모 프로세스가 먼저 종료되면 자식 프로세스의 exit()
함수는 돌아갈 곳이 없는데 이런 경우 고아 프로세스가 발생하게 된다.
물론 반환되지 못한 자원은 나중에 운영체제의 자원 회수로 처리 되겠지만, 고아 프로세스가 많이 발생하면 시스템의 자원이 낭비된다.
운영체제는 부모 프로세스가 먼저 종료됨으로써 고아 프로세스가 생기는 것을 방지하기 위해 wait()
시스템 호출을 사용한다. wait()
시스템 호출은 자식 프로세스가 끝나기를 기다렸다가 자식 프로세스가 종료되면 다음 문장을 실행한다.
다음은 wait()
시스템 호출이 포함된 fork()
코드이다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
int pid;
pid = fork();
if (pid < 0){
printf("Error ");
exit(-1);
}
else if (pid == 0){
printf("C=%d\n", getpid());
exit(0);
}
else {
wait(NULL);
printf("P=%d\n", getpid());
exit(0);
}
}
./waitTest
C=6270
P=6269
위 코드에서는 현재 프로세스의 프로세스 구분자를 확인하기 위해 getpid()
를 사용했다. 만 약 부모 프로세스의 프로세스 구분자를 알고 싶다면 getppid()
를 사용하면 된다.
위 코드에서는 wait()
함수가 추가되었으므로 자식 프로세스의 프로세스 구분자(PID)가 먼저 출력되고 부모 프로세스는 wait()
함수에서 자식 프로세스가 끝나기를 기다린다.
자식 프로세스의 exit(0)
문이 실행되어 프로세스가 끝나면 부모 프로세스의 wait()
함수 다음 코드가 실행되어 부모 프로세스의 프로세스 구분자가 출력된다.
그러므로 위 코드는 부모 프로세스가 자식 프로세스보다 먼저 끝나는 경우가 없다. 이러한 특징때문에 wait()
함수는 부모 프로세스와 자식 프로세스 간 동기화에도 사용된다.
wait()
함수를 어떻게 응용할 수 있는지 예를 통해 살펴보자. 윈도우 운영체제에서 창을 여러 개 띄웠다고 가정해보자. 맨 앞의 창은 키보드와 모니터를 사용할 수 있지만 뒤에 있는 창은 키보드와 모니터를 사용하지 못한다.
이떄 맨 앞에서 동작하는 프로세스를 foreground process, 뒤에서 작동하는 프로세스를 background process라고 한다.
Linux/Unix 셸에서 백그라운드 프로세스를 만드려면 명령어 뒤에 &를 붙인다. 다음 예를 보자.
sleep 100 // foreground process
sleep 100& // background process
1행의 sleep 100
은 100초 동안 기다리라는 명령이다. 이러한 전면 프로세스의 경우 키보드를 눌러서 명령을 내려도 응답하지 않는다. 즉 다른 작업을 하지 못한 채 100초 동안 기다려야 한다.
그러나 2행의 후면 프로세스는 바로 다음 작업을 실행할 수 있다. 대신 100초 뒤에 sleep 100
이 끝났음을 알려준다.