¿Cuál es el propósito de fork ()?


87

En muchos programas y páginas de manual de Linux, he visto el uso de código fork(). ¿Por qué necesitamos usar fork()y cuál es su propósito?


150
Para que todos esos filósofos de la cena no se mueran de hambre.
kenj0418

Respuestas:


106

fork()así es como se crean nuevos procesos en Unix. Cuando llama fork, está creando una copia de su propio proceso que tiene su propio espacio de direcciones . Esto permite que varias tareas se ejecuten independientemente unas de otras como si cada una tuviera la memoria completa de la máquina para sí misma.

Estos son algunos ejemplos de usos de fork:

  1. Su shell utiliza forkpara ejecutar los programas que invoca desde la línea de comandos.
  2. Los servidores web como apache se utilizan forkpara crear múltiples procesos de servidor, cada uno de los cuales maneja las solicitudes en su propio espacio de direcciones. Si uno muere o pierde memoria, otros no se ven afectados, por lo que funciona como un mecanismo de tolerancia a fallas.
  3. Google Chrome utiliza forkpara manejar cada página dentro de un proceso separado. Esto evitará que el código del lado del cliente en una página detenga todo su navegador.
  4. forkse utiliza para generar procesos en algunos programas paralelos (como los escritos con MPI ). Tenga en cuenta que esto es diferente al uso de subprocesos , que no tienen su propio espacio de direcciones y existen dentro de un proceso.
  5. Los lenguajes de secuencias de comandos se utilizan forkindirectamente para iniciar procesos secundarios. Por ejemplo, cada vez que usa un comando como subprocess.Popenen Python, forkun hijo procesa y lee su salida. Esto permite que los programas funcionen juntos.

El uso típico de forken un shell podría verse así:

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

El shell genera un proceso hijo execy espera a que se complete, luego continúa con su propia ejecución. Tenga en cuenta que no tiene que usar fork de esta manera. Siempre puede generar muchos procesos secundarios, como podría hacer un programa paralelo, y cada uno puede ejecutar un programa al mismo tiempo. Básicamente, cada vez que crea nuevos procesos en un sistema Unix, está usando fork(). Para el equivalente de Windows, eche un vistazo a CreateProcess.

Si desea más ejemplos y una explicación más extensa, Wikipedia tiene un resumen decente. Y aquí hay algunas diapositivas sobre cómo funcionan los procesos, los subprocesos y la concurrencia en los sistemas operativos modernos.


Bala 5: ¿'a menudo'? ¿Solo 'a menudo'? Cuáles no lo usan, o bajo qué circunstancias no se usa fork (), es decir, en sistemas que admiten fork ().
Jonathan Leffler

19
Por extraño que parezca, se llama CreateProcess () - esos locos de Windows :-)
paxdiablo

2
¡Nunca me di cuenta hasta ahora de que "shell usa fork para ejecutar los programas que invoca desde la línea de comandos"!
Lazer

1
Enlace de diapositivas se rompe
piertoni

1
Todas las respuestas dicen que fork()es la manera de crear un nuevo proceso en UNIX, pero para ser pedante, hay al menos otro: posix_spawn().
Davislor

15

fork () es cómo Unix crea nuevos procesos. En el punto en el que llamó a fork (), su proceso se clona y dos procesos diferentes continúan la ejecución desde allí. Uno de ellos, el hijo, hará que fork () devuelva 0. El otro, el padre, hará que fork () devuelva el PID (ID de proceso) del hijo.

Por ejemplo, si escribe lo siguiente en un shell, el programa de shell llamará a fork () y luego ejecutará el comando que pasó (telnetd, en este caso) en el hijo, mientras que el padre mostrará el indicador nuevamente, también como un mensaje que indica el PID del proceso en segundo plano.

$ telnetd &

