관리 메뉴

Linux Programmer

fork, vfork 그리고 posix_spawn 이야기 본문

컴퓨터 관련/프로그래밍 일반

fork, vfork 그리고 posix_spawn 이야기

sunyzero 2013.03.13 18:48

* TOC


fork란?

fork는 유닉스/리눅스 계열에서 새로운 프로세스를 만드는 표준 함수이다. 그런데 fork는 새로운 프로세스를 만들 때 기존 프로세스를 복제하는 방식을 사용한다. 이 때 원본 프로세스를 부모 프로세스(parent process)라고 부르고 새로 복제된 프로세스를 자식 프로세스(child process)라고 부른다.


1. 부모와 자식 프로세스의 관계

현실 세계에서 부모와 자식은 끊을 수 없는 강한 연결점이 있지만, 유닉스 계열의 부모와 자식 프로세스는 그런 것과는 상관이 없이 이름이 지어졌다. 그냥 유전자가 복제되듯이 정적 자원 만을 복제하는 것이라고 생각하면 된다.


정적 자원에는 file, mask, memory등이 해당되며 복제되지 않는 것으로는 pid, ppid와 같이 프로세스 고유 정보들이 있다.


간혹 몇몇 인터넷이나 책에 부모 프로세스가 죽으면 자식 프로세스도 종료된다고 쓰여있지만 틀린 이야기다. 부모와 자식 프로세스는 별도의 공간에서 실행되므로 아무런 관련이 없다. 심지어 부모 프로세스가 부지불식간에 종료하거나 자식 프로세스가 비정상 종료한다고 해서 서로에게 문제를 발생시키는 경우는 없다. 오히려 데몬(daemon) 프로세스는 자식 프로세스가 시동되면 부모 프로세스는 일부러 죽도록 설계된 형태를 가진다.


부모가 먼저 죽은 경우에 자식 프로세스를 고아(orphan process)라고 부른다. 이와 반대로 자식이 먼저 죽은 경우에 부모가 return status를 읽지 않은 경우, 즉 PCB의 meta 정보들이 해제되지 않은 경우를 defunct (zombie) process라고 부른다. 이 두가지 개념을 정확하게 알고 있어야만 한다.


그러면 왜 부모, 자식 프로세스로 분류를 해두었을까?

유닉스 계열에서 부모, 자식 프로세스 관계를 만들어 둔 것은 세션(SID)과 프로세스 그룹(PGID) 그리고 제어 터미널(Control terminal)을 통해 프로세스를 관리하기 위해서 만들어놓은 개념일 뿐이다. 이에 대한 것은 운영체제 개론 및 실습 시간에 배우기 때문에 여기서는 SID, PGID, control terminal의 힌트만 적겠다. 책을 봐도 모르겠고 비전공자라면 메일로 콕 찔러보면 답변을...



2. fork와 fork-exec

fork로 부모 프로세스를 복제하는 목적은 2가지 유형이 있다.


첫번째는 부모 프로세스가 자식 프로세스에게 어떤 작업을 위임하는 경우다. 일반적으로 서버 프로세스가 여기에 해당한다. 아래 그림처럼 작업 태스크가 서로 의존성(dependency)이 없다면 fork를 통해 멀티태스킹을 할 수 있다. (보통 이 과정에서 부모 자식 프로세스가 데이터 교환이 필요하다면 IPC를 이용해서 통신하기도 한다.)


fork - multi-taskingfork - multi-tasking


대표적인 예로 잘 알려진 웹서버인 아파치(apache)가 이런 방식을 사용한다. 아파치 웹서버는 처음 기동된 부모 프로세스가 fork를 통해 몇 개의 자식 프로세스를 복제해둔다. 그리고 새로운 접속은 자식 프로세스들이 받아서 처리하도록 설계되어있다. 부모는 자식 프로세스의 개수를 조절하는 일만 담당한다. 이렇게 접속이 있기 전에 미리 fork해두는 서버 형태를 pre-fork MPM(Multi Process Model) 방식이라고 부른다.


두번째는 쉘(shell)처럼 다른 프로세스를 실행시키는 경우다. 이 경우는 fork 후에 exec 계열 명령을 실행하게 된다.

exec 계열 명령은 현재 프로세스의 실행 이미지를 다른 파일로 교체시키는 기능이다. exec가 실행되면 기존의 메모리는 모두 해제되고 file, mask, pid 등의 정보만 유지된채 다른 실행 파일 이미지로 교체된다. 쉽게 말해 다른 실행 파일이 작동되는 것이다.


