A diferença entre fork (), vfork (), exec () e clone ()


197

Eu estava procurando encontrar a diferença entre esses quatro no Google e esperava que houvesse uma quantidade enorme de informações sobre isso, mas realmente não havia uma comparação sólida entre as quatro chamadas.

Comecei a tentar compilar uma espécie de visão geral básica das diferenças entre essas chamadas de sistema e aqui está o que recebi. Todas essas informações estão corretas / estou perdendo alguma coisa importante?

Fork : A chamada de fork basicamente faz uma duplicata do processo atual, idêntica em quase todos os aspectos (nem tudo é copiado, por exemplo, nos limites de recursos em algumas implementações, mas a idéia é criar uma cópia o mais próxima possível).

O novo processo (filho) obtém um ID de processo diferente (PID) e tem o PID do processo antigo (pai) como seu PID pai (PPID). Como os dois processos agora estão executando exatamente o mesmo código, eles podem dizer qual é o código de retorno do fork - a criança recebe 0, o pai recebe o PID da criança. Isso é tudo, é claro, supondo que a chamada da bifurcação funcione - caso contrário, nenhum filho será criado e o pai receberá um código de erro.

Vfork: A diferença básica entre vfork e fork é que, quando um novo processo é criado com vfork (), o processo pai é suspenso temporariamente e o processo filho pode pedir emprestado o espaço de endereço do pai. Esse estranho estado de coisas continua até que o processo filho saia ou chame execve (), quando o processo pai continua.

Isso significa que o processo filho de um vfork () deve ter cuidado para evitar alterações inesperadas nas variáveis ​​do processo pai. Em particular, o processo filho não deve retornar da função que contém a chamada vfork () e não deve chamar exit () (se precisar sair, deve usar _exit (); na verdade, isso também é verdade para o filho de um garfo normal ()).

Exec :A chamada exec é uma maneira de substituir basicamente todo o processo atual por um novo programa. Ele carrega o programa no espaço de processo atual e o executa a partir do ponto de entrada. exec () substitui o processo atual por um executável apontado pela função. O controle nunca retorna ao programa original, a menos que haja um erro exec ().

Clone :Clone, como fork, cria um novo processo. Diferentemente da bifurcação, essas chamadas permitem que o processo filho compartilhe partes de seu contexto de execução com o processo de chamada, como espaço de memória, tabela de descritores de arquivo e tabela de manipuladores de sinal.

Quando o processo filho é criado com o clone, ele executa o aplicativo de função fn (arg). (Isso difere da bifurcação, onde a execução continua no filho a partir do ponto da chamada de bifurcação original.) O argumento fn é um ponteiro para uma função chamada pelo processo filho no início de sua execução. O argumento arg é passado para a função fn.

Quando o aplicativo da função fn (arg) retorna, o processo filho termina. O número inteiro retornado por fn é o código de saída para o processo filho. O processo filho também pode terminar explicitamente chamando exit (2) ou após receber um sinal fatal.

Informações obtidas:

Obrigado por tomar o tempo para ler este ! :)


2
Por que o vfork não deve chamar exit ()? Ou para não voltar? Exit () não usa apenas _exit ()? Eu também estou tentando entender :)
LazerSharks

2
@ Gnuey: porque é potencialmente (se implementado de forma diferente da fork()que está no Linux e provavelmente todos os BSDs) emprestar o espaço de endereço de seus pais. Tudo o que faz, além de chamar execve()ou _exit(), tem um grande potencial para atrapalhar os pais. Em particular, exit()chama atexit()manipuladores e outros "finalizadores", por exemplo: libera fluxos stdio. O retorno de um vfork()filho potencialmente (com a mesma ressalva de antes) prejudicaria a pilha dos pais.
Ninjalj

Eu queria saber o que acontece com os threads do processo pai; Todos eles são clonados ou apenas o thread que chama o forksyscall?
Mohammad Jafar Mashhadi

O @LazerSharks vfork produz um processo do tipo thread em que a memória é compartilhada sem proteções de copiar na gravação, portanto, fazer coisas de pilha pode prejudicar o processo pai.
Jasen

Respostas:


159
  • vfork()é uma otimização obsoleta. Antes de um bom gerenciamento de memória, fork()fazia uma cópia completa da memória dos pais, por isso era muito caro. como em muitos casos a fork()foi seguido por exec(), que descarta o mapa de memória atual e cria um novo, era uma despesa desnecessária. Hoje em dia, fork()não copia a memória; é simplesmente definido como "copiar na gravação", então fork()+ exec()é tão eficiente quanto vfork()+ exec().

  • clone()é o syscall usado por fork(). com alguns parâmetros, cria um novo processo, com outros, cria um encadeamento. a diferença entre eles é justamente quais estruturas de dados (espaço na memória, estado do processador, pilha, PID, arquivos abertos etc.) são compartilhadas ou não.