En cuanto a la razón por la que crea nuevos procesos, así es como su sistema operativo puede hacer muchas cosas al mismo tiempo. Es por eso que puede ejecutar un programa y, mientras se está ejecutando, cambiar a otra ventana y hacer otra cosa.


@varDumper ¡Buena captura!
Daniel C. Sobral

9

fork () se usa para crear un proceso hijo. Cuando se llama a una función fork (), se generará un nuevo proceso y la llamada a la función fork () devolverá un valor diferente para el hijo y el padre.

Si el valor de retorno es 0, sabe que es el proceso secundario y si el valor de retorno es un número (que resulta ser el ID del proceso secundario), sabe que es el padre. (y si es un número negativo, la bifurcación falló y no se creó ningún proceso hijo)

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


1
A menos que el valor de retorno sea -1, en cuyo caso el fork () falló.
Jonathan Leffler

8

fork () se usa básicamente para crear un proceso hijo para el proceso en el que está llamando a esta función. Siempre que llame a un fork (), devuelve un cero para la identificación del niño.

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

con esto, puede proporcionar diferentes acciones para el padre y el hijo y hacer uso de la función de subprocesos múltiples.


6

fork () creará un nuevo proceso hijo idéntico al padre. Entonces, todo lo que ejecute en el código después de eso será ejecutado por ambos procesos, muy útil si tiene, por ejemplo, un servidor y desea manejar múltiples solicitudes.


¿Por qué creas un hijo que es idéntico al padre? ¿Cuál es el uso?

1
Es como construir un ejército contra un solo soldado. Bifurca para que tu programa pueda manejar más solicitudes al mismo tiempo, en lugar de una por una.
cloudhead

fork () devuelve 0 en el hijo y el pid del hijo en el padre. Luego, el niño puede usar una llamada como exec () para reemplazar su estado con un nuevo programa. Así es como se lanzan los programas.
Todd Gamblin

Los procesos son casi idénticos, pero hay muchas diferencias sutiles. Las diferencias evidentes son el PID actual y el PID principal. Hay problemas relacionados con los bloqueos retenidos y los semáforos retenidos. La página de manual fork () para POSIX enumera 25 diferencias entre el padre y el hijo.
Jonathan Leffler

2
@kar: Una vez que tenga dos procesos, pueden continuar por separado desde allí y uno de ellos podría reemplazarse a sí mismo (exex ()) con otro programa por completo.
Vatine

4

Probablemente no necesite usar fork en la programación diaria si está escribiendo aplicaciones.

Incluso si desea que su programa inicie otro programa para realizar alguna tarea, existen otras interfaces más simples que usan fork entre bastidores, como "system" en C y perl.

Por ejemplo, si desea que su aplicación inicie otro programa como bc para hacer algunos cálculos por usted, puede usar 'system' para ejecutarlo. El sistema hace una 'bifurcación' para crear un nuevo proceso, luego un 'ejecutivo' para convertir ese proceso en bc. Una vez que bc se completa, el sistema devuelve el control a su programa.

También puede ejecutar otros programas de forma asincrónica, pero no recuerdo cómo.

Si está escribiendo servidores, shells, virus o sistemas operativos, es más probable que desee utilizar fork.


Gracias por usted system(). Estaba leyendo fork()porque quiero que mi código C ejecute un script de Python.
Bean Taxi

4

Fork crea nuevos procesos. Sin bifurcación, tendría un sistema Unix que solo podría ejecutar init.


4

La llamada al sistema fork () se usa para crear procesos. No toma argumentos y devuelve un ID de proceso. El propósito de fork () es crear un nuevo proceso, que se convierte en el proceso hijo de la persona que llama. Después de que se crea un nuevo proceso hijo, ambos procesos ejecutarán la siguiente instrucción después de la llamada al sistema fork (). Por tanto, tenemos que distinguir al padre del hijo. Esto se puede hacer probando el valor devuelto de fork ():

