Qual é o propósito de fork ()?

87

Em muitos programas e páginas de manual do Linux, vi código usando fork(). Por que precisamos usar fork()e qual é o seu propósito?

senfo
fonte
150
Para que todos aqueles filósofos que jantam não morram de fome.
kenj0418

Respostas:

106

fork()é como você cria novos processos no Unix. Ao ligar fork, você está criando uma cópia de seu próprio processo que possui seu próprio espaço de endereço . Isso permite que várias tarefas sejam executadas independentemente umas das outras, como se cada uma tivesse toda a memória da máquina para si.

Aqui estão alguns exemplos de uso de fork:

  1. Seu shell usa forkpara executar os programas que você invoca a partir da linha de comando.
  2. Servidores da Web como o apache usam forkpara criar vários processos de servidor, cada um dos quais lida com solicitações em seu próprio espaço de endereço. Se um morre ou perde memória, os outros não são afetados, por isso funciona como um mecanismo de tolerância a falhas.
  3. O Google Chrome usa forkpara lidar com cada página em um processo separado. Isso impedirá que o código do lado do cliente em uma página desative todo o navegador.
  4. forké usado para gerar processos em alguns programas paralelos (como aqueles escritos usando MPI ). Observe que isso é diferente de usar threads , que não têm seu próprio espaço de endereço e existem dentro de um processo.
  5. Linguagens de script usam forkindiretamente para iniciar processos filho. Por exemplo, toda vez que você usa um comando como subprocess.Popenno Python, você forkprocessa um filho e lê sua saída. Isso permite que os programas funcionem juntos.

O uso típico de forkem um shell pode ter a seguinte aparência:

int child_process_id = fork();
if (child_process_id) {
    // Fork returns a valid pid in the parent process.  Parent executes this.

    // wait for the child process to complete
    waitpid(child_process_id, ...);  // omitted extra args for brevity

    // child process finished!
} else {
    // Fork returns 0 in the child process.  Child executes this.

    // new argv array for the child process
    const char *argv[] = {"arg1", "arg2", "arg3", NULL};

    // now start executing some other program
    exec("/path/to/a/program", argv);
}

O shell gera um processo filho usando exece aguarda sua conclusão, então continua com sua própria execução. Observe que você não precisa usar o fork desta forma. Você sempre pode gerar muitos processos filho, como um programa paralelo pode fazer, e cada um pode executar um programa simultaneamente. Basicamente, sempre que você está criando novos processos em um sistema Unix, você está usando fork(). Para obter o equivalente do Windows, dê uma olhada em CreateProcess.

Se você quiser mais exemplos e uma explicação mais longa, a Wikipedia tem um resumo decente. E aqui estão alguns slides sobre como processos, threads e simultaneidade funcionam em sistemas operacionais modernos.

Todd Gamblin
fonte
Marcador 5: 'frequentemente'? Apenas 'frequentemente'? Quais não o usam, ou sob quais circunstâncias fork () não é usado - isto é, em sistemas que suportam fork ().
Jonathan Leffler
19
Estranhamente, é chamado de CreateProcess () - aqueles caras malucos do Windows :-)
paxdiablo
2
nunca percebi até agora que "o shell usa fork para executar os programas que você invoca a partir da linha de comando"!
Lazer
1
O link do Slides está quebrado
piertoni
1
Todas as respostas dizer que fork()é o caminho para criar um novo processo no UNIX, mas para ser pedante, há pelo menos um outro: posix_spawn().
Davislor
15

fork () é como o Unix cria novos processos. No ponto em que você chamou fork (), seu processo é clonado e dois processos diferentes continuam a execução a partir daí. Um deles, o filho, fará com que fork () retorne 0. O outro, o pai, fará com que fork () retorne o PID (ID do processo) do filho.

Por exemplo, se você digitar o seguinte em um shell, o programa shell chamará fork () e, em seguida, executará o comando que você passou (telnetd, neste caso) no filho, enquanto o pai exibirá o prompt novamente também como uma mensagem indicando o PID do processo em segundo plano.

$ telnetd &

Quanto ao motivo pelo qual você cria novos processos, é assim que seu sistema operacional pode fazer muitas coisas ao mesmo tempo. É por isso que você pode executar um programa e, enquanto ele está em execução, alternar para outra janela e fazer outra coisa.

Daniel C. Sobral
fonte
@varDumper Boa captura!
Daniel C. Sobral
9

fork () é usado para criar o processo filho. Quando uma função fork () é chamada, um novo processo é gerado e a chamada da função fork () retorna um valor diferente para o filho e o pai.

