Diferencias entre tenedor y ejecutivo

¿Cuáles son las diferencias entre fork y exec ?

El uso de fork y exec ejemplifica el espíritu de UNIX ya que proporciona una manera muy simple de iniciar nuevos procesos.

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

El nuevo proceso (secundario) obtiene un ID de proceso (PID) diferente y tiene el PID del proceso anterior (principal) como su PID principal (PPID). Debido a que los dos procesos ahora están ejecutando exactamente el mismo código, pueden decir cuál es cuál por el código de retorno del fork : el niño obtiene 0, el padre obtiene el PID del niño. Esto es todo, por supuesto, suponiendo que funciona la llamada de la fork ; si no, no se crea un niño y el padre recibe un código de error.

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

Entonces, fork y exec menudo se usan en secuencia para ejecutar un nuevo progtwig como un elemento secundario de un proceso actual. Normalmente, las shells hacen esto cuando intenta ejecutar un progtwig como find – the shell, y luego el niño carga el progtwig de find en la memoria, configurando todos los argumentos de línea de comando, E / S estándar y demás.

Pero no se requiere que se usen juntos. Es perfectamente aceptable que un progtwig se fork sin exec si, por ejemplo, el progtwig contiene códigos padre e hijo (debe tener cuidado con lo que hace, cada implementación puede tener restricciones). Esto se usó bastante (y sigue siendo) para daemons que simplemente escuchan en un puerto TCP y fork una copia de ellos mismos para procesar una solicitud específica mientras el padre vuelve a escuchar.

Del mismo modo, los progtwigs que saben que están terminados y solo quieren ejecutar otro progtwig no necesitan fork , ejecutar y luego wait al hijo. Pueden simplemente cargar al niño directamente en su espacio de proceso.

Algunas implementaciones de UNIX tienen una fork optimizada que usa lo que llaman copy-on-write. Este es un truco para retrasar la copia del espacio de proceso en el fork hasta que el progtwig intente cambiar algo en ese espacio. Esto es útil para aquellos progtwigs que usan solo fork y no exec ya que no tienen que copiar un espacio de proceso completo.

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

Tenga en cuenta que hay toda una familia de llamadas exec ( execl , execle , execve , etc.) pero exec en contexto aquí significa cualquiera de ellas.

El siguiente diagtwig ilustra la operación fork/exec típica donde el shell bash se usa para listar un directorio con el comando ls :

 +--------+ | 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 el proceso actual en dos procesos. O en otras palabras, su progtwig simpático, lineal y fácil de pensar se convierte de repente en dos progtwigs separados que ejecutan un 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 hacer volar tu mente. Ahora tiene una pieza de 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 llamada fork() acaba de terminar. La única diferencia es el código de retorno de la fork() para informarle si usted es el padre o el hijo. Si usted es el padre, el valor de retorno es la identificación del niño.

exec es un poco más fácil de entender, simplemente le dice a exec que ejecute un proceso usando el ejecutable de destino y no tiene dos procesos ejecutando el mismo código o heredando el mismo estado. Al igual que dice @Steve Hawkins, el ejecutor se puede usar después de que fork para ejecutar en el proceso actual el ejecutable de destino.

Creo que algunos conceptos de la “Progtwigción Unix Avanzada” de Marc Rochkind fueron útiles para comprender los diferentes roles de fork() / exec() , especialmente para alguien que está acostumbrado al modelo Windows CreateProcess() :

Un progtwig es una colección de instrucciones y datos que se guardan en un archivo común en el disco. (de 1.1.2 Progtwigs, procesos e hilos)

.

Para ejecutar un progtwig, primero se le pide al kernel que cree un nuevo proceso , que es un entorno en el que se ejecuta un progtwig. (también desde 1.1.2 Progtwigs, Procesos e Hilos)

.

Es imposible entender las llamadas al sistema ejecutivo o fork sin entender completamente la distinción entre un proceso y un progtwig. Si estos términos son nuevos para usted, puede volver atrás 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 durante el tiempo de ejecución. , mientras que un progtwig es un archivo que contiene instrucciones y datos que se utilizan para inicializar los segmentos de instrucción y datos de usuario de un proceso. (de 5.3 llamadas al sistema del exec )

Una vez que comprenda la distinción entre un progtwig y un proceso, el comportamiento de las funciones fork() y exec() se puede resumir como sigue:

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

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

Fork crea una copia de un proceso de llamada. generalmente sigue la estructura enter image description here

 int cpid = fork( ); if (cpid = = 0) { //child code exit(0); } //parent code wait(cpid); // end 

(para el texto de proceso secundario (código), datos, la stack es lo mismo que el proceso de llamada) el proceso hijo ejecuta el código en el bloque if.

EXEC reemplaza el proceso actual con el nuevo código de proceso, datos, stack. generalmente sigue la estructura enter image description here

 int cpid = fork( ); if (cpid = = 0) { //child code exec(foo); exit(0); } //parent code wait(cpid); // end 

(después de que el núcleo de la llamada ejecutora unix borre el texto del proceso hijo, los datos, la stack y los llene con texto / datos relacionados con el proceso foo), entonces el proceso hijo tiene un código diferente (código de foo {no igual que el padre})

Se usan juntos para crear un nuevo proceso hijo. Primero, el fork llamada crea una copia del proceso actual (el proceso hijo). Luego, se llama al exec desde dentro del 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(argv)); } else { // This is the Parent Process //Continue executing parent process } 

fork () crea una copia del proceso actual, con la ejecución en el nuevo hijo empezando justo después de la llamada fork (). Después de la horquilla (), son idénticos, excepto por el valor de retorno de la función de horquilla (). (RTFM para obtener más detalles.) Los dos procesos pueden 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 () suele seguir fork () cuando lo que se quiere es iniciar un proceso hijo diferente, en lugar de reemplazar el actual.

enter image description here fork() :

Crea una copia del proceso en ejecución. El proceso en ejecución se denomina proceso principal y el proceso recién creado se denomina 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 elemento secundario.

exec() :

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

fork() + exec() :

Cuando se lanza un nuevo progtwig, primero se fork() , se crea un nuevo proceso y luego se ejecuta exec() (es decir, se carga en la memoria y se ejecuta) el progtwig binario que se supone que se debe ejecutar.

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

El mejor ejemplo para comprender el concepto fork() y exec() es el shell , el progtwig de intérprete de comandos que los usuarios suelen ejecutar después de iniciar sesión en el sistema. El intérprete de comandos interpreta la primera palabra de la línea de comando como un nombre de comando

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

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