Diferenças entre fork e exec


199

Quais são as diferenças entre forke exec?


3
Um bom, resumo detalhado do fork, exec, e outras funções de controle de processo está em yolinux.com/TUTORIALS/ForkExecProcesses.html
Jonathan Fingland

9
@ Justin, porque queremos que o SO se torne o lugar certo para perguntas sobre programação.
precisa saber é

4
@ Polaris878: ah, agora! : D
Janusz Lenar

então forké basicamente a clonagem: O
Sebastian Hojas

Respostas:


364

O uso forke execexemplifica o espírito do UNIX, pois fornece uma maneira muito simples de iniciar novos processos.

A forkchamada basicamente cria uma duplicata do processo atual, idêntica em quase todos os aspectos. Nem tudo é copiado (por exemplo, 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 possui 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 de fork- o filho recebe 0, o pai recebe o PID do filho. Isso é tudo, é claro, supondo que a forkchamada funcione - caso contrário, nenhum filho será criado e o pai receberá um código de erro.

A execchamada é 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.

Portanto, forke execgeralmente são usados ​​em sequência para executar um novo programa como filho de um processo atual. Os shells normalmente fazem isso sempre que você tenta executar um programa como find- o shell bifurca, então o filho carrega o findprograma na memória, configurando todos os argumentos da linha de comando, E / S padrão e assim por diante.

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

Da mesma forma, os programas que sabem que estão concluídos e que desejam executar outro programa não precisam fork , em execseguida, waitpara a criança. Eles podem simplesmente carregar a criança diretamente em seu espaço de processo.

Algumas implementações do UNIX têm um otimizado forkque usa o que chamam de cópia na gravação. Este é um truque para atrasar a cópia do espaço do processo forkaté que o programa tente alterar algo nesse espaço. Isso é útil para os programas que usam apenasfork e não execporque não precisam copiar um espaço de processo inteiro.

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

Observe que há uma família inteira de execchamadas ( execl, execle,execve e assim por diante), mas execno contexto aqui significa qualquer um deles.

O diagrama a seguir ilustra a fork/execoperação típica em que o bashshell é usado para listar um diretório com o lscomando:

+--------+
| 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

52

fork()divide o processo atual em dois processos. Ou, em outras palavras, seu programa linear fácil de pensar se transforma em dois programas separados, executando um único 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 meio que 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 memória do processo que o criou, incluindo o início de onde a fork()chamada parou. A única diferença é afork() código de retorno para informar se você é o pai ou a criança. Se você é o pai, o valor de retorno é o ID do filho.

execé um pouco mais fácil de entender, basta dizer execpara 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 @Steve Hawkins diz, execpode ser usado depois de você forkexecutar no processo atual o executável de destino.


6
há também a condição quando pid < 0ea fork()chamada falhou
Jonathan Fingland

3
Isso não me surpreende :-) Um pedaço de código sendo executado por dois processos acontece toda vez que uma biblioteca compartilhada ou DLL está sendo usada.
paxdiablo

31

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

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

.

Para executar um programa, primeiro solicita-se ao kernel que crie um novo processo , que é um ambiente no qual um programa é executado. (também da 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 esses termos forem novos para você, convém voltar e revisar a Seção 1.1.2. Se você estiver pronto para prosseguir agora, resumiremos a distinção em uma frase: Um processo é um ambiente de execução que consiste em segmentos de instrução, dados do usuário e dados do sistema, além de muitos outros recursos adquiridos em tempo de execução , enquanto um programa é um arquivo que contém instruções e dados usados ​​para inicializar a instrução e os segmentos de dados do usuário de um processo. (de 5,3exec Chamadas do sistema)

Depois de entender a distinção entre um programa e um processo, o comportamento de fork()eexec() função podem ser resumidos como:

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

(esta é essencialmente uma versão simplificada 'for dummies' da resposta muito mais detalhada de paxdiablo )


29

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 texto do processo filho (código), dados, a pilha é igual ao processo de chamada) o processo filho executa o código no bloco if.

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

(após a chamada exec, o unix kernel limpa o texto do processo filho, dados, pilha e preenchimento com texto / dados relacionados ao processo foo), portanto, o processo filho fica com um código diferente (o código foo {não é o mesmo do pai})


