Diferencias entre fork y exec

199

¿Cuáles son las diferencias entre forky exec?

Sashi
fuente
3
Un buen resumen detallado de fork, exec y otras funciones de control de procesos está en yolinux.com/TUTORIALS/ForkExecProcesses.html
Jonathan Fingland
9
@Justin, porque queremos que SO se convierta en el lugar para preguntas de programación.
paxdiablo
44
@ Polaris878: ¡oh, lo hace ahora! : D
Janusz Lenar
así forkque básicamente es clonación: O
Sebastian Hojas

Respuestas:

364

El uso forky execejemplifica el espíritu de UNIX en el sentido de que proporciona una forma muy sencilla de iniciar nuevos procesos.

La forkllamada básicamente hace un duplicado del proceso actual, idéntico en casi todos los sentidos. No todo se copia (por ejemplo, límites de recursos en algunas implementaciones), pero la idea es crear una copia lo más cercana posible.

El nuevo proceso (hijo) obtiene un ID de proceso diferente (PID) y tiene el PID del proceso anterior (padre) como su PID padre (PPID). Debido a que los dos procesos ahora ejecutan exactamente el mismo código, pueden determinar cuál es cuál mediante el código de retorno de fork: el elemento secundario obtiene 0, el elemento primario obtiene el PID del elemento secundario. Esto es todo, por supuesto, suponiendo que la forkllamada funciona; de lo contrario, no se crea ningún elemento secundario y el padre obtiene un código de error.

La execllamada es una forma de reemplazar básicamente todo el proceso actual con un nuevo programa. Carga el programa en el espacio de proceso actual y lo ejecuta desde el punto de entrada.

Entonces, forky a execmenudo se usan en secuencia para que un nuevo programa se ejecute como hijo de un proceso actual. Los shells generalmente hacen esto cada vez que intentas ejecutar un programa como find: el shell se bifurca, luego el niño carga el findprograma en la memoria, configurando todos los argumentos de línea de comandos, E / S estándar, etc.

Pero no se requiere que se usen juntos. Es perfectamente aceptable para un programa en forksí mismo sin execque, por ejemplo, el programa contenga tanto el código principal como el secundario (debe tener cuidado con lo que hace, cada implementación puede tener restricciones). Esto se usó bastante (y todavía lo es) para demonios que simplemente escuchan en un puerto TCP y forkuna copia de ellos mismos para procesar una solicitud específica mientras el padre vuelve a escuchar.

Del mismo modo, los programas que saben que están terminados y solo quieren ejecutar otro programa no necesitan hacerlo fork, execy luego waitpara el niño. Simplemente pueden cargar al niño directamente en su espacio de proceso.

Algunas implementaciones de UNIX tienen un sistema optimizado forkque utiliza lo que llaman copia en escritura. Este es un truco para retrasar la copia del espacio del proceso forkhasta que el programa intente cambiar algo en ese espacio. Esto es útil para aquellos programas que solo usan forky no execporque no tienen que copiar un espacio de proceso completo.

Si exec se llama siguiente fork(y esto es lo que sucede principalmente), eso genera una escritura en el espacio del proceso y luego se copia para el proceso secundario.

Tenga en cuenta que hay toda una familia de execllamadas ( execl, execle, execveetc.), pero execen el contexto aquí significa cualquiera de ellos.

El siguiente diagrama ilustra la fork/execoperación típica en la que el bashshell se usa para listar un directorio con el 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
paxdiablo
fuente
52

fork()divide el proceso actual en dos procesos. O, en otras palabras, su agradable programa lineal, fácil de pensar, de repente se convierte en dos programas separados que ejecutan una sola pieza de 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
 }

Esto puede volar tu mente. Ahora tiene un código con un estado prácticamente idéntico ejecutado por dos procesos. El proceso secundario hereda todo el código y la memoria del proceso que acaba de crearlo, incluido el inicio desde donde la fork()llamada se acaba. La única diferencia es el fork()código de retorno para decirle si usted es el padre o el hijo. Si usted es el padre, el valor de retorno es el id del niño.