Se o valor de retorno for 0, você sabe que é o processo filho e se o valor de retorno for um número (que é o id do processo filho), você sabe que é o pai. (e se for um número negativo, a bifurcação falhou e nenhum processo filho foi criado)

http://www.yolinux.com/TUTORIALS/ForkExecProcesses.html

Wadih M.
fonte
1
A menos que o valor de retorno seja -1, caso em que fork () falhou.
Jonathan Leffler
8

fork () é basicamente usado para criar um processo filho para o processo no qual você está chamando esta função. Sempre que você chama um fork (), ele retorna um zero para o id filho.

pid=fork()
if pid==0
//this is the child process
else if pid!=0
//this is the parent process

com isso, você pode fornecer ações diferentes para o pai e a criança e fazer uso do recurso multithreading.

Nave
fonte
6

fork () criará um novo processo filho idêntico ao pai. Portanto, tudo o que você executar no código depois disso será executado por ambos os processos - muito útil se você tiver, por exemplo, um servidor e quiser lidar com várias solicitações.

cabeça de nuvem
fonte
por que você cria uma criança que é idêntica ao pai qual é a utilidade?
1
É como construir um exército contra um único soldado. Você bifurca para que seu programa possa lidar com mais solicitações ao mesmo tempo, em vez de uma por uma.
cloudhead de
fork () retorna 0 no filho e o pid do filho no pai. O filho pode então usar uma chamada como exec () para substituir seu estado por um novo programa. É assim que os programas são iniciados.
Todd Gamblin
Os processos são quase idênticos, mas existem muitas diferenças sutis. As diferenças gritantes são o PID atual e o PID pai. Existem problemas relacionados a bloqueios mantidos e semáforos suspensos. A página de manual fork () para POSIX lista 25 diferenças entre o pai e o filho.
Jonathan Leffler
2
@kar: Uma vez que você tenha dois processos, eles podem continuar separados e um deles pode se substituir (exex ()) por outro programa completamente.
Vatine
4

Você provavelmente não precisa usar fork na programação do dia-a-dia se estiver escrevendo aplicativos.

Mesmo se você quiser que seu programa inicie outro programa para fazer alguma tarefa, existem outras interfaces mais simples que usam fork nos bastidores, como "sistema" em C e perl.

Por exemplo, se você quiser que seu aplicativo inicie outro programa, como o bc, para fazer alguns cálculos para você, pode usar 'sistema' para executá-lo. O sistema faz uma 'bifurcação' para criar um novo processo e, em seguida, um 'exec' para transformar esse processo em bc. Assim que o bc for concluído, o sistema retornará o controle ao programa.

Você também pode executar outros programas de forma assíncrona, mas não me lembro como.

Se você estiver escrevendo servidores, shells, vírus ou sistemas operacionais, é mais provável que queira usar o fork.

Alex Brown
fonte
Obrigado por system(). Eu estava lendo sobre fork()porque quero que meu código C execute um script Python.
Bean Taxi
4

Fork cria novos processos. Sem o fork, você teria um sistema unix que só poderia executar o init.

Stephen
fonte
4

A chamada do sistema fork () é usada para criar processos. Não leva argumentos e retorna um ID de processo. O objetivo de fork () é criar um novo processo, que se torna o processo filho do chamador. Depois que um novo processo filho é criado, ambos os processos executarão a próxima instrução após a chamada de sistema fork (). Portanto, temos que distinguir o pai do filho. Isso pode ser feito testando o valor retornado de fork ():

Se fork () retornar um valor negativo, a criação de um processo filho não foi bem-sucedida. fork () retorna um zero para o processo filho recém-criado. fork () retorna um valor positivo, o ID do processo filho, para o pai. O ID do processo retornado é do tipo pid_t definido em sys / types.h. Normalmente, o ID do processo é um número inteiro. Além disso, um processo pode usar a função getpid () para recuperar o ID do processo atribuído a este processo. Portanto, após a chamada do sistema para fork (), um teste simples pode dizer qual processo é filho. Observe que o Unix fará uma cópia exata do espaço de endereço do pai e a dará ao filho. Portanto, os processos pai e filho têm espaços de endereço separados.

Vamos entendê-lo com um exemplo para esclarecer os pontos acima. Este exemplo não distingue os processos pai e filho.

#include  <stdio.h>
#include  <string.h>
#include  <sys/types.h>

#define   MAX_COUNT  200
#define   BUF_SIZE   100

