¿Cuáles son las diferencias entre fork
y exec
?
fork
que básicamente es clonación: O
¿Cuáles son las diferencias entre fork
y exec
?
fork
que básicamente es clonación: O
Respuestas:
El uso fork
y exec
ejemplifica el espíritu de UNIX en el sentido de que proporciona una forma muy sencilla de iniciar nuevos procesos.
La fork
llamada 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 fork
llamada funciona; de lo contrario, no se crea ningún elemento secundario y el padre obtiene un código de error.
La exec
llamada 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, fork
y a exec
menudo 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 find
programa 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 fork
sí mismo sin exec
que, 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 fork
una 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
, exec
y luego wait
para el niño. Simplemente pueden cargar al niño directamente en su espacio de proceso.
Algunas implementaciones de UNIX tienen un sistema optimizado fork
que utiliza lo que llaman copia en escritura. Este es un truco para retrasar la copia del espacio del proceso fork
hasta que el programa intente cambiar algo en ese espacio. Esto es útil para aquellos programas que solo usan fork
y no exec
porque 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 exec
llamadas ( execl
, execle
, execve
etc.), pero exec
en el contexto aquí significa cualquiera de ellos.
El siguiente diagrama ilustra la fork/exec
operación típica en la que el bash
shell se usa para listar un directorio con el 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 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.
exec
es un poco más fácil de entender, solo le indica exec
que 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, exec
se puede usar después de que usted fork
ejecute en el proceso actual el ejecutable de destino.
pid < 0
y la fork()
llamada falló
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
exec
Llamadas 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 actualexec()
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 )
Fork crea una copia de un proceso de llamada. generalmente sigue la estructura
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
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})
Se usan juntos para crear un nuevo proceso hijo. Primero, la llamada fork
crea una copia del proceso actual (el proceso secundario). Luego, exec
se 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
}
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.
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".
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:
fork()
devuelve el identificador de proceso (pid) del proceso hijo en el padre
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;
}
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.)