새로운 프로세스를 생성하기 위해 분기해야하는 이유는 무엇입니까?


95

유닉스에서는 새로운 프로세스를 만들고 싶을 때마다 현재 프로세스를 분기하여 부모 프로세스와 정확히 동일한 새 자식 프로세스를 만듭니다. 그런 다음 exec 시스템 호출을 수행하여 상위 프로세스의 모든 데이터를 새 프로세스의 데이터로 바꿉니다.

처음에 상위 프로세스의 사본을 작성하고 새 프로세스를 직접 작성하지 않는 이유는 무엇입니까?


답변:


61

짧은 대답은 fork당시의 기존 시스템에 맞추기 쉽고 버클리이전 시스템 이 포크 개념을 사용 했기 때문에 Unix에 있습니다 .

에서 유닉스 시간 공유 시스템의 진화 (관련 텍스트가되었습니다 강조 ) :

현대적인 형태의 프로세스 제어는 며칠 내에 설계 및 구현되었습니다. 기존 시스템에 얼마나 쉽게 적용되는지 놀랍습니다. 동시에 , 디자인에서 약간 특이한 특징 중 일부가 존재하는 것에 대한 작고 쉽게 코딩 된 변경 사항을 나타 내기 때문에 어떻게 정확하게 나타나는지 쉽게 알 수 있습니다 . 좋은 예는 포크와 실행 함수의 분리입니다. 새로운 프로세스 생성을위한 가장 일반적인 모델은 프로세스가 실행할 프로그램을 지정하는 것입니다. 유닉스에서, 분기 프로세스는 명시 적 실행을 수행 할 때까지 상위 프로세스와 동일한 프로그램을 계속 실행합니다. 기능의 분리는 확실히 유닉스 고유의 것이 아니며, 실제로는 톰슨에게 잘 알려진 버클리 시간 공유 시스템에 존재했습니다.. 여전히 Unix에 존재 한다고 가정하는 것이 합리적 입니다. 포크를 쉽게 변경하지 않고도 쉽게 구현할 수 있기 때문입니다 . 시스템은 이미 여러 프로세스 (즉, 두 개)를 처리했습니다. 프로세스 테이블이 있었고 프로세스는 기본 메모리와 디스크간에 교환되었습니다. 포크의 초기 구현에만 필요

1) 공정 테이블 확장

2) 기존 스왑 IO 프리미티브를 사용하여 현재 프로세스를 디스크 스왑 영역에 복사하고 프로세스 테이블을 약간 조정 한 포크 호출 추가.

실제로 PDP-7 포크 호출에는 정확히 27 줄의 어셈블리 코드가 필요했습니다. 물론 운영 체제와 사용자 프로그램의 다른 변경이 필요했으며 그 중 일부는 다소 흥미롭고 예상치 못한 결과였습니다. 그러나 exec가 존재하지 않았기 때문에 결합 된 fork-exec는 훨씬 더 복잡 했을 것입니다. 이 기능은 명시 적 IO를 사용하여 셸에서 이미 수행되었습니다.

그 논문 이후, 유닉스는 발전했다. fork다음에 exec더 이상 프로그램을 실행할 수있는 유일한 방법입니다.

  • vfork 는 새로운 프로세스가 포크 바로 다음에 exec를 수행하려는 경우보다 효율적인 포크로 만들어졌습니다. vfork를 수행 한 후 상위 프로세스와 하위 프로세스는 동일한 데이터 공간을 공유하며 하위 프로세스가 프로그램을 실행하거나 종료 될 때까지 상위 프로세스가 일시 중단됩니다.

  • posix_spawn 은 새 프로세스를 작성하고 단일 시스템 호출로 파일을 실행합니다. 호출자의 열린 파일을 선택적으로 공유하고 신호 처리 및 기타 속성을 새 프로세스에 복사 할 수있는 많은 매개 변수가 필요합니다.