22
vforkevita a necessidade de comprometer temporariamente muito mais memória para que você possa executar exec, e ainda é mais eficiente do que fork, mesmo que não seja tão alto quanto possível. Assim, é possível evitar ter que comprometer demais a memória, para que um grande programa possa gerar um processo filho. Portanto, não apenas um aumento de desempenho, mas pode torná-lo possível.
Deduplicator

5
Na verdade, eu testemunhei em primeira mão como o fork () está longe de ser barato quando o seu RSS é grande. Eu presumo que isso ocorre porque o kernel ainda precisa copiar todas as tabelas de páginas.
Martina Ferrari

4
Ele deve copiar todas as tabelas de páginas, definir toda a memória gravável na gravação nos dois processos , liberar o TLB e, em seguida, reverter todas as alterações no pai (e liberar o TLB novamente) exec.
zwol 12/09/16

3
O vfork ainda é útil no cygwin (uma dll que emula o kernel, que roda em cima do Windows da Microsoft). O cygwin não pode implementar um fork eficiente, pois o sistema operacional subjacente não possui um.
Ctrl-alt-delor

80
  • execve() substitui a imagem executável atual por outra carregada de um arquivo executável.
  • fork() cria um processo filho.
  • vfork()é uma versão histórica otimizada do fork(), destinada a ser usada quando execve()for chamada diretamente depois fork(). Acabou funcionando bem em sistemas não-MMU (onde fork()não pode funcionar de maneira eficiente) e ao fork()processar processos com um enorme espaço de memória para executar algum programa pequeno (pense em Java Runtime.exec()). O POSIX padronizou oposix_spawn() substituição desses dois últimos usos mais modernos do vfork().
  • posix_spawn()faz o equivalente a fork()/execve(), e também permite algum fd malabarismo no meio. É suposto substituirfork()/execve() , principalmente para plataformas não-MMU.
  • pthread_create() cria um novo thread.
  • clone()é uma chamada específica do Linux, que pode ser usada para implementar qualquer coisa de fork()até pthread_create(). Dá muito controle. Inspirado em rfork().
  • rfork()é uma chamada específica do plano 9. Deveria ser uma chamada genérica, permitindo vários graus de compartilhamento, entre processos e threads completos.

2
Obrigado por adicionar mais informação do que foi realmente pediu, ele me ajudou a salvar o meu tempo
Neeraj

5
O plano 9 é uma provocação.
JJ

1
Para quem não se lembra o que significa MMU: "unidade de gestão de memória" - uma leitura mais adicional na Wikipedia
mgarey

43
  1. fork()- cria um novo processo filho, que é uma cópia completa do processo pai. Os processos filho e pai usam diferentes espaços de endereço virtual, inicialmente preenchidos pelas mesmas páginas de memória. Então, à medida que os dois processos são executados, os espaços de endereço virtual começam a diferir cada vez mais, porque o sistema operacional realiza uma cópia lenta das páginas de memória que estão sendo gravadas por um desses dois processos e atribui cópias independentes das páginas modificadas do memória para cada processo. Essa técnica é chamada de Copy-On-Write (COW).
  2. vfork()- cria um novo processo filho, que é uma cópia "rápida" do processo pai. Ao contrário da chamada do sistema fork(), os processos filho e pai compartilham o mesmo espaço de endereço virtual. NOTA! Usando o mesmo espaço de endereço virtual, pai e filho usam a mesma pilha, o ponteiro da pilha e o ponteiro da instrução, como no caso do clássico fork()! Para evitar interferência indesejada entre pai e filho, que usam a mesma pilha, a execução do processo pai é congelada até que o filho chame exec()(crie um novo espaço de endereço virtual e uma transição para uma pilha diferente) ou _exit()(finalização da execução do processo ) vfork()é a otimizaçãofork() "fork-and-exec". Pode ser executado 4-5 vezes mais rápido que o fork()porque, ao contrário dofork()(mesmo com o COW em mente), a implementação da dovfork() a chamada do sistema não inclui a criação de um novo espaço de endereço (a alocação e configuração de novos diretórios de página).
  3. clone()- cria um novo processo filho. Vários parâmetros dessa chamada do sistema especificam quais partes do processo pai devem ser copiadas no processo filho e quais partes serão compartilhadas entre eles. Como resultado, essa chamada do sistema pode ser usada para criar todos os tipos de entidades de execução, iniciando nos encadeamentos e finalizando por processos completamente independentes. De fato, a clone()chamada do sistema é a base usada para a implementação pthread_create()e toda a família das fork()chamadas do sistema.
  4. exec()- redefine toda a memória do processo, carrega e analisa o binário executável especificado, configura uma nova pilha e passa o controle para o ponto de entrada do executável carregado. Essa chamada do sistema nunca retorna o controle ao chamador e serve para carregar um novo programa no processo já existente. Essa chamada de sistema com a chamada de fork()sistema em conjunto formam um modelo clássico de gerenciamento de processos UNIX chamado "fork-and-exec".