이렇게 fork후에 exec가 연속해서 등장하는 과정을 fork-exec라고 부른다. 


그런데 fork를 하면서 복제되었던 메모리가 곧바로 exec를 하면서 해제되므로 무의미한 메모리 복제 과정, 즉 오버헤드가 존재하게 된다. fork-exec의 오버헤드를 제거하려면 메모리 복제를 피하도록 설계해야만 했다. 하지만 문제는 fork후 exec 호출 할지 아닐지를 어떻게 예측할 수 있느냐가 관건이었다. 


결국 예측할 수 없으므로 프로그래머의 용도에 따라 fork-exec인 경우라면, 메모리 복제를 하지 않는 새로운 함수를 쓰도록 대안 함수가 제시되었다. 그 결과 실험적인 유닉스인 BSD3에서 vfork라는 기능이 탄생되었다.



3. vfork와 문제점

vfork는 1980년 경에 BSD3에 포팅되었고, 기능은 부모 프로세스의 메모리를 복제하지 않도록 하는 것이었다. 즉 부모 프로세스와 페이지 테이블을 공유하도록 설계하는 것이었다.


이를 위해 vfork는 몇 가지 제약점을 가지게 되었다. 


첫째로 vfork로 만들어진 자식 프로세스가 종료하거나 exec로 교체되기 전까지 부모 프로세스가 대기(wait)상태로 만들어야만 했다. 이는 vfork에서는 부모와 자식이 페이지 테이블을 공유하므로 메모리를 변경하면 문제가 발생할 수 있기 때문이었다. 


둘째로 만에 하나라도 메모리가 변경되면 어떤 일이 발생할지 알 수 없으므로 자식 프로세스는 페이지 테이블을 읽기 전용으로 접근 해야만 했다.


셋째로 스케줄러는 vfork로 만들어진 자식 프로세스가 exec를 호출하기 전에 부모, 자식 프로세스가 스케줄링 되지 않도록 조정해야만 했다. 이는 스케줄러를 비효율적으로 만드는 예외상황이기도 했다.


결과적으로 vfork는 1980년대 당시에는 혁신적이었지만 메모리 테이블 및 스케줄러에 대한 예외를 발생시키는 문제점을 가지고 있었다. 더군다나 exit 함수나 IPC자원, 쓰레드를 사용하는 환경에서는 위의 제약점을 위반시킬 수 있게 되었다. 이는 몇몇 라이브러리를 사용할 때 예기치 못한 잠재적 오류를 발생시킬 수 있었다. 표준안의 semantic은 함수가 프로세스의 상태나 조건에 따라 다르게 작동하는 예외상황을 인정하지 않으므로 vfork는 표준안에서 당연히 채택될 수 없었다.


이런 문제점 때문에 표준안이 정해지면서 vfork는 제외되었다. 과거에 vfork로 개발된 코드들도 문제를 일으키기 십상이었기 때문에 사용을 금지해야 할 정도로 유해한 함수가 되었다.


또한 성능에 대한 장점도 fork에 COW(copy-on-write) 기법이 탑재된 뒤로는 차이가 없어져서 현실적으로 vfork는 어드밴티지는 없고 부작용만 남게 되었다. 가까운 미래에 커널 내부에서조차 vfork는 퇴출거거나 호환성을 위해 남겨두지만 fork로 대체될 가능성도 있다.[각주:1]



4. posix_spawn의 등장


앞서 언급한대로 fork-exec에서 COW가 도입되고 성능적인 제약은 사라졌지만 fork-exec는 여전히 문제점을 가지고 있었다. 그것은 Signal mask나 파일, 스케줄링 정책/우선순위가 상속되는 점과 스왑영역 확보등의 문제였다. 특히 파일(or 소켓)이나 스케줄링 정책/우선순위의 상속 문제는 매우 민감하여 예기치 못한 작동을 발생시킬 소지가 있었다.


결국 POSIX 표준에서 fork-exec를 대체할 기능으로 posix_spawn을 제안하게 되었고 1999년 IEEE-1003.1d에 정식 채택되었다.