5
좋은 대답이지만 vfork를 더 이상 사용해서는 안된다고 덧붙입니다. 현재 성능 차이는 미미하여 사용이 위험 할 수 있습니다. 이 SO 질문 stackoverflow.com/questions/4856255/...이 사이트를 참조하십시오 ewontfix.com/7 vfork 상태에 대한, 그리고 "고급 유닉스 프로그래밍"페이지 299
라파엘 렌스

4
posix_spawn()쉽게 사용 fork()하고 인라인 코드를 사용하여 수행 할 수있는 동일한 포스트 포크 리필 작업을 수행하는 데 필요한 기계화 (데이터 구조 설정)는 사용 fork()이 훨씬 간단하다는 강력한 주장 을합니다.
Jonathan Leffler 2016 년

34

[내 답변의 일부를 여기 에서 반복 하겠습니다 .]

처음부터 새로운 프로세스를 만드는 명령이없는 이유는 무엇입니까? 바로 교체 될 제품을 복사하는 것이 터무니없고 비효율적이지 않습니까?

실제로 몇 가지 이유로 효율적이지 않을 수 있습니다.

  1. fork()커널이 COW ( Copy-On-Write) 시스템을 사용하기 때문에 "복사" 는 약간 추상화 된 것입니다 . 실제로 만들어야하는 것은 가상 메모리 맵입니다. 복사가 즉시을 호출 exec()하는 경우 프로세스의 활동에 의해 수정 된 경우 복사 된 대부분의 데이터는 프로세스에서 사용이 필요한 작업을 수행하지 않으므로 실제로 복사 / 생성 할 필요가 없습니다.

  2. 하위 프로세스 (예 : 환경)의 다양한 중요한 측면은 컨텍스트 등의 복잡한 분석을 기반으로 개별적으로 복제되거나 설정 될 필요가 없습니다. 단지 호출 프로세스와 동일하다고 가정합니다. 이것은 우리에게 익숙한 상당히 직관적 인 시스템입니다.

# 1을 조금 더 설명하기 위해, "복사"되었지만 이후에 액세스되지 않는 메모리는 적어도 대부분의 경우 실제로 복사되지 않습니다. 이 컨텍스트에서 예외 는 프로세스를 분기 한 다음 하위 프로세스가로 대체되기 전에 상위 프로세스를 종료 한 경우 일 수 있습니다 exec(). 내가 말할 힘을 여유 메모리가 충분한 경우 부모의 대부분은 캐시 할 수 있기 때문에, 나는 (운영 체제 구현에 달려있는) 어느 정도이 악용 될 것이 확실하지 않다.

물론, 표면에없는 것은 빈 슬레이트를 사용하는 것보다 복사본을 효율적으로 사용하는 것입니다. "빈 슬레이트"는 말 그대로 아무것도 아니며 할당을 포함해야합니다. 시스템은 동일한 방식으로 복사하는 일반 빈 / 새 프로세스 템플릿을 가질 수 있지만 , 1 은 복사 중 작성 포크와 비교하여 아무것도 저장하지 않습니다. 따라서 # 1은 "새"빈 프로세스를 사용하는 것이 더 효율적이지 않다는 것을 보여줍니다.

포인트 # 2는 포크 사용이 더 효율적인 이유를 설명합니다. 자식 환경은 완전히 다른 실행 파일이더라도 부모 환경에서 상속됩니다. 예를 들어, 상위 프로세스가 쉘이고 하위가 웹 브라우저 인 $HOME경우 여전히 둘 다 동일하지만 둘 중 하나가 변경 될 수 있으므로 두 개의 별도 사본이어야합니다. 아이의 것은 원본에 의해 만들어집니다 fork().

1. 말 그대로 의미가없는 전략이지만 프로세스를 만들려면 디스크에서 이미지를 메모리에 복사하는 것 이상의 작업이 필요합니다.


3
두 가지 점이 모두 사실이지만 주어진 실행 파일에서 새 프로세스를 취소하는 대신 포크 방법이 선택된 이유를 지원하지 않습니다.
SkyDan