execes un poco más fácil de entender, solo le indica execque ejecute un proceso utilizando el ejecutable de destino y no tiene dos procesos que ejecuten el mismo código o hereden el mismo estado. Como dice @Steve Hawkins, execse puede usar después de que usted forkejecute en el proceso actual el ejecutable de destino.

Doug T.
fuente
66
también existe la condición cuando pid < 0y la fork()llamada falló
Jonathan Fingland
3
Eso no me sorprende en absoluto :-) Una pieza de código ejecutada por dos procesos ocurre cada vez que se usa una biblioteca compartida o DLL.
paxdiablo
31

Creo que algunos conceptos de "Programación avanzada de Unix" de Marc Rochkind fueron útiles para comprender los diferentes roles de fork()/ exec(), especialmente para alguien acostumbrado al CreateProcess()modelo de Windows :

Un programa es una colección de instrucciones y datos que se guardan en un archivo normal en el disco. (de 1.1.2 Programas, procesos y subprocesos)

.

Para ejecutar un programa, primero se le pide al núcleo que cree un nuevo proceso , que es un entorno en el que se ejecuta un programa. (también de 1.1.2 Programas, procesos y subprocesos)

.

Es imposible entender las llamadas del sistema ejecutivo o fork sin comprender completamente la distinción entre un proceso y un programa. Si estos términos son nuevos para usted, es posible que desee volver y revisar la Sección 1.1.2. Si está listo para continuar ahora, resumiremos la distinción en una oración: un proceso es un entorno de ejecución que consta de segmentos de instrucción, datos de usuario y datos del sistema, así como muchos otros recursos adquiridos en tiempo de ejecución , mientras que un programa es un archivo que contiene instrucciones y datos que se utilizan para inicializar la instrucción y los segmentos de datos de usuario de un proceso. (de 5.3 execLlamadas del sistema)

Una vez que comprenda la distinción entre un programa y un proceso, el comportamiento fork()y la exec()función se pueden resumir como:

  • fork() crea un duplicado del proceso actual
  • exec() reemplaza el programa en el proceso actual con otro programa

(esta es esencialmente una versión simplificada 'para tontos ' de la respuesta mucho más detallada de paxdiablo )

Michael Burr
fuente
29

Fork crea una copia de un proceso de llamada. generalmente sigue la estructura ingrese la descripción de la imagen aquí

int cpid = fork( );

if (cpid = = 0) 
{

  //child code

  exit(0);

}

//parent code

wait(cpid);

// end

(para el texto del proceso secundario (código), datos, la pila es igual que el proceso de llamada) el proceso secundario ejecuta el código en el bloque if.

EXEC reemplaza el proceso actual con el nuevo código de proceso, datos, pila. generalmente sigue la estructura ingrese la descripción de la imagen aquí

int cpid = fork( );

if (cpid = = 0) 
{   
  //child code

  exec(foo);

  exit(0);    
}

//parent code

wait(cpid);

// end

(después de la llamada ejecutiva, el kernel de Unix borra el texto del proceso secundario, los datos, la pila y se llena con texto / datos relacionados con el proceso foo), por lo tanto, el proceso secundario tiene un código diferente (el código foo {no es el mismo que el padre})

Sandesh Kobal
fuente
1
Es un poco ajeno a la pregunta, pero ¿este código anterior no causa una condición de carrera si el proceso secundario termina primero su código? En ese caso, el proceso de los padres se quedaría para siempre esperando que el niño terminara, ¿verdad?
stdout
7

Se usan juntos para crear un nuevo proceso hijo. Primero, la llamada forkcrea una copia del proceso actual (el proceso secundario). Luego, execse llama desde el proceso secundario para "reemplazar" la copia del proceso principal con el nuevo proceso.

El proceso es algo como esto:

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
}
Steve Hawkins
fuente
2
En línea 7 se menciona que la función exec () crea el proceso hijo .. ¿Es realmente así porque tenedor () ya ha creado el proceso hijo y exec () llamar simplemente reemplaza el programa del nuevo proceso acaba de crear
cbinder
4