void  main(void)
{
     pid_t  pid;
     int    i;
     char   buf[BUF_SIZE];

     fork();
     pid = getpid();
     for (i = 1; i <= MAX_COUNT; i++) {
          sprintf(buf, "This line is from pid %d, value = %d\n", pid, i);
          write(1, buf, strlen(buf));
     } 
}

Suponha que o programa acima seja executado até o ponto da chamada para fork ().

Se a chamada para fork () for executada com sucesso, o Unix fará duas cópias idênticas dos espaços de endereço, uma para o pai e outra para o filho. Ambos os processos iniciarão sua execução na próxima instrução após a chamada fork (). Neste caso, ambos os processos iniciarão sua execução na atribuição

pid = .....;

Ambos os processos iniciam sua execução logo após a chamada do sistema fork (). Como os dois processos têm espaços de endereço idênticos, mas separados, essas variáveis ​​inicializadas antes da chamada fork () têm os mesmos valores em ambos os espaços de endereço. Como cada processo tem seu próprio espaço de endereço, quaisquer modificações serão independentes das outras. Em outras palavras, se o pai alterar o valor de sua variável, a modificação afetará apenas a variável no espaço de endereço do processo pai. Outros espaços de endereço criados por chamadas fork () não serão afetados, embora tenham nomes de variáveis ​​idênticos.

Qual é a razão de usar write em vez de printf? É porque printf () é "armazenado em buffer", o que significa que printf () agrupará a saída de um processo. Ao armazenar em buffer a saída do processo pai, o filho também pode usar printf para imprimir algumas informações, que também serão armazenadas em buffer. Como resultado, como a saída não será enviada para a tela imediatamente, você pode não obter a ordem correta do resultado esperado. Pior, a saída dos dois processos pode ser misturada de maneiras estranhas. Para superar esse problema, você pode considerar o uso de gravação "sem buffer".

Se você executar este programa, poderá ver o seguinte na tela:

................
This line is from pid 3456, value 13
This line is from pid 3456, value 14
     ................
This line is from pid 3456, value 20
This line is from pid 4617, value 100
This line is from pid 4617, value 101
     ................
This line is from pid 3456, value 21
This line is from pid 3456, value 22
     ................

O ID de processo 3456 pode ser atribuído ao pai ou à criança. Devido ao fato de que esses processos são executados simultaneamente, suas linhas de saída são misturadas de uma forma bastante imprevisível. Além disso, a ordem dessas linhas é determinada pelo escalonador da CPU. Portanto, se você executar este programa novamente, poderá obter um resultado totalmente diferente.

cpp-coder
fonte
3
Em vez de copiar e colar o texto, você poderia comentar um link: csl.mtu.edu/cs4411.ck/www/NOTES/process/fork/create.html
chaitanya lakkundi
3

O multiprocessamento é fundamental para a computação. Por exemplo, seu IE ou Firefox pode criar um processo para baixar um arquivo para você enquanto você ainda está navegando na Internet. Ou, enquanto estiver imprimindo um documento em um processador de texto, você ainda pode ver páginas diferentes e ainda fazer algumas edições com elas.

não polaridade
fonte
3

Fork () é usado para criar novos processos à medida que cada corpo escreve.

Aqui está o meu código que cria processos na forma de árvore binária ... Ele pedirá para verificar o número de níveis até os quais você deseja criar processos na árvore binária

#include<unistd.h> 
#include<fcntl.h> 
#include<stdlib.h>   
int main() 
{
int t1,t2,p,i,n,ab;
p=getpid();                
printf("enter the number of levels\n");fflush(stdout);
scanf("%d",&n);                
printf("root %d\n",p);fflush(stdout);
for(i=1;i<n;i++)    
{        
    t1=fork();

    if(t1!=0)
        t2=fork();        
    if(t1!=0 && t2!=0)        
        break;            
    printf("child pid %d   parent pid %d\n",getpid(),getppid());fflush(stdout);
}   
    waitpid(t1,&ab,0);
    waitpid(t2,&ab,0);
return 0;
}

RESULTADO

  enter the number of levels
  3
  root 20665
  child pid 20670   parent pid 20665
  child pid 20669   parent pid 20665
  child pid 20672   parent pid 20670
  child pid 20671   parent pid 20670
  child pid 20674   parent pid 20669
  child pid 20673   parent pid 20669
Anil Kumar Arya
fonte
2

Primeiro, é preciso entender o que é chamada de sistema fork (). Deixe-me explicar

  1. A chamada do sistema fork () cria a duplicata exata do processo pai, faz a duplicata da pilha pai, heap, dados inicializados, dados não inicializados e compartilha o código em modo somente leitura com o processo pai.

  2. A chamada do sistema fork copia a memória na base de cópia na gravação, significa que a criança faz na página de memória virtual quando há necessidade de cópia.