3
나는 이것이 질문에 대답한다고 생각합니다. 새 프로세스를 작성하는 것이 가장 효율적인 방법 인 경우 포크를 사용하는 비용이 사소한 것 (프로세스 작성 비용의 1 % 미만)이기 때문에 포크가 사용됩니다. 반면에 포크가 파일 핸들 처리와 같은 API보다 훨씬 더 효율적이거나 훨씬 간단한 곳이 많이 있습니다. 유닉스가 내린 결정은 하나의 API 만 지원하여 사양을 단순화하는 것이 었습니다.
Cort Ammon 2018 년

1
@SkyDan 당신이 맞습니다. Mark Plotnick이 더 직접적 으로 대답하는 이유가 아닌 이유 에 대한 대답입니다. 이것이 가장 쉬운 선택 일뿐 만 아니라 아마도 가장 효율적 이라는 것을 의미하는 것으로 해석됩니다. 선택 (Dennis Richie 인용문에 따르면 : "PDP-7의 포크 호출에는 정확히 27 줄의 어셈블리가 필요했습니다. 실행 파일은 존재하지 않았으며 그 기능은 이미 수행되었습니다"). 그래서이 "왜 안 되겠는가?"는 아마도 두 가지 전략에 대한 생각을하는 것입니다. 두 가지 전략 중 하나 더 단순하고 더 효율적으로 보일 수 있습니다 (아마도 그렇지 않은 경우).
goldilocks

1
골디 락이 맞습니다. 처음부터 새로 작성하는 것보다 포크와 수정이 더 저렴한 상황이 있습니다. 물론 가장 극단적 인 예는 포크 동작 자체를 원할 때입니다. fork()GL이 언급 한 것처럼 27 개 라인의 순서로 매우 빠르게 수행 할 수 있습니다. 다른 방향을 살펴보면, "처음부터 프로세스 생성"을 원한다면, fork()빈 프로세스 (27 줄의 어셈블리 + 파일 핸들 닫는 비용)에서 시작하는 것보다 약간의 비용이 든다. 따라서 fork포크와 생성을 모두 잘 create처리 할 수 ​​있지만 잘 만들 수만 있습니다.
Cort Ammon

2
귀하의 답변은 하드웨어 개선 : 가상 메모리, 기록 중 복사에 관한 것입니다. 이 전에 fork실제로 모든 프로세스 메모리를 복사했는데 비용이 많이 들었습니다.
Barmar

6

유닉스가 fork새로운 프로세스를 만드는 기능 만 가지고있는 이유 는 유닉스 철학 의 결과 라고 생각합니다

그들은 하나의 일을 잘하는 하나의 기능을 만듭니다. 자식 프로세스를 만듭니다.

새로운 프로세스로하는 일은 프로그래머에게 달려 있습니다. 그는 exec*함수 중 하나를 사용 하고 다른 프로그램을 시작할 수 있거나 exec를 사용하지 못하고 동일한 프로그램의 두 인스턴스를 사용할 수있어 유용 할 수 있습니다.

사용할 수 있기 때문에 더 큰 자유를 얻습니다

  1. exec가없는 포크 *
  2. exec *가있는 포크 또는
  3. 포크없이 그냥 exec *

그리고 추가로 당신은 단지 암기해야 fork하고, exec*1970 년대에 당신이해야 할 일을했을 함수 호출을.


3
포크 작동 방식과 사용 방법을 이해합니다. 그러나 왜 똑같은 일을 할 수 있지만 적은 노력으로 새로운 프로세스를 만들고 싶습니까? 예를 들어, 선생님은 argv에 전달 된 각 숫자에 대한 프로세스를 작성하여 숫자가 소수인지 확인하는 과제를주었습니다. 그러나 그것은 궁극적으로 똑같은 일을 우회하는 것이 아닙니까? 방금 배열을 사용하고 각 숫자에 대해 함수를 사용할 수 있었을 것입니다. 그렇다면 주 프로세스에서 모든 처리를 수행하는 대신 왜 하위 프로세스를 작성합니까?
user1534664

