Quais são as diferenças entre fork
e exec
?
fork
é basicamente a clonagem: O
Quais são as diferenças entre fork
e exec
?
fork
é basicamente a clonagem: O
Respostas:
O uso fork
e exec
exemplifica o espírito do UNIX, pois fornece uma maneira muito simples de iniciar novos processos.
A fork
chamada 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 fork
chamada funcione - caso contrário, nenhum filho será criado e o pai receberá um código de erro.
A exec
chamada é 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, fork
e exec
geralmente 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 find
programa 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 fork
si mesmo, sem exec
se, 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 fork
uma 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 exec
seguida, wait
para 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 fork
que usa o que chamam de cópia na gravação. Este é um truque para atrasar a cópia do espaço do processo fork
até que o programa tente alterar algo nesse espaço. Isso é útil para os programas que usam apenasfork
e não exec
porque 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 exec
chamadas ( execl
, execle
,execve
e assim por diante), mas exec
no contexto aqui significa qualquer um deles.
O diagrama a seguir ilustra a fork/exec
operação típica em que o bash
shell é usado para listar um diretório com o ls
comando:
+--------+
| 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 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 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 @Steve Hawkins diz, exec
pode ser usado depois de você fork
executar no processo atual o executável de destino.
pid < 0
ea fork()
chamada falhou
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,3
exec
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 atualexec()
substitui o programa no processo atual por outro programa(esta é essencialmente uma versão simplificada 'for dummies' da resposta muito mais detalhada de paxdiablo )
Fork cria uma cópia de um processo de chamada. geralmente segue a estrutura
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
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})
Eles são usados juntos para criar um novo processo filho. Primeiro, a chamada fork
cria 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
}
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.
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".
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:
fork()
retorna o identificador do processo (pid) do processo filho no pai
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;
}
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.)