fork는 새로운 프로세스를 만들 때 기존 프로세스를 복제하는 방식을 사용한다.
이때, fork를 호출한 부모 프로세스(parent process)를 원본으로 삼아 새로 복제하여 자식 프로세스(child process)를 만든다.
fork는 자식 프로세스 생성과 동시에 필요한 메모리 영역을 할당하고 부모 프로세스의 메모리 영역을 모두 자식 프로세스의 메모리 공간으로 복사한다. 하지만 fork가 부모 프로세스 메모리 공간을 모두 복사 하는 것은 비효율적이고, 오래 전 방식이며 현재 리눅스의 fork는 copy-on-write 기법을 사용하도록 개선되었다.
Copy-on-write 기법
자식 프로세스가 부모 프로세스의 메모리 공간을 모두 복사하는 것이 아니고, 동일 메모리 공간을 참조하고 있다가 변경사항이 발생할 경우에만 복사 하도록 구현되어 있다. 현재 리눅스의 fork가 Copy-on-write 기법을 도입하면서, 아래 설명할 vfork 의 장점이 많이 사라진 상태이다.
위에서 설명할 fork 함수의 사용법을 살펴보면, 왜 vfork가 탄생하게 되었는지를 이해할 수 있게 된다.
fork 함수는 주로 fork함수 실행 직후에 exec 계열 함수를 호출하는 방식으로 사용된다. exec 계열 명령은 현재 프로세스 실행 이미지를 다른 파일로 교체 시키는 기능이다. exec가 실행되면 기존 메모리는 모두 해제되고, file, mask, pid 등의 정보만 유지된 채 다른 실행 파일 이미지로 교체된다. 쉽게 말해 다른 실행 파일이 실행 되는 것이다. 이렇게 fork 후에 exec가 연속해서 호출되는 과정을 fork-and-exec모델이라고 부른다. 그런데 fork를 하면서 복제되었던 메모리가 곧바로 exec를 하면서 해제 되므로 무의미한 메모리 복제과정, 즉 오버헤드가 존재 하게 된다. fork-and-exec 방식의 오버헤드를 제거하려면 메모리 복제를 피하도록 설계 해야만 했다. 하지만 문제는 fork후에 exec 호출할 것인지 아닌지를 예측할 수 있는 방법은 없으며, 결국 프로그래머 용도에 fork-and-exec인 경우라면 메모리 복제를 하지 않는 새로운 함수를 쓰도록 대안이 제시되었다. 그 결과 실험적인 유닉스인 BSD3에서 vfork라는 함수가 탄생되었다.
자식 프로세스가 생성되면서 부모 프로세스 메모리, 파일 기술자 테이블 등을 복사하여 프로세스를 구성한다. 하지만, 자원을 복사하는데 걸리는 시간 때문에 프로세스 생성까지 오랜 시간이 걸렸고, 우리는 vfork가 fork의 무의미한 메모리 복제과정을 회피하기 위해 고안된 것이라는 것을 알고 있다. vfork 는 부모 프로세스의 메모리를 복제하는 대신에, 메모리를 공유하는 방법을 선택했다. fork는 광역과 지역 메모리 공간까지 똑같은 복사본을 만들고 부모와 자식이 서로 다른 메모리 공간을 사용하지만, 단점을 보완하기 위해서 vfork는 프로세스만 만들고 부모의 메모리 공간을 그대로 이용한다.(부모와 자식이 메모리 공간을 공유)
vfork는 부모프로세스 와 주소공간을 공유하고, 메모리 주소 공간에 대한 복사가 이루어지지 않기 때문에, 기존 fork보다 빠르게 프로세스를 생성할 수 있다. 하지만 자원을 공유하기 때문에, 자식 프로세스가 실행되어 변수들을 수정하기 시작하면, 부모 프로세스는 영향을 받게된다. 자원에 대한 race condition이 발생하게 되는 것이다. vfork는 이 문제를 해결하기 위해서 부모 프로세스는 자식프로세스가 exit 하거나 execve가 호출되지 전까지 SLEEP 상태로 대기시킨다. 즉, 자식프로세스가 실행이 완료되어 exit를 실행하거나 exec를 호출하여 다른 프로그램을 실행하고 종료될 때까지 부모 프로세스는 계속 기다리고 있게 된다. vfork 를 사용하는 경우에 execve를 바로 호출 하지 않는 경우라면 자식 프로세스에서 수정한 광역변수나 지역변수들이 부모 프로세스에 영향을 미치기 때문에, 이를 고려하여 코딩 하여야 한다. 이점은 많이 이들이 vfork 가 위험하다고 보는 이유이기도 하다.
위에 언급한 것과 같이 fork가 전체 메모리 주소 공간을 복사한다는 것은 예전 방식이고 현재 리눅스에서는 fork는 copy-on-write 방식을 사용하여 프로세스 생성 시 모든 자원을 복사하는 것이 아니고 변경사항이 생길 경우에만 복사하도록 구현되어 있다. 따라서 현재 vfork가 갖는 장점은 부모 프로세스 페이지 테이블을 복사 하지 않는 것 뿐이다. 또한 성능에 대한 장점도 fork 함수에 copy-on-write 방식이 구현된 후로는 차이가 없어져서 현실적으로 vfork는 이점은 없어졌고, 자식 프로세스가 종료되거나 exec로 교체되기 전까지 부모 프로세스가 대기 상태로 기다려야만 하는 부작용 등만 남게 되었다.
따라서, 많은 관련 문서에서는 더 이상 vfork의 사용을 중지하기를 권고하고 하고 있다. POSIX 표준에서는 fork-and-exec 방식을 이용하여 표준 함수posix_spawn를 만들었다. 아직 많은 부분에서 vfork는 사용되고 있지만, 시간이 흘러감에 따라 점차 vfork는 퇴출되거나 호환성을 위해 남겨두지만 fork 로 완전 대체될 가능성도 있다.
fork()는 부모 프로세스의 메모리를 복사해서 사용
vfork()는 부모 프로세스와의 메모리를 공유함. 복사하지 않기 때문에 fork()보다 생성 속도 빠름. 하지만 자원을 공유하기 때문에 자원에 대한 race condition이 발생하지 않도록 하기 위해 부모 프로세스는 자식 프로세스가 exit하거나 execute가 호출되기 전까지 block된다