Si fork () devuelve un valor negativo, la creación de un proceso hijo no tuvo éxito. fork () devuelve un cero al proceso hijo recién creado. fork () devuelve un valor positivo, el ID de proceso del proceso hijo, al padre. El ID de proceso devuelto es del tipo pid_t definido en sys / types.h. Normalmente, el ID del proceso es un número entero. Además, un proceso puede usar la función getpid () para recuperar el ID de proceso asignado a este proceso. Por lo tanto, después de la llamada del sistema a fork (), una prueba simple puede decir qué proceso es el hijo. Tenga en cuenta que Unix hará una copia exacta del espacio de direcciones de los padres y se lo dará al niño. Por lo tanto, los procesos padre e hijo tienen espacios de direcciones separados.

Entendamos con un ejemplo para aclarar los puntos anteriores. Este ejemplo no distingue los procesos padre e hijo.

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

Suponga que el programa anterior se ejecuta hasta el punto de la llamada a fork ().

Si la llamada a fork () se ejecuta con éxito, Unix hará dos copias idénticas de espacios de direcciones, una para el padre y la otra para el hijo. Ambos procesos comenzarán su ejecución en la siguiente instrucción que sigue a la llamada a fork (). En este caso, ambos procesos iniciarán su ejecución en la asignación

pid = .....;

Ambos procesos comienzan su ejecución justo después de que el sistema llame a fork (). Dado que ambos procesos tienen espacios de direcciones idénticos pero separados, esas variables inicializadas antes de la llamada a fork () tienen los mismos valores en ambos espacios de direcciones. Dado que cada proceso tiene su propio espacio de direcciones, cualquier modificación será independiente de las demás. En otras palabras, si el padre cambia el valor de su variable, la modificación solo afectará a la variable en el espacio de direcciones del proceso padre. Otros espacios de direcciones creados por llamadas a fork () no se verán afectados aunque tengan nombres de variables idénticos.

¿Cuál es la razón de usar write en lugar de printf? Es porque printf () está "almacenado", lo que significa que printf () agrupará la salida de un proceso. Mientras almacena en búfer la salida del proceso principal, el hijo también puede usar printf para imprimir cierta información, que también se almacenará en búfer. Como resultado, dado que la salida no se enviará a la pantalla inmediatamente, es posible que no obtenga el orden correcto del resultado esperado. Peor aún, la salida de los dos procesos puede mezclarse de formas extrañas. Para superar este problema, puede considerar utilizar la escritura "sin búfer".

Si ejecuta este programa, es posible que vea lo siguiente en la pantalla:

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

El ID de proceso 3456 puede ser el asignado al padre o al hijo. Debido al hecho de que estos procesos se ejecutan al mismo tiempo, sus líneas de salida se entremezclan de una manera bastante impredecible. Además, el orden de estas líneas lo determina el planificador de la CPU. Por lo tanto, si ejecuta este programa nuevamente, puede obtener un resultado totalmente diferente.


3
En lugar de copiar y pegar el texto, podría haber comentado un enlace: csl.mtu.edu/cs4411.ck/www/NOTES/process/fork/create.html
chaitanya lakkundi

3

El multiprocesamiento es fundamental para la informática. Por ejemplo, su IE o Firefox puede crear un proceso para descargar un archivo mientras todavía está navegando por Internet. O, mientras imprime un documento en un procesador de texto, aún puede ver diferentes páginas y aún hacer algunas modificaciones con él.


3

Fork () se usa para crear nuevos procesos como todo el mundo ha escrito.

Aquí está mi código que crea procesos en forma de árbol binario ... Le pedirá escanear el número de niveles hasta los que desea crear procesos en árbol binario

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

SALIDA

  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

2

