fork (), vfork (), exec () 및 clone ()의 차이점


198

Google 에서이 4 가지의 차이점을 찾고 있었고이 정보에 대한 많은 양의 정보가있을 것으로 예상했지만 실제로는 4 개의 통화 사이에 확실한 비교가 없었습니다.

이러한 시스템 호출의 차이점을 살펴보면 일종의 기본 개요를 한눈에 살펴볼 수 있습니다. 이 모든 정보가 정확합니까 / 중요한 것이 누락 되었습니까?

Fork : 포크 호출은 기본적으로 현재 프로세스의 복제본을 거의 모든 방식으로 동일하게 만듭니다 (예를 들어, 일부 구현에서 자원 제한과 같은 모든 것이 복사되지는 않지만 가능한 한 사본을 가깝게 만드는 아이디어입니다).

새 프로세스 (자식)는 다른 프로세스 ID (PID)를 가져오고 이전 프로세스 (부모)의 PID를 상위 PID (PPID)로 갖습니다. 두 프로세스가 이제 정확히 동일한 코드를 실행하고 있기 때문에 fork의 리턴 코드를 통해 어떤 프로세스가 어떤 것인지 알 수 있습니다. 하위는 0이되고 상위는 하위의 PID를 가져옵니다. 이것은 물론 포크 호출이 작동한다고 가정합니다. 그렇지 않으면 자식이 생성되지 않고 부모가 오류 코드를 얻습니다.

Vfork: vfork와 fork의 기본 차이점은 vfork ()를 사용하여 새 프로세스를 만들 때 부모 프로세스가 일시적으로 중단되고 자식 프로세스가 부모의 주소 공간을 빌릴 수 있다는 것입니다. 이 이상한 상황은 자식 프로세스가 종료되거나 execve ()를 호출 할 때까지 계속되며,이 때 부모 프로세스는 계속됩니다.

즉, vfork ()의 ​​자식 프로세스는 부모 프로세스의 변수를 예기치 않게 수정하지 않도록주의해야합니다. 특히, 자식 프로세스는 vfork () 호출을 포함하는 함수에서 리턴해서는 안되며 exit ()를 호출하지 않아야합니다 (종료해야하는 경우 _exit ()를 사용해야합니다. 실제로, 이는 자식에 대해서도 마찬가지입니다. 일반 포크 ()).

Exec :exec 호출은 기본적으로 현재 프로세스 전체를 새로운 프로그램으로 대체하는 방법입니다. 프로그램을 현재 프로세스 공간으로로드하고 진입 점에서 실행합니다. exec ()는 현재 프로세스를 함수가 가리키는 실행 파일로 바꿉니다. exec () 오류가 없으면 컨트롤은 원래 프로그램으로 돌아 가지 않습니다.

Clone :클론은 포크로서 새로운 프로세스를 만듭니다. 포크와는 달리, 이러한 호출을 통해 하위 프로세스는 메모리 공간, 파일 디스크립터 테이블 및 신호 핸들러 테이블과 같은 실행 컨텍스트의 일부를 호출 프로세스와 공유 할 수 있습니다.

자식 프로세스가 clone으로 생성되면 함수 응용 프로그램 fn (arg)이 실행됩니다. (이것은 원래 포크 호출 시점부터 자식에서 실행이 계속되는 포크와 다릅니다.) fn 인수는 실행 시작시 자식 프로세스가 호출하는 함수에 대한 포인터입니다. arg 인수는 fn 함수로 전달됩니다.

fn (arg) 함수 응용 프로그램이 리턴되면 하위 프로세스가 종료됩니다. fn에 의해 리턴 된 정수는 하위 프로세스의 종료 코드입니다. 하위 프로세스는 exit (2)를 호출하거나 치명적 신호를 수신 한 후 명시 적으로 종료 될 수도 있습니다.

입수 한 정보 :

이것을 읽어 주셔서 감사합니다! :)


2
vfork가 exit ()를 호출하지 않아야하는 이유는 무엇입니까? 아니면 돌아 오지 않습니까? exit ()가 _exit ()를 사용하지 않습니까? 나는 또한 이해하려고 노력하고있다 :)
LazerSharks

2
@Gnuey : fork()부모의 주소 공간을 빌릴 가능성이 있기 때문에 ( 리눅스와 아마도 모든 BSD와 다르게 구현 되는 경우) 가능합니다. 아무거나는 전화 외에, 수행 execve()또는 _exit()부모까지 혼란에 큰 잠재력을 가지고있다. 특히, exit()호출 atexit()처리기 및 기타 "완료 기"는 예를 들어 stdio 스트림을 비 웁니다. vfork()아이 에게서 돌아 오면 (전과 같은 경고) 부모의 스택을 엉망으로 만들 수 있습니다.
ninjalj