fork () crea una copia del proceso actual, con ejecución en el nuevo hijo a partir de justo después de la llamada fork (). Después de fork (), son idénticos, excepto por el valor de retorno de la función fork (). (RTFM para más detalles). Los dos procesos pueden entonces divergir aún más, con uno incapaz de interferir con el otro, excepto posiblemente a través de los identificadores de archivos compartidos.

exec () reemplaza el proceso actual con uno nuevo. No tiene nada que ver con fork (), excepto que un exec () a menudo sigue a fork () cuando lo que se desea es iniciar un proceso secundario diferente, en lugar de reemplazar el actual.

Warren Young
fuente
3

La principal diferencia entre fork()y exec()es que,

La fork()llamada al sistema crea un clon del programa actualmente en ejecución. El programa original continúa la ejecución con la siguiente línea de código después de la llamada a la función fork (). El clon también comienza a ejecutarse en la siguiente línea de código. Mira el siguiente código que obtuve de 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 una variable de contador, establecida en cero, antes de fork()ing. Después de la llamada fork, tenemos dos procesos ejecutándose en paralelo, ambos incrementando su propia versión del contador. Cada proceso se ejecutará hasta su finalización y saldrá. Debido a que los procesos se ejecutan en paralelo, no tenemos forma de saber cuál terminará primero. La ejecución de este programa imprimirá algo similar a lo que se muestra a continuación, aunque los resultados pueden variar de una ejecución a la siguiente.

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

La exec()familia de llamadas al sistema reemplaza el código actualmente en ejecución de un proceso con otro fragmento de código. El proceso conserva su PID pero se convierte en un nuevo programa. Por ejemplo, considere el siguiente 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 llama a la execvp()función para reemplazar su código con el programa de fecha. Si el código se almacena en un archivo llamado exec1.c, su ejecución produce el siguiente resultado:

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

El programa emite la línea ―Ready to exec (). . . ‖ Y después de llamar a la función execvp (), reemplaza su código con el programa de fecha. Tenga en cuenta que la línea -. . . funcionó‖ no se muestra, porque en ese momento el código ha sido reemplazado. En cambio, vemos el resultado de ejecutar "fecha -u".

Abdulhakim Zeinu
fuente
1

ingrese la descripción de la imagen aquífork():

Crea una copia del proceso en ejecución. El proceso en ejecución se llama proceso padre y el proceso recién creado se llama proceso hijo . La forma de diferenciar los dos es mirando el valor devuelto:

  1. fork() devuelve el identificador de proceso (pid) del proceso hijo en el padre

  2. fork() devuelve 0 en el niño.

exec():

Inicia un nuevo proceso dentro de un proceso. Carga un nuevo programa en el proceso actual, reemplazando el existente.

fork() + exec() :

Cuando se inicia un nuevo programa fork(), primero se crea un nuevo proceso y luego exec()(es decir, se carga en la memoria y se ejecuta) el programa binario que debe ejecutarse.

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;
}
Yogeesh HT
fuente
0

El principal ejemplo para entender el fork()y exec()el concepto es la cáscara , el programa intérprete de comandos que los usuarios normalmente se ejecuta después de la tala en la cáscara system.The interpreta la primera palabra de la línea de comando como un comando de nombre

Para muchos comandos, el shell se bifurca y el proceso secundario ejecuta el comando asociado con el nombre que trata las palabras restantes en la línea de comando como parámetros del comando.

El shell permite tres tipos de comandos. Primero, un comando puede ser un archivo ejecutable que contiene código objeto producido por la compilación de código fuente (un programa en C, por ejemplo). En segundo lugar, un comando puede ser un archivo ejecutable que contiene una secuencia de líneas de comando de shell. Finalmente, un comando puede ser un comando de shell interno (en lugar de un archivo ejecutable ex-> cd , ls , etc.)

krpra
fuente