2
포크가 작동하는 방법과 사용 방법을 이해한다고 말하고 싶습니다. 여러분 은 한 번에 많은 프로세스를 생성 해야하는 과제를 주었던 교사가 있었기 때문에 (런타임에 숫자가 지정되어 있음) 그들을 통제하고 조정하며 그들 사이에 의사 소통을합니다. 물론 아무도 현실에서 그렇게 사소한 일을하지 않을 것입니다. 그러나 병렬로 처리 할 수있는 조각 (예 : 이미지의 가장자리 감지)으로 쉽게 분해되는 큰 문제가있는 경우 포크를 사용하면 여러 CPU 코어를 동시에 사용할 수 있습니다.
Scott

5

프로세스 생성의 두 가지 철학이 있습니다. 상속을 가진 포크와 인수로 만드는 것입니다. 유닉스는 분명히 포크를 사용합니다. (예를 들어, OSE 및 VMS는 create 메소드를 사용합니다.) Unix에는 상속 가능한 특성이 많으며 더 정기적으로 추가됩니다. 상속을 통해 기존 프로그램을 변경하지 않고도 이러한 새로운 특성을 추가 할 수 있습니다! 인수로 작성 모델을 사용하여 새 특성을 추가하면 작성 호출에 새 인수를 추가해야합니다. 유닉스 모델이 더 간단합니다.

또한 프로세스가 여러 조각으로 나눌 수있는 매우 유용한 포크없는 실행 모델을 제공합니다. 이것은 비동기 I / O 형식이 없을 때 중요했으며 시스템에서 여러 CPU를 활용할 때 유용합니다. (사전 스레드) 나는 수년 동안, 심지어 최근에도 이것을 많이 해왔습니다. 본질적으로 여러 '프로그램'을 단일 프로그램으로 컨테이너화 할 수 있으므로 손상이나 버전 불일치 등이 전혀 없습니다.