부모 프로세스의 스레드가 어떻게되는지 궁금합니다. 모두 복제되었거나 forksyscall 을 호출하는 스레드 만 있습니까?
Mohammad Jafar Mashhadi

@LazerSharks vfork는 복사 중 복사 방지없이 메모리를 공유하는 스레드와 유사한 프로세스를 생성하므로 스택 작업을 수행하면 상위 프로세스가 손상 될 수 있습니다.
Jasen

답변:


160
  • vfork()사용되지 않는 최적화입니다. 좋은 메모리 관리 전에 fork()부모 메모리의 전체 사본을 만들었으므로 꽤 비쌌습니다. 많은 경우에 현재 메모리 맵을 버리고 새로운 맵을 생성하는 a fork()가 뒤에 exec()오기 때문에 불필요한 비용이 들었습니다. 요즘에는 fork()메모리를 복사하지 않습니다. 그것은 단순히 때문에, "쓰기 복사"로 설정되어 fork()+는 exec()효율적으로 그냥 vfork()+ exec().

  • clone()에서 사용하는 syscall fork()입니다. 일부 매개 변수를 사용하면 새 프로세스를 만들고 다른 매개 변수를 사용하면 스레드를 만듭니다. 이들의 차이점은 메모리 공간, 프로세서 상태, 스택, PID, 열린 파일 등의 데이터 구조가 공유되는지 여부입니다.



22
vfork일시적으로 훨씬 더 많은 메모리를 커밋 할 필요가 없어서 실행할 수 exec있으며, 그 정도는 fork아니더라도 여전히보다 효율적 입니다. 따라서, 큰 프로그램이 자식 프로세스를 생성 할 수 있도록 메모리를 초과 커밋하지 않아도됩니다. 따라서 성능 향상뿐만 아니라 전혀 가능하지 않을 수도 있습니다.
중복 제거기

5
실제로 RSS가 클 때 fork ()가 얼마나 저렴한 지 직접 확인했습니다. 커널이 여전히 모든 페이지 테이블을 복사해야하기 때문이라고 생각합니다.
Martina Ferrari

4
모든 페이지 테이블을 복사하고 쓰기 프로세스 에서 쓰기 가능한 모든 메모리에 쓰기시 복사를 설정 하고 TLB를 플러시 한 다음 모든 변경 사항을 부모로 되돌리고 다시 TLB를 플러시해야합니다 exec.
zwol

3
vfork는 여전히 cygwin (Microsoft의 Windows에서 실행되는 커널 에뮬레이션 dll)에서 유용합니다. cygwin은 기본 OS에 포크가 없으므로 효율적인 포크를 구현할 수 없습니다.
ctrl-alt-delor

81
  • execve() 현재 실행 가능 이미지를 실행 파일에서로드 된 다른 이미지로 대체합니다.
  • fork() 자식 프로세스를 만듭니다.
  • vfork()의 최적화 된 이전 버전 fork()으로을 (를) execve()바로 다음에 호출 할 때 사용됩니다 fork(). MMU가 아닌 시스템 ( fork()효율적인 방식으로 작동 할 수없는 곳 )과 fork()작은 메모리 공간을 가진 프로세스를 작은 프로그램을 실행하기 위해 처리 할 때 잘 작동하는 것으로 나타났습니다 (Java 생각 Runtime.exec()). POSIX는 posix_spawn()이 후자를 두 가지 더 현대적인 용도로 대체하도록 표준화했습니다 vfork().
  • posix_spawn()는 a fork()/execve()와 동등하며 일부 fd 사이에서 저글링을 허용합니다. fork()/execve()주로 비 MMU 플랫폼의 경우을 대체해야합니다 .
  • pthread_create() 새로운 스레드를 만듭니다.
  • clone()는 Linux 전용 호출이며에서 fork()까지 구현하는 데 사용할 수 있습니다 pthread_create(). 그것은 많은 통제권을줍니다. 에 영감을 받았습니다 rfork().
  • rfork()Plan-9 전용 통화입니다. 전체 프로세스와 스레드간에 여러 수준의 공유를 허용하는 일반 호출이어야합니다.

2
실제로 요청한 것보다 많은 정보를 추가해 주셔서 감사합니다. 시간을 절약 할 수있었습니다.
Neeraj

5
계획 9는 그런 애타게입니다.
JJ

1
MMU의 의미를 기억할 수없는 사람들을 위해 : "메모리 관리 장치"- 위키 백과에 대한
mgarey

