현대 운영체제에서 fork()와 exec()를 넘어서

전통적인 Unix 프로세스 생성 모델인 fork()exec()의 조합은 개발자와 시스템 아키텍트들이 현대 컴퓨팅에 있어 비효율적인 추상화라고 주장함에 따라 새로운 정밀 조사를 받고 있습니다. 이 모델은 초기 Unix 설계의 초석 역할을 했지만, 새로운 실행 파일을 교체하기 전에 부모 프로세스를 복제해야 한다는 요구 사항은 종종 불필요하며 계산 비용이 많이 듭니다.

fork() + exec() 모델의 비효율성

fork() + exec() 패턴의 핵심 문제는 복제와 폐기의 중복된 사이클을 생성한다는 점입니다. 대부분의 사용 사례에서 개발자는 현재 프로세스의 복제본보다는 완전히 새로운 프로세스를 시작하기를 원합니다. fork()를 사용하면 운영체제가 프로세스 상태를 복제하도록 강제하며, 그 직후 exec()가 새로운 바이너리를 로드하기 위해 해당 상태를 즉시 폐기하게 됩니다.

성능 오버헤드와 Copy-on-Write의 신화

Copy-on-Write (CoW)는 모든 물리 메모리의 즉각적인 복사를 방지하는 최적화 기법이지만, fork()의 성능 비용을 제거하지는 못합니다.

"fork()가 저렴하다는 것은 이상할 정도로 흔한 오해입니다... 그것은 프로세스 크기에 따라 O(N)이며, 항상 그래왔습니다. 네, copy on write 방식이지만... 프로세스의 크기와 이를 표현하기 위해 필요한 페이지 테이블 엔트리의 수 사이에는 선형적인 관계가 있습니다."

커널은 프로세스의 메모리 공간을 표현하기 위해 여전히 페이지 테이블을 복제해야 하므로, fork()의 비용은 프로세스의 크기에 따라 선형적으로 증가합니다. 대규모 프로세스의 경우, 특히 프로세스 직후에 exec() 호출이 이어지는 경우, 이 오버헤드는 상당한 병목 현상이 됩니다.

프로세스 복제의 아키텍처적 비판

성능을 넘어, fork() 모델은 개념적으로 결함이 있는 추상화로 비판받습니다. 이는 주된 목표가 단순히 실행 파일을 실행하는 것임에도 불구하고 개발자가 복제용으로 설계된 메커니즘을 사용하도록 강제합니다.

"Clone and Fix" 안티 패턴

fork()는 부모의 정확한 복제본을 생성하기 때문에, 개발자들은 종종 fork() 이후 exec() 이전에 자식 프로세스를 "수정"해야 하는 상황에 직면하게 됩니다. 여기에는 불필요한 파일 디스크립터를 닫거나 환경 변수를 조정하는 작업이 포함됩니다. 이러한 "clone and then fix in post" 방식은 버그가 발생하기 쉽고 직접적인 프로세스 생성 호출보다 덜 직관적입니다.

다른 OS 모델과의 비교

일부 개발자들은 다른 운영체제가 이를 더 깔끔하게 구현했다고 주장합니다. 예를 들어, Windows의 CreateProcessW 인터페이스는 부모 프로세스의 사전 복제 없이도 개발자가 새 프로세스의 매개변수를 직접 지정할 수 있게 해주므로 더 자연스러운 접근 방식이라고 인용됩니다.

fork() + exec() 모델을 옹호하는 논거

비판에도 불구하고, 일부에서는 fork() + exec() 모델의 우아함이 그 유연성에 있다고 주장합니다. 프로세스 생성을 두 단계로 나눔으로써, OS는 자식 프로세스가 새로운 프로그램의 실행을 시작하기 전에 표준 API를 사용하여 환경을 구성할 수 있는(예: 표준 입출력 리다이렉션) 시간적 여유를 제공합니다.

결합된 호출의 과제

대안을 제안하는 비판론자들은 spawn 또는 posix_spawn 스타일의 결합된 호출이 fork() 모델의 유연성을 맞추기 위해 방대한 양의 구성 매개변수를 필요로 할 것이다라고 제안합니다. 이것이 없다면, 결합된 호출은 너무 제한적이거나 새로운 요구 사항이 emerge(출현)함에 따라 복잡하고 유지보수가 불가능한 매개변수 덩어리가 될 것입니다.

제안된 대안과 미래 방향

fork()를 대체하는 것에 대한 논의는 로직을 사용자 공간으로 이동시키거나 커널 수준의 hooks를 활용하는 것을 포함하여 몇 가지 앞으로의 경로를를 제안합니다.

사용자 공간 라이브러리와 eBPF

일부에서는 반복적으로 프로세스를 생성하는 오버헤드(예: 장기 실행 작업에서 git을 반복적으로 호출하는 경우)를 도구 자체를를 라이브러리로 변환하여 직접 링크함으로써 해결해야 한다고 제안합니다. 이를 통해 프로세스 생성의 필요성을 완전히 피계할 수 있습니다.

현대적인 커널 접근 방식

다른 이들은 더 현대적인 커널 접근 방식을 제안합니다. 아마도 eBPF (extended Berkeley Packet Filter)를 활용하여 숙련된 사용자가 hooks를 통해 프로세스 생성 흐름을 것을하게 하여, 복잡한 구성을 위한 유연성을 유지하면서 불필요한 복제 단계를 건너뛸 수 있게 하는 것입니다.

Sources