posix_spawn은 fork와 exec가 1개의 함수 내에서 행해지도록 되어있다. fork만 따로 했을 때 생기는 스왑 영역을 확보하거나 스케줄링, 파일기술자들을 상속하는 오버 헤드 부분이 생략되므로 저성능 임베디드 시스템이나 실시간 시스템에서 좀 더 나은 성능을 보여준다.[각주:2] 물론 임베디드가 아닌 경우에도 큰 장점이 된다.


문제는 1999년에 채택된 표준임에도 불구하고 15여년이 지난 지금까지도 posix_spawn을 사용하지 않는 구식 코드들이 많다. 이는 보수적인 유닉스 업계의 문제도 있지만 가장 걸림돌은 posix_spawn을 소개하지 않는 시스템 프로그래밍 책들도 문제다. 실례로 최근에 졸업한 신입사원들을 교육시키다 보면 posix_spawn에 대해 배우지 않았다고 한다. 이 외에 SUS 표준에 새로 추가된 IPC나 동기화, 쓰레드 기능들을 모르는 사원들이 태반이다.(심지어 SUS가 뭔가요?하는 사람들도...) OTL~


요즘 대학 강의요즘 대학 강의


사진처럼 대학 강의에서 필기가 사라져가고 스마트 폰으로 촬영할 정도로 시대는 변해가고 있다. 그런데 15년 전에 나온 표준 함수인 posix_spawn 같은 기능조차 배우지 않는 것은 좀 심하다는 생각이 든다.


IT업계는 자꾸 변화하고 있다. 변화에 뒤쳐지지 않으려면 새로운 기능을 배워야만 한다. 유닉스 업계는 몇 년에 한 번 정도 새로운 기능이 추가될 정도로 변화가 느린 편이다.(자바나 다른 언어에 비하면 엄청 발전이 느리다.) 그럼에도 불구하고 몇 년에 한번씩 나오는 새로운 기능조차 습득하지 않는다면 게으른게 아닐까?



5. 2줄 결론

vfork는 쓰지 말자.

지금 fork-exec를 코딩하고 있다면 posix_spawn으로 바꿔보자. - 끝 -

  1. Marc J. Rochkind. Advanced UNIX Programming 제2판. 정보문화사. p423 [본문으로]
  2. Marc J. Rochkind. Advanced UNIX Programming 제2판. 정보문화사. p423 [본문으로]
