concurrent programming을 하기 위해 가장 기본적으로 사용되는 함수가 바로 fork입니다. fork는 기존에 존재하는 프로세스를 그대로 복사하여 하나의 프로세스를 생성하는 개념입다. 이때, 기존에 있던 프로세스를 부모 프로세스, 새로 만들어진 프로세스를 자식 프로세스라고 생각하면 이해하기 쉽습니다.
부모와 자식은 독립적으로 존재하지만 자식은 부모로부터 대부분의 자원을 물려받습니다.
fork로 concurrent programming을 하는 대표적인 예제는 아래와 같습니다.
이런 코드의 흐름이 가능한 이유는 부모 프로세스는 fork()의 반환값으로 자식 프로세스의 pid를 받고, 자식 프로세스는 0을 반환 값으로 받기 때문입니다.
위의 코드는 자식은 a에 5를 삽입하고 부모는 6을 삽입하여 각각 출력합니다.
자식 프로세스가 exit()로 나가고 그것을 부모 프로세스가 wait()로 종료됨을 확인하면 병행 프로세스는 종료됩니다.
부모 프로세스가 자식 프로세스가 종료된 것을 확인하고 종료되므로 항상 프로세스 종료 순서는 자식 -> 부모이지만 뭐가 먼저 출력되는가는 다른 문제입니다.
실행 순서는 쉽게 말해 랜덤이라고 생각하면 됩니다.
exit, wait 함수는 병행적으로 동작하는 프로세스들간에 순서를 정해주는 함수입니다. 앞선 예제와 같이 어느 프로세스가 exit으로 실행이 종료됨을 알리면 wait 함수가 그것을 알아차려서 종료하는 방식입니다. exit과 wait 함수는 매개변수로 status 값을 넣어서 어떤 상태로 exit 하고 wait 하는지를 알릴 수 있습니다. 중요한 점은 exit으로 실행이 종료되었다고 그 프로세스가 완전히 없어진 것이 아니라 zombie 상태로 남아있습니다. 기다리던 프로세스가 wait 상태에서 프로세스가 종료됨을 확인하고 깨어나면서 zombie 프로세스에 할당되어 있던 자원이 모두 해제되고 완전히 없어집니다.
wait 함수는 무슨 프로세스든지 exit으로 종료되면 wait하던 프로세스도 종료됩니다. 그래서 특정 프로세스를 기다리지는 못합니다. 특정 프로세스가 종료될 때까지 wait 하기 위해서는 waitpid라는 함수를 사용합니다.
이 함수는 pid에 해당하는 프로세스가 종료될 때까지 기다립니다. wait 함수와 마찬가지로 status를 지정할 수 있고 option으로 blocking, non-blocking을 설정할 수 있습니다. blocking은 기다리는 함수가 종료될 때까지 다른 작업을 안 하고 기다리는 것이고 non-blocking은 기다리는 함수가 끝났는지 확인해봤는데 작업 수행 중이면 0을 반환하고 바로 다른 작업을 수행합니다.
exec 함수는 fork와 비슷해 보이지만 다른 함수입니다. 프로세스를 새로 생성하지 않고 기존에 존재하는 프로세스가 하는 일을 바꿉니다. 쉽게 prog에 해당하는 주소 값에 위치하는 프로그램으로 작업을 전환합니다. int가 반환형이지만 exec에 성공하면 반환을 하지 않고 자기 갈 길을 가고 exec에 실패한 경우에는 -1을 반환합니다. exec을 수행하여도 아래의 자원은 그대로입니다.
같은 프로세스이지만 새로운 프로그램이 로드되었으므로 새로운 text, data, heap, stack을 가집니다.
위와 같이 fork를 하여서 새로운 프로세스를 생성 후 그 프로세스에다가 test.exe을 exec 하여 test.exe의 기능을 하는 프로세스로 만들 수 있습니다.
최종적으로는 아래와 같은 관계를 갖습니다.
fork를 한 프로세스는 부모와 자식의 관계이지만 exec만을 실행했을 때는 동일한 프로세스입니다.
따라서, 문제 1번의 shell의 부모는 init이고 문제 2번의 a.out의 부모는 shell입니다.