43
  1. fork()-부모 프로세스의 완전한 복사 본인 새 자식 프로세스를 만듭니다. 자식 프로세스와 부모 프로세스는 서로 다른 가상 주소 공간을 사용하며 처음에는 동일한 메모리 페이지로 채워집니다. 그런 다음 두 프로세스가 실행될 때 운영 체제가이 두 프로세스 중 하나에 의해 작성되는 메모리 페이지의 지연 복사를 수행하고 수정 된 페이지의 독립적 인 사본을 할당하기 때문에 가상 주소 공간이 점점 더 달라지기 시작합니다. 각 프로세스에 대한 메모리. 이 기술을 COW (Copy-On-Write)라고합니다.
  2. vfork()-부모 프로세스의 "빠른"복사 본인 새 자식 프로세스를 만듭니다. 시스템 호출과 달리 fork()자식 프로세스와 부모 프로세스는 동일한 가상 주소 공간을 공유합니다. 노트! 동일한 가상 주소 공간을 사용하면 부모와 자식 모두 클래식의 경우와 동일한 스택, 스택 포인터 및 명령 포인터를 사용합니다 fork()! 동일한 스택을 사용하는 부모와 자식 간의 원치 않는 간섭을 방지하기 위해 자식 프로세스가 호출 exec()(새 가상 주소 공간 생성 및 다른 스택으로의 전환) 또는 _exit()프로세스 실행 종료 까지 부모 프로세스 실행이 고정됩니다. ). "fork-and-exec"모델 vfork()의 최적화입니다 fork(). fork()와 달리 4-5 배 빠르게 수행 할 수 있습니다 .fork()(COW를 염두에두고도) vfork()시스템 호출 구현 에는 새 주소 공간 작성 (새 페이지 디렉토리의 할당 및 설정)이 포함되지 않습니다.
  3. clone()-새로운 자식 프로세스를 만듭니다. 이 시스템 호출의 다양한 매개 변수는 상위 프로세스의 어떤 부분이 하위 프로세스에 복사되어야하는지와 그 사이에서 공유 될 부분을 지정합니다. 결과적으로이 시스템 호출을 사용하여 스레드에서 시작하여 완전히 독립적 인 프로세스로 마무리하는 모든 종류의 실행 엔터티를 만들 수 있습니다. 실제로, clone()시스템 호출은 시스템 호출의 pthread_create()모든 패밀리 및 구현에 사용되는 기본입니다 fork().
  4. exec()-프로세스의 모든 메모리를 재설정하고 지정된 실행 가능한 바이너리를로드 및 구문 분석하고 새 스택을 설정하고로드 된 실행 파일의 시작점으로 제어를 전달합니다. 이 시스템 호출은 호출자에게 제어를 리턴하지 않으며 기존 프로그램에 새 프로그램을로드하는 데 사용됩니다. 시스템 호출과 fork()함께이 시스템 호출은 "fork-and-exec"라는 고전적인 UNIX 프로세스 관리 모델을 형성합니다.

2
BSD 및 POSIX 요구 사항 vfork이 너무 약하여 vfork동의어 를 만드는 것이 합법적 입니다 fork(및 POSIX.1-2008은 vfork사양에서 완전히 제거됨 ). 동의어가있는 시스템에서 코드를 테스트하는 경우 (예 : NetBSD를 제외한 대부분의 4.4 이후 BSD, 2.0.pre-pre6 Linux 커널 등) vfork계약 을 위반하더라도 폭발 할 수 있습니다. 다른 곳에서 실행하면 forkOpenBSD 를 사용하여 시뮬레이션하는 것 중 일부는 여전히 부모가 자식 exec또는 _exits가 될 때까지 다시 실행되지 않도록 보장합니다 . 말도 안되는 이식성이 없습니다.
ShadowRanger

2
당신의 3 점의 마지막 문장에 관한 : 내가 strace를을 사용하여 Linux에 발견하는 포크 ()에 대한 참으로 glibc는 래퍼) 복제 콜, vfork를위한 래퍼 (호출 vfork를 시스템 콜을 호출하는 동안
ilstam

7

fork (), vfork () 및 clone ()은 모두 실제 작업을 수행하기 위해 do_fork ()를 호출하지만 매개 변수는 다릅니다.

asmlinkage int sys_fork(struct pt_regs regs)
{
    return do_fork(SIGCHLD, regs.esp, &regs, 0);
}

asmlinkage int sys_clone(struct pt_regs regs)
{
    unsigned long clone_flags;
    unsigned long newsp;

    clone_flags = regs.ebx;
    newsp = regs.ecx;
    if (!newsp)
        newsp = regs.esp;
    return do_fork(clone_flags, newsp, &regs, 0);
}
asmlinkage int sys_vfork(struct pt_regs regs)
{
    return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, &regs, 0);
}
#define CLONE_VFORK 0x00004000  /* set if the parent wants the child to wake it up on mm_release */
#define CLONE_VM    0x00000100  /* set if VM shared between processes */