12 Comments
  • 프로필사진 v명월v 2013.03.13 22:43 신고 안녕하세요.. 좋은글 읽고 갑니다..
    새로운 것을 또 알아가네요...
    좋은 하루 되세요^^
  • 프로필사진 sunyzero 2013.03.13 23:28 신고 감사해요. 좋은 하루 되세요. ^^
  • 프로필사진 독자 2013.03.22 10:47 신고 내용 감사합니다.
    그런데 이런 업데이트 되는 정보는 어떻게 얻으시나요?
    그게 궁금합니다..
  • 프로필사진 sunyzero 2013.03.22 12:14 신고 1차적으로는 SUS표준을 봅니다.
    그리고 서점에 가서 표준에 추가된 새로운 내용이 있는 책을 사보죠.

    미리 표준안의 내용을 알고 있으면 좋은 책을 고를 수 있죠.
    (반대로 말하면 표준안의 내용을 알고 있으면 전공서적중에 내용이 허접하거나 페이지만 잔뜩 늘린 책을 피할 수 있죠.)

    그 외엔 IBM developerworks나 intel 같은 사이트를 주로 봅니다.
  • 프로필사진 독자 2013.03.23 15:38 신고 답변 감사합니다.
    SUS 표준부터 찾아봐야 겠군요...
  • 프로필사진 sunyzero 2013.03.23 17:42 신고 Single UNIX Spec.은 여기서 다운 받으실 수 있습니다.

    http://www.opengroup.org/standards/platform

    HTML , PDF버전으로 모두 제공됩니다. 돈 안받으니 다운 받으셔서 보세요. ^^
  • 프로필사진 독자 2013.03.25 11:51 신고 알려주셔서 감사합니다..
  • 프로필사진 나그네 2018.06.05 00:51 신고 안녕하세요 몇개 궁금한게 있는데
    cow 기법이 fork시에 자식 프로세스가 write를 할 경우에만 메모리 복사가 일어나고 그렇지 않으면 공유하도록 지원 하는것 같은데
    예전에는 이게 없어서 fork-exec시에 메모리 복사, 교체라는 오버헤드가 발생하였고 현재에는 오버헤드는 아니지만 스왑영역확보등의 문제가 여전히 남아있다는 거죠?
    이 스왑영역확보가 뭔지 혹시 알려주실수 있을까요?
    그리고 cow 지원상태에서 fork-exec가 실행된다면 메모리 공유상태에서 교체가 되는게 맞는건가요? 저술하신 시스템 네트워크 프로그래밍과
    추천하신 유닉스 고급 프로그래밍등의 책을 보면서 공부하는데 쉽지가 않네요
  • 프로필사진 sunyzero 2018.06.05 12:28 신고 먼저 CoW는 fork시에만 적용되는 기술은 아닙니다. 크게 보면 메모리 페이지에 대한 처리 기술인데, fork-exec에서 가장 큰 효과를 본다고 할 수 있습니다. 그래서 mmap이나 다른 부분을 보시다 보면 CoW는 자주 등장하게 됩니다.

    CoW는 자식 프로세스가 write할 경우만이 아니라 부모가 write를 해도 일어납니다. 즉 공유된 페이지의 변경이 일어나면 발생합니다.

    그리고 궁금해하시는 스왑 확보는 유닉스의 역사에 관련된 부분입니다. 지금 대부분의 유닉스, 리눅스는 overcommit을 지원하고 있으므로 swap 공간 확보에 대한 문제는 상당부분 완화되어있지만, 설정에 따라 overcommit을 제한할 수도 있고, 혹은 몇몇 구형 유닉스는 스왑에 대해 보수적으로 접근하도록 설계되어있기도 합니다. 실제로 과거 Solaris UNIX는 이 스왑 확보 문제때문에 대용량 메모리를 사용하는 프로세스에서 system() 함수를 사용하거나 Java JVM에서 외부 명령을 실행할 때 비효율적으로 작동하는 일이 있었습니다.

    그리고 fork-exec가 실행되면 메모리 공유상태에서 교체된다고는 표현하지 않습니다. CoW의 효과로 인해 무의미한 메모리 복사가 스킵되는 것으로 표현합니다. 비슷하지만 둘은 뜻이 다릅니다.
    또한 exec로 인해 새로운 이미지로 교체될 때, 자주 사용되던 실행 바이너리 이미지라면 어딘가에는 cached 되어있는 filebacked page이므로 실행 속도가 빨라지게 되죠. 그래서 strace로 실행 구간을 분석해보면 대부분의 SO(shared object)들도 mmap으로 가져오는 것을 볼 수 있는 것입니다. (물론 설정 파일들도 실행 이미지는 아니지만 저렇게 mmap으로 읽어옵니다)

    결과적으로 cow는 page copy 행위를 pending시키는 기법이며, 이에 대한 이해는 OS의 page 관리에 대한 부분을 잘 보셔야 전체적인 그림이 제대로 그려집니다. 예를 들어 page중에 filebacked memory와 anonymous memory의 차이와 active memory, inactive memory의 차이도 알아야만 합니다. 이를 통해 page hit와 성능에 대한 이해를 하시면 큰 그림을 그릴 수 있을겁니다.
    원래 시스템 프로그래밍은 OS에 대한 이해가 깊어야만 쉽습니다. 이론적으로 부족한 부분이 있으시면 OS를 같이 공부하시는 것도 좋습니다.
  • 프로필사진 나그네 2018.06.05 23:31 신고 헉 답변 달아주셔서 정말 감사합니다
    궁금증이 해결 되긴 했는데 공부할게 몇개 더 생겼네요 ^^;
    혹시 os 공부 관련해서 추천 해주실만한 책이 있을까요?
  • 프로필사진 sunyzero 2018.06.06 00:13 신고 OS는 공룡책이나 스톨링즈 책 같은 책은 기본서인데 학교에서 수업때 쓰니까 그 때 배우시면 됩니다.
    그 외의 OS 응용서로는 CSAPP나 리눅스 운영체제에 대한 책이나 커널 관련 책들을 보시면 좋을 듯 합니다. 한번에 이해하려고 하기보단 여러번 보면서 이해 안가는 부분은 건너뛰더라도 계속 보는게 좋습니다.

    또한 국내서로는 강우진님이 쓰신 리눅스 책이 근래에 나온 책중에는 괜찮습니다.
  • 프로필사진 나그네 2018.06.06 14:29 신고 sunyzero님 정말 감사합니다
댓글쓰기 폼