Primero hay que entender qué es la llamada al sistema fork (). Dejame explicar

  1. La llamada al sistema fork () crea el duplicado exacto del proceso principal. Hace el duplicado de la pila principal, el montón, los datos inicializados, los datos no inicializados y comparte el código en modo de solo lectura con el proceso principal.

  2. La llamada al sistema de la bifurcación copia la memoria sobre la base de copia sobre escritura, significa que el niño hace en la página de memoria virtual cuando hay un requisito de copia.

Ahora el propósito de fork ():

  1. Fork () se puede usar en el lugar donde hay una división del trabajo, como un servidor tiene que manejar varios clientes, por lo que el padre tiene que aceptar la conexión de forma regular, por lo que el servidor se bifurca para que cada cliente realice lectura y escritura.

1

fork()se utiliza para generar un proceso hijo. Por lo general, se usa en situaciones similares a las de los subprocesos, pero existen diferencias. A diferencia de los hilos, fork()crea procesos completamente separados, lo que significa que el niño y el padre, aunque son copias directas el uno del otro en el punto que fork()se llama, están completamente separados, ninguno puede acceder al espacio de memoria del otro (sin pasar a los problemas normales vas a acceder a la memoria de otro programa).

fork()todavía lo utilizan algunas aplicaciones de servidor, principalmente las que se ejecutan como root en una máquina * NIX que eliminan los permisos antes de procesar las solicitudes de los usuarios. Todavía hay algunos otros casos de uso, pero la mayoría de las personas se han trasladado ahora al subproceso múltiple.


2
No entiendo la percepción de que "la mayoría de la gente" se ha pasado al subproceso múltiple. Los procesos llegaron para quedarse, al igual que los hilos. Nadie se ha "movido" de ninguno de los dos. En la programación paralela, los códigos más grandes y concurrentes son programas multiproceso de memoria distribuida (por ejemplo, MapReduce y MPI). Aún así, la mayoría de la gente optaría por OpenMP o algún paradigma de memoria compartida para una máquina multinúcleo, y las GPU están usando subprocesos en estos días, pero hay mucho más allá de eso. Sin embargo, apuesto a que más codificadores en este sitio encuentran paralelismo de procesos en el lado del servidor que cualquier otro multiproceso.
Todd Gamblin

1

La razón de ser de fork () versus simplemente tener una función exec () para iniciar un nuevo proceso se explica en una respuesta a una pregunta similar en el intercambio de pila de Unix .

Esencialmente, dado que fork copia el proceso actual, todas las opciones posibles para un proceso se establecen de forma predeterminada, por lo que el programador no tiene que proporcionarlas.

En el sistema operativo Windows, por el contrario, los programadores tienen que usar la función CreateProcess que es MUCHO más complicada y requiere poblar una estructura múltiple para definir los parámetros del nuevo proceso.

Entonces, en resumen, la razón para bifurcar (versus ejecutar) es la simplicidad en la creación de nuevos procesos.


0

Uso de la llamada al sistema Fork () para crear un proceso hijo. Es un duplicado exacto del proceso principal. La bifurcación copia la sección de pila, la sección de montón, la sección de datos, la variable de entorno, los argumentos de la línea de comandos del padre.

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


0

La función fork () se utiliza para crear un nuevo proceso duplicando el proceso existente desde el que se llama. El proceso existente desde el que se llama a esta función se convierte en el proceso padre y el proceso recién creado se convierte en el proceso hijo. Como ya se indicó, el niño es una copia duplicada del padre, pero hay algunas excepciones.

  • El niño tiene un PID único como cualquier otro proceso que se ejecute en el sistema operativo.

  • El niño tiene un ID de proceso principal que es el mismo que el PID del
    proceso que lo creó.

  • Los contadores de tiempo de CPU y de utilización de recursos se ponen a cero en el proceso hijo.

  • El conjunto de señales pendientes en el niño está vacío.

  • El niño no hereda ningún temporizador de su padre

Ejemplo:

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

Ahora, cuando se compila y ejecuta el código anterior:

$ ./fork

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

Child Process :: var_lcl = [1], var_glb[1]
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.