1
É um pouco não relacionado à pergunta, mas esse código acima não causa uma condição de corrida se o processo filho terminar seu código primeiro? Nesse caso, o processo pai ficaria para sempre esperando que a criança se encerrasse, certo?
stdout

7

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

O processo é mais ou menos 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<char**>(argv));
} else {                       // This is the Parent Process
    //Continue executing parent process
}

2
Na 7ª linha, é mencionado que a função exec () cria o processo filho. Realmente é porque fork () já criou o processo filho e a chamada exec () substitui o programa do novo processo recém-criado
cbinder

4

fork () cria uma cópia do processo atual, com a execução no novo filho, iniciando logo após a chamada fork (). Após o fork (), eles são idênticos, exceto pelo valor de retorno da função fork (). (RTFM para mais detalhes.) Os dois processos podem divergir ainda mais, com um incapaz de interferir no outro, exceto possivelmente por meio de qualquer identificador de arquivo compartilhado.

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 se quer é iniciar um processo filho diferente, em vez de substituir o atual.


3

A principal diferença entre fork()e exec()é que,

A fork()chamada do sistema cria um clone do programa em execução no momento. O programa original continua a execução com a próxima linha de código após a chamada da função fork (). O clone também inicia a execução na próxima linha de código. Veja o código a seguir obtido em http://timmurphy.org/2014/04/26/using-fork-in-cc-a-minimum-working-example/

#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
    printf("--beginning of program\n");
    int counter = 0;
    pid_t pid = fork();
    if (pid == 0)
    {
        // child process
        int i = 0;
        for (; i < 5; ++i)
        {
            printf("child process: counter=%d\n", ++counter);
        }
    }
    else if (pid > 0)
    {
        // parent process
        int j = 0;
        for (; j < 5; ++j)
        {
            printf("parent process: counter=%d\n", ++counter);
        }
    }
    else
    {
        // fork failed
        printf("fork() failed!\n");
        return 1;
    }
    printf("--end of program--\n");
    return 0;
}

Este programa declara uma variável de contador, definida como zero, antes de fork()ing. Após a chamada da bifurcação, temos dois processos em execução em paralelo, ambos incrementando sua própria versão do contador. Cada processo será executado para conclusão e saída. Como os processos são executados em paralelo, não temos como saber qual será o primeiro a terminar. A execução deste programa imprimirá algo semelhante ao mostrado abaixo, embora os resultados possam variar de uma execução para a seguinte.

--beginning of program
parent process: counter=1
parent process: counter=2
parent process: counter=3
child process: counter=1
parent process: counter=4
child process: counter=2
parent process: counter=5
child process: counter=3
--end of program--
child process: counter=4
child process: counter=5
--end of program--

A exec()família de chamadas do sistema substitui o código atualmente em execução de um processo por outro pedaço de código. O processo mantém seu PID, mas se torna um novo programa. Por exemplo, considere o seguinte código:

#include <stdio.h> 
#include <unistd.h> 
main() {
 char program[80],*args[3];
 int i; 
printf("Ready to exec()...\n"); 
strcpy(program,"date"); 
args[0]="date"; 
args[1]="-u"; 
args[2]=NULL; 
i=execvp(program,args); 
printf("i=%d ... did it work?\n",i); 
} 

Este programa chama a execvp()função para substituir seu código pelo programa de data. Se o código estiver armazenado em um arquivo chamado exec1.c, sua execução produzirá a seguinte saída:

Ready to exec()... 
Tue Jul 15 20:17:53 UTC 2008 

O programa gera a linha “Pronto para exec (). . . E depois de chamar a função execvp (), substitui seu código pelo programa date. Observe que a linha -. . . funcionou ”não é exibido, porque nesse momento o código foi substituído. Em vez disso, vemos a saída da execução de "data -u".


1

insira a descrição da imagem aquifork():

Ele 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():

Ao iniciar um novo programa é primeiro fork()criar um novo processo e, em seguida exec()(carregar na memória e executar) o programa binário que ele deve executar.

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;
}

0

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

Para muitos comandos, os garfos do shell e o processo filho executam o comando associado ao nome, tratando as palavras restantes na linha de comando 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 o código de objeto produzido pela compilação do código-fonte (um programa C, por exemplo). Segundo, um comando pode ser um arquivo executável que contém uma sequência de linhas de comando do shell. Finalmente, um comando pode ser um comando interno do shell (em vez de um arquivo executável ex-> cd , ls etc.)

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.