포크 / 실행 모델은 또한 특정 아동이 포크와 실행 사이에 설정된 근본적으로 이상한 환경을 상속받을 수있는 능력을 제공합니다. 특히 상속 된 파일 디스크립터와 같은 것들. (stdio fd 's의 확장) create 모델은 create call 생성자가 상상하지 않은 것을 상속하는 기능을 제공하지 않습니다.

일부 시스템은 고유 코드의 동적 컴파일을 지원할 수 있으며, 여기서 프로세스는 자체 고유 코드 프로그램을 작성합니다. 다시 말해, 소스 코드 / 컴파일러 / 링커주기를 거치지 않고 디스크 공간을 차지하지 않고 즉시 자체 작성하는 새 프로그램을 원합니다. (이 작업을 수행하는 Verilog 언어 시스템이 있다고 생각합니다.) 포크 모델이이를 지원하지만 작성 모델은 일반적으로 지원하지 않습니다.


파일 디스크립터는 "stdio의 확장"이 아닙니다. stdio 파일 포인터는 파일 디스크립터를 감싸는 래퍼입니다. 파일 디스크립터가 먼저 나타 났으며 기본 Unix I / O 핸들입니다. 그러나 그렇지 않으면 이것이 좋은 지적입니다.
Scott

2

fork () 함수는 아버지 프로세스를 복사 할뿐 아니라 프로세스가 아버지 또는 아들 프로세스임을 나타내는 값을 리턴합니다. 아래 이미지는 fork ()를 아버지로서 사용하는 방법을 설명합니다. 아들:

여기에 이미지 설명을 입력하십시오

프로세스가 아버지 인 경우에 표시되는 것처럼 fork ()는 아들 프로세스 ID를 리턴하고 PID 그렇지 않으면 리턴합니다.0

예를 들어 요청을 수신하는 프로세스 (웹 서버)가 있고 요청마다이 요청 son process을 처리 할 프로세스를 작성하는 경우이를 사용할 수 있습니다. 여기에서 아버지와 아들은 다른 작업을 갖습니다.

따라서 프로세스 복사본을 실행하지 않는 것은 fork ()와 정확히 일치하지 않습니다.


5
사실이지만 이것은 질문에 대답하지 않습니다. 다른 실행 파일을 실행하려는 경우 프로세스 작성에 포크가 필요한 이유는 무엇입니까?
SkyDan

1
SkyDan에 동의합니다 – 이것은 질문에 대한 답변이 아닙니다. posix_spawnfork_execve 함수 로 30 년 전에 (Posix가 존재하기 전에) 상상되었던 것보다 다소 더 멋진 버전입니다 . 새로운 프로세스를 생성하고, 부모 프로세스의 이미지를 복사하지 않아도 (인수 목록, 환경 및 프로세스 속성 (예 : 작업 디렉토리) 제외) 실행 파일에서 이미지를 초기화하고 프로세스 를 반환하는 프로세스 호출자에 대한 새 프로세스의 PID (부모 프로세스) .
Scott

1
"부모"정보를 자녀에게 전달하는 다른 방법이 있습니다. 첫 번째 장소에서 fork 원한다고 가정하면fork
Cort Ammon

0

I / O 리디렉션은 포크 후 및 실행 전에 가장 쉽게 구현됩니다. 자식은 자식임을 알고 파일 부모를 닫지 않고 파일 설명자를 닫고, 새 설명자를 열고, dup () 또는 dup2 () 올바른 fd 번호 등으로 가져올 수 있습니다. 그렇게 한 다음, 원하는 환경 변수 변경 (부모에 영향을 미치지 않음)을 수행 한 후에는 조정 된 환경에서 새 프로그램을 실행할 수 있습니다.


여기에서하는 일은 Jim Cathey의 답변 의 세 번째 단락을 조금 더 자세하게 반복하는 것입니다 .
Scott

-2

나는 여기의 모든 사람들이 포크가 어떻게 작동하는지 알고 있다고 생각하지만 포크를 사용하여 부모의 정확한 복제본을 만들어야하는 이유는 무엇입니까? 답변 ==> 서버의 예를 들어 (포크없이), client-1이 서버에 액세스하는 동안, 동시에 client-2가 도착하여 서버에 액세스하려고하지만 서버가 새로 도착한 서버에 권한을 부여하지 않는 경우 client-2는 서버가 client-1을 서비스하기 때문에 바쁘기 때문에 client-2는 기다려야합니다. client-1에 대한 모든 서비스가 완료된 후 client-2는 이제 서버에 액세스 할 수 있습니다. client-3가 도착하면 client-3는 client-2에 대한 모든 서비스가 완료 될 때까지 기다려야합니다. 수천 명의 클라이언트가 동시에 서버에 액세스해야하는 시나리오를 수행하십시오. 대기 (서버가 바쁘다 !!).

이것은 (포크를 사용하여) 서버의 정확한 복제본 (즉, 하위)을 생성함으로써 피할 수 있습니다. 여기서 각 하위 (상위 즉 서버의 정확한 복제본)는 새로 도착한 클라이언트 전용이므로 모든 클라이언트가 동시에 액세스합니다 섬기는 사람.


그렇기 때문에 서버 프로세스 가 단일 스레드가되어 클라이언트 요청 을 개별 프로세스에서 동시에 처리 할 수있을 때 연속적 으로 처리해야 합니다 . 그러나 다중 스레드 서버 모델은 클라이언트의 요청을 수락하고 클라이언트 서비스 프로그램을 실행할 새로운 프로세스를 작성하는 리스너 프로세스로 쉽게 구현할 수 있습니다. fork상위 프로세스를 복사 하는 호출에 의해 제공되는 유일한 이점 은 두 개의 별도 프로그램이 필요하지 않지만 별도의 프로그램 (예 :)이 inetd있으면 시스템을보다 모듈화 할 수 있다는 것입니다.
Scott
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.