Diferenças entre fork e exec

Quais são as diferenças entre fork e exec ?

O uso de fork e exec exemplifica o espírito do UNIX, pois fornece uma maneira muito simples de iniciar novos processos.

A chamada fork basicamente faz uma duplicata do processo atual, idêntica em quase todos os aspectos (nem tudo é copiado, por exemplo, limites de resources 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 (PID) diferente e tem o PID do processo antigo (pai) como seu PID pai (PPID). Como os dois processos estão executando exatamente o mesmo código, eles podem dizer qual é qual pelo código de retorno do fork – o filho recebe 0, o pai recebe o PID do filho. Isto é tudo, é claro, assumindo que a chamada fork funcione – se não, nenhum filho é criado e o pai recebe um código de erro.

A chamada exec é uma maneira de basicamente replace 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 input.

Então, fork e exec são freqüentemente usados ​​em seqüência para obter um novo programa rodando como um filho de um processo atual. Shells normalmente fazem isso sempre que você tenta executar um programa como find – o shell fork, então o filho carrega o programa find na memory, configurando todos os argumentos de linha de comando, I / O padrão e assim por diante.

Mas eles não precisam ser usados ​​juntos. É perfeitamente aceitável que um programa não seja exec se, por exemplo, o programa contiver código pai e filho (você precisa ser cuidadoso com o que faz, cada implementação pode ter restrições). Isso foi usado bastante (e ainda é) para daemons que simplesmente escutam em uma porta TCP e fork uma cópia de si mesmos para processar uma requisição específica enquanto o pai volta a escutar.

Da mesma forma, programas que sabem que estão acabados e que apenas querem executar outro programa não precisam fork , exec e wait pela criança. Eles podem simplesmente carregar a criança diretamente em seu espaço de processo.

Algumas implementações do UNIX possuem um fork otimizado que usa o que eles chamam de copy-on-write. Esse é um truque para atrasar a cópia do espaço de processo no fork até que o programa tente alterar algo nesse espaço. Isso é útil para os programas que usam apenas fork e não exec , pois não precisam copiar todo o espaço do processo.

Se o exec é chamado de fork seguir (e é isso que acontece principalmente), isso causa uma gravação no espaço do processo e é então copiado para o processo filho.

Note que há toda uma família de chamadas exec ( execl , execle , execve e assim por diante), mas exec no contexto aqui significa qualquer uma delas.

O diagrama a seguir ilustra a operação fork/exec típica em que o shell bash é usado para listar um diretório com o comando ls :

 +--------+ | pid=7 | | ppid=4 | | bash | +--------+ | | calls fork V +--------+ +--------+ | pid=7 | forks | pid=22 | | ppid=4 | ----------> | ppid=7 | | bash | | bash | +--------+ +--------+ | | | waits for pid 22 | calls exec to run ls | V | +--------+ | | pid=22 | | | ppid=7 | | | ls | V +--------+ +--------+ | | pid=7 | | exits | ppid=4 | <---------------+ | bash | +--------+ | | continues V 

fork() divide o processo atual em dois processos. Ou, em outras palavras, seu bom programa linear fácil de pensar em programas torna-se subitamente dois programas separados executando uma parte do código:

  int pid = fork(); if (pid == 0) { printf("I'm the child"); } else { printf("I'm the parent, my child is %i", pid); // here we can kill the child, but that's not very parently of us } 

Isso pode explodir sua mente. Agora você tem um pedaço de código com um estado praticamente idêntico sendo executado por dois processos. O processo filho herda todo o código e a memory do processo que acabou de criá-lo, incluindo a partir de onde a chamada fork() foi interrompida. A única diferença é o código de retorno fork() para informar se você é pai ou filho. Se você é pai, o valor de retorno é o id do filho.

exec é um pouco mais fácil de entender, você apenas diz ao exec para executar um processo usando o executável de destino e você não tem dois processos executando o mesmo código ou herdando o mesmo estado. Como o @Steve Hawkins diz, exec pode ser usado depois que você fork para executar no processo atual o executável de destino.

Eu acho que alguns conceitos de “Advanced Unix Programming” por Marc Rochkind foram úteis para entender os diferentes papéis de fork() / exec() , especialmente para alguém acostumado com o modelo Windows CreateProcess() :

Um programa é uma coleção de instruções e dados que são mantidos em um arquivo regular no disco. (de 1.1.2 Programas, Processos e Threads)

.

Para executar um programa, primeiro é pedido ao kernel para criar um novo processo , que é um ambiente no qual um programa é executado. (também de 1.1.2 Programas, Processos e Threads)

.

É impossível entender as chamadas do sistema exec ou fork sem entender completamente a distinção entre um processo e um programa. Se estes termos forem novos para você, você pode querer voltar e rever a Seção 1.1.2. Se você estiver pronto para prosseguir agora, resumiremos a distinção em uma sentença: Um processo é um ambiente de execução que consiste em instruções, dados do usuário e segmentos de dados do sistema, além de muitos outros resources adquiridos em tempo de execução. , enquanto um programa é um arquivo contendo instruções e dados que são usados ​​para inicializar os segmentos de instrução e dados de usuário de um processo. (de 5.3 exec System Calls)

Depois de entender a distinção entre um programa e um processo, o comportamento da function fork() e exec() pode ser resumido como:

  • fork() cria uma duplicata do processo atual
  • exec() substitui o programa no processo atual por outro programa

(isso é essencialmente uma versão simplificada para “dummies” da resposta muito mais detalhada de paxdiablo )

Fork cria uma cópia de um processo de chamada. geralmente segue a estrutura insira a descrição da imagem aqui

 int cpid = fork( ); if (cpid = = 0) { //child code exit(0); } //parent code wait(cpid); // end 

(para processo filho texto (código), dados, pilha é o mesmo que processo de chamada) processo filho executa código no bloco se.

O EXEC substitui o processo atual pelo código, dados e pilha do novo processo. geralmente segue a estrutura insira a descrição da imagem aqui

 int cpid = fork( ); if (cpid = = 0) { //child code exec(foo); exit(0); } //parent code wait(cpid); // end 

(depois da execução exec unix kernel limpa o texto do processo filho, dados, pilha e preenche o texto / dados relacionados ao processo foo), assim o processo filho é com código diferente (código do foo {não igual ao pai})

Eles são usados ​​juntos para criar um novo processo filho. Primeiro, o fork chamada cria uma cópia do processo atual (o processo filho). Em seguida, exec é chamado de dentro do processo filho para “replace” a cópia do processo pai pelo novo processo.

O processo é algo assim:

 child = fork(); //Fork returns a PID for the parent process, or 0 for the child, or -1 for Fail if (child < 0) { std::cout << "Failed to fork GUI process...Exiting" << std::endl; exit (-1); } else if (child == 0) { // This is the Child Process // Call one of the "exec" functions to create the child process execvp (argv[0], const_cast(argv)); } else { // This is the Parent Process //Continue executing parent process } 

fork () cria uma cópia do processo atual, com a execução no novo filho começando logo após a chamada fork (). Após o fork (), eles são idênticos, exceto pelo valor de retorno da function fork (). (RTFM para mais detalhes.) Os dois processos podem então divergir ainda mais, com um incapaz de interferir com o outro, exceto, possivelmente, através de quaisquer identificadores de arquivos compartilhados.

exec () substitui o processo atual por um novo. Não tem nada a ver com fork (), exceto que um exec () geralmente segue fork () quando o que é desejado é iniciar um processo filho diferente, em vez de replace o atual.

insira a descrição da imagem aqui fork() :

Cria uma cópia do processo em execução. O processo em execução é chamado de processo pai e o processo recém-criado é chamado de processo filho . A maneira de diferenciar os dois é observando o valor retornado:

  1. fork() retorna o identificador do processo (pid) do processo filho no pai

  2. fork() retorna 0 no filho.

exec() :

Inicia um novo processo dentro de um processo. Carrega um novo programa no processo atual, substituindo o existente.

fork() + exec() :

Quando iniciar um novo programa é primeiramente fork() , criando um novo processo, e então exec() (isto é, carregando na memory e executando) o binário do programa que ele deve rodar.

 int main( void ) { int pid = fork(); if ( pid == 0 ) { execvp( "find", argv ); } //Put the parent to sleep for 2 sec,let the child finished executing wait( 2 ); return 0; } 

O principal exemplo para entender o conceito fork() e exec() é o shell , o programa interpretador de comandos que os usuários geralmente executam após efetuar login no sistema. O shell interpreta a primeira palavra da linha de comando como um nome de comando.

Para muitos comandos, os shell forks e o processo filho executam o comando associado ao nome tratando as palavras restantes na linha de comandos como parâmetros para o comando.

O shell permite três tipos de comandos. Primeiro, um comando pode ser um arquivo executável que contém código de object produzido pela compilation do código-fonte (um programa C, por exemplo). Segundo, um comando pode ser um arquivo executável que contém uma seqüência de linhas de comando do shell. Finalmente, um comando pode ser um comando shell interno (ao invés de um arquivo executável ex-> cd , ls etc.)