Objetivo do fork ():

  1. Fork () pode ser usado no local onde há divisão de trabalho, como um servidor tem que lidar com vários clientes, então o pai tem que aceitar a conexão regularmente, então o servidor bifurca para cada cliente para executar leitura-gravação.
Sandeep_black
fonte
1

fork()é usado para gerar um processo filho. Normalmente é usado em situações semelhantes como threading, mas há diferenças. Ao contrário dos threads, fork()cria processos separados inteiros, o que significa que o filho e o pai, embora sejam cópias diretas um do outro no ponto em que fork()é chamado, eles são completamente separados, nenhum pode acessar o espaço de memória do outro (sem ir para os problemas normais você vai acessar a memória de outro programa).

fork()ainda é usado por alguns aplicativos de servidor, principalmente aqueles executados como root em uma máquina * NIX que elimina as permissões antes de processar as solicitações do usuário. Ainda existem alguns outros casos de uso, mas a maioria das pessoas mudou para multithreading agora.

Matthew Scharley
fonte
2
Não entendo a percepção de que "a maioria das pessoas" mudou para o multithreading. Os processos vieram para ficar, assim como os threads. Ninguém "mudou" de qualquer um. Na programação paralela, os códigos maiores e mais concorrentes são programas multiprocessos de memória distribuída (por exemplo, MapReduce e MPI). Ainda assim, a maioria das pessoas optaria por OpenMP ou algum paradigma de memória compartilhada para uma máquina multicore, e as GPUs estão usando threads atualmente, mas há muito além disso. Aposto, porém, que mais codificadores neste site encontram paralelismo de processo no lado do servidor do que qualquer coisa multithread.
Todd Gamblin
1

A razão por trás de fork () versus apenas ter uma função exec () para iniciar um novo processo é explicada em uma resposta a uma pergunta semelhante na troca de pilha do Unix .

Essencialmente, como o fork copia o processo atual, todas as várias opções possíveis para um processo são estabelecidas por padrão, de forma que o programador não as tenha fornecido.

No sistema operacional Windows, por outro lado, os programadores precisam usar a função CreateProcess, que é MUITO mais complicada e requer o preenchimento de uma estrutura multifacetada para definir os parâmetros do novo processo.

Portanto, para resumir, a razão para a bifurcação (versus execução) é a simplicidade na criação de novos processos.

Tyler Durden
fonte
0

Utilização de chamada de sistema Fork () para criar um processo filho. É uma duplicata exata do processo pai. Fork copia seção de pilha, seção de heap, seção de dados, variável de ambiente, argumentos de linha de comando do pai.

consulte: http://man7.org/linux/man-pages/man2/fork.2.html


fonte
0

A função fork () é usada para criar um novo processo, duplicando o processo existente a partir do qual é chamado. O processo existente a partir do qual essa função é chamada se torna o processo pai e o processo recém-criado se torna o processo filho. Como já foi dito, o filho é uma cópia duplicada do pai, mas há algumas exceções.

  • A criança tem um PID exclusivo como qualquer outro processo em execução no sistema operacional.

  • O filho possui um ID de processo pai que é igual ao PID do
    processo que o criou.

  • A utilização de recursos e os contadores de tempo da CPU são redefinidos para zero no processo filho.

  • O conjunto de sinais pendentes na criança está vazio.

  • O filho não herda nenhum cronômetro de seu pai

Exemplo:

    #include <unistd.h>
    #include <sys/types.h>
    #include <errno.h>
    #include <stdio.h>
    #include <sys/wait.h>
    #include <stdlib.h>

    int var_glb; /* A global variable*/

int main(void)
{
    pid_t childPID;
    int var_lcl = 0;

    childPID = fork();

    if(childPID >= 0) // fork was successful
    {
        if(childPID == 0) // child process
        {
            var_lcl++;
            var_glb++;
            printf("\n Child Process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
        }
        else //Parent process
        {
            var_lcl = 10;
            var_glb = 20;
            printf("\n Parent process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
        }
    }
    else // fork failed
    {
        printf("\n Fork failed, quitting!!!!!!\n");
        return 1;
    }

    return 0;
}

Agora, quando o código acima é compilado e executado:

$ ./fork

Parent process :: var_lcl = [10], var_glb[20]

Child Process :: var_lcl = [1], var_glb[1]
Usman
fonte