SIGCHLD means the child should send this signal to its father when exit.

포크의 경우 자식과 아버지는 독립적 인 VM 페이지 테이블을 가지지 만 효율성 때문에 포크는 실제로 페이지를 복사하지 않으므로 쓰기 가능한 모든 페이지를 자식 프로세스의 읽기 전용으로 설정합니다. 따라서 자식 프로세스가 해당 페이지에 무언가를 쓰려고 할 때 페이지 예외가 발생하고 커널은 이전 페이지에서 복제 된 새 페이지를 쓰기 권한으로 할당합니다. 이를 "쓰기시 복사"라고합니다.

vfork의 경우 가상 메모리는 정확히 자녀와 아버지에 의한 것입니다. 그 때문에 아버지와 자녀는 서로 영향을 미치기 때문에 동시에 깨울 수 없습니다. 따라서 아버지는 "do_fork ()"의 끝에서 자고 자식이 exit () 또는 execve ()를 호출하면 새 페이지 테이블을 소유 할 때 깨어납니다. 아버지가 자고있는 코드 (do_fork ())가 있습니다.

if ((clone_flags & CLONE_VFORK) && (retval > 0))
down(&sem);
return retval;

다음은 아버지를 깨우는 exit () 및 execve ()에 의해 호출 된 mm_release () 코드입니다.

up(tsk->p_opptr->vfork_sem);

sys_clone ()의 경우 clone_flags를 입력 할 수 있으므로 더 유연합니다. 따라서 pthread_create ()는 많은 clone_flags로이 시스템 호출을 호출합니다.

int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM);

요약 : fork (), vfork () 및 clone ()은 아버지 프로세스와 다른 공유 자원 마운트로 하위 프로세스를 작성합니다. vfork ()와 clone ()은 VM 페이지 테이블을 아버지 프로세스와 공유하기 때문에 스레드를 생성 할 수 있다고 말할 수 있습니다.


-4

fork ()에서는 자식 또는 부모 프로세스가 CPU 선택에 따라 실행됩니다. 그러나 vfork ()에서는 반드시 자식이 먼저 실행됩니다. 자식이 종료되면 부모가 실행됩니다.


3
잘못된. vfork()로 구현할 수 있습니다 fork().
ninjalj

AnyFork () 이후에 누가 첫 번째 부모 / 자식을 실행하는지는 정의되지 않습니다.
AjayKumarBasuthkar

5
@ Raj : 포크 후 일련의 순서에 대한 암시 적 개념이 있다고 생각하면 개념적 오해가 있습니다. Forking은 새로운 프로세스를 생성 한 다음 두 프로세스 모두에 제어를 반환합니다 (각각 다른 프로세스가 반환 됨 pid). 운영 체제는 이러한 프로세스가 의미가있는 경우 (예 : 다중 프로세서) 새 프로세스가 병렬로 실행되도록 예약 할 수 있습니다. 어떤 이유로 특정 일련의 순서로 이러한 프로세스를 실행해야하는 경우 분기가 제공하지 않는 추가 동기화가 필요합니다. 솔직히, 당신은 아마 처음부터 포크를 원하지 않을 것입니다.
Andon M. Coleman

실제로 @AjayKumarBasuthkar와 @ninjalj는 둘 다 잘못되었습니다. 를 사용 vfork()하면 자식이 먼저 실행됩니다. 매뉴얼 페이지에 있습니다. 부모의 처형은 아동이 사망하거나 사망 할 때까지 중단 exec됩니다. 그리고 ninjalj는 커널 소스 코드를 찾습니다. 구현 방법이 없습니다 vfork()로서 fork()그들이 서로 다른 인수를 전달하기 때문에 do_fork()커널 내에서가. 그러나 syscall을 vfork사용하여 구현할 수 있습니다clone
Zac Wimer

@ZacWimer : 다른 답변에 대한 ShadowRanger의 의견을보십시오 stackoverflow.com/questions/4856255 / ... 오래된 Linux NetBSD 이외의 BSD (많은 MMU 이외의 시스템으로 이식되는 경향이 있음)와 마찬가지로 이들을 동의어로 만들었습니다. 리눅스 맨 페이지에서 : 4.4BSD에서는 fork (2)와 동의어가되었지만 NetBSD는이를 다시 소개했습니다. ⟨참조 netbsd.org/Documentation/kernel/vfork.html을 ⟩. Linux에서는 2.2.0-pre6 정도까지 fork (2)와 동일합니다.
ninjalj
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.