2
Observe que os requisitos do BSD e do POSIX vforksão tão fracos que seria legal fazer vforkum sinônimo fork(e o POSIX.1-2008 remove vforkcompletamente as especificações). Se você testar seu código em um sistema que os sinonimiza (por exemplo, a maioria dos BSDs pós-4.4, exceto o NetBSD, os kernels Linux pré-2.2.0-pre6 do Linux, etc.), pode funcionar mesmo se você violar o vforkcontrato e explodir se você executá-lo em outro lugar. Alguns daqueles que simulá-lo com fork(por exemplo, OpenBSD) ainda garantir o pai não continuar funcionando até que a criança execs ou _exits. É ridiculamente não portátil.
ShadowRanger

2
sobre a última frase do seu 3º ponto: eu notei no Linux usando strace que, enquanto na verdade o wrapper glibc para fork () chama a syscall clone, o wrapper para vfork () chama a syscall vfork
ilstam

7

O fork (), vfork () e o clone () chamam o do_fork () para fazer o trabalho real, mas com parâmetros diferentes.

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.

Para fork, o filho e o pai têm a tabela de páginas VM independente, mas, como a eficiência, o fork realmente não copia nenhuma página, apenas define todas as páginas graváveis ​​como somente leitura para o processo filho. Portanto, quando o processo filho quiser escrever algo nessa página, ocorrerá uma exceção de página e o kernel alocará uma nova página clonada da página antiga com permissão de gravação. Isso é chamado "copiar na gravação".

Para o vfork, a memória virtual é exatamente por filho e pai - apenas por isso, pai e filho não podem acordar simultaneamente, pois eles se influenciarão. Portanto, o pai dormirá no final de "do_fork ()" e acordará quando a criança chamar exit () ou execve () desde então, ela possuirá a nova tabela de páginas. Aqui está o código (em do_fork ()) que o pai dorme.

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

Aqui está o código (em mm_release () chamado por exit () e execve ()) que acorda o pai.

up(tsk->p_opptr->vfork_sem);

Para sys_clone (), é mais flexível, pois você pode inserir qualquer clone_flags nele. Então pthread_create () chame essa chamada de sistema com muitos clone_flags:

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

Resumo: o fork (), vfork () e clone () criarão processos filhos com montagem diferente de compartilhamento de recursos com o processo pai. Também podemos dizer que o vfork () e o clone () podem criar threads (na verdade, são processos, pois possuem task_struct independente), pois compartilham a tabela de páginas da VM com o processo pai.


-4

no fork (), o processo filho ou pai será executado com base na seleção da CPU. Mas no vfork (), certamente o filho será executado primeiro. após o término do filho, o pai será executado.


3
Errado. vfork()apenas pode ser implementado como fork().
Ninjalj 04/09/2013

depois de AnyFork (), não está definido quem executa o primeiro pai / filho.
precisa saber é o seguinte

5
@Raj: Você tem alguns mal-entendidos conceituais se pensa que, depois de bifurcar, existe uma noção implícita de ordem serial. A bifurcação cria um novo processo e, em seguida, retorna o controle para os dois processos (cada um retornando um diferente pid) - o sistema operacional pode agendar o novo processo para ser executado em paralelo se algo assim fizer sentido (por exemplo, vários processadores). Se, por algum motivo, você precisar que esses processos sejam executados em uma ordem serial específica, precisará de uma sincronização adicional que o bifurcação não fornece; francamente, você provavelmente nem iria querer um garfo em primeiro lugar.
22614 Andon M. Coleman

Na verdade, @AjayKumarBasuthkar e @ninjalj, vocês dois estão errados. com vfork(), a criança corre primeiro. Está nas páginas do manual; a execução dos pais é suspensa até a criança morrer ou morrer exec. E ninjalj procura o código fonte do kernel. Não há maneira de implementar vfork()como fork()porque passam argumentos diferentes para do_fork()dentro do kernel. Você pode, no entanto, implementar vforkcom o clonesyscall
Zac Wimer 16/05/19

@ZacWimer: veja o comentário do ShadowRanger para outra resposta stackoverflow.com/questions/4856255/… O Linux antigo os sincronizou, como aparentemente BSDs diferentes do NetBSD (que tende a ser portado para muitos sistemas não-MMU). Na página de manual do Linux: no 4.4BSD, ele foi tornado sinônimo de fork (2), mas o NetBSD o introduziu novamente; veja ⟨netbsd.org/Documentation/kernel/vfork.html⟩ . No Linux, foi equivalente ao fork (2) até a versão 2.2.0-pre6.
Ninjalj 19/11/19
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.