example - fork() in c
Diferencias entre tenedor y ejecutivo (8)
Creo que algunos conceptos de la "Programació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 programa es una colección de instrucciones y datos que se guardan en un archivo común en el disco. (de 1.1.2 Programas, procesos e hilos)
.
Para ejecutar un programa, primero se le pide al kernel que cree un nuevo proceso , que es un entorno en el que se ejecuta un programa. (también desde 1.1.2 Programas, Procesos e Hilos)
.
Es imposible entender las llamadas al sistema ejecutivo o fork sin entender completamente la distinción entre un proceso y un programa. 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 programa 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 programa 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 programa en el proceso actual con otro programa
(Esto es esencialmente una versión simplificada ''para tontos '' de la respuesta mucho más detallada de paxdiablo )
¿Cuáles son las diferencias entre fork
y exec
?
El mejor ejemplo para comprender el concepto fork()
y exec()
es el shell , el programa 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 compilación del código fuente (un programa 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.)
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 programa. Carga el programa 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 programa como un elemento secundario de un proceso actual. Normalmente, las shells hacen esto cuando intenta ejecutar un programa como find
- the shell, y luego el niño carga el programa 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 programa se fork
sin exec
si, por ejemplo, el programa 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 programas que saben que están terminados y solo quieren ejecutar otro programa 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 programa intente cambiar algo en ese espacio. Esto es útil para aquellos programas 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 diagrama 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
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<char**>(argv));
} else { // This is the Parent Process
//Continue executing parent process
}
Warren Young dio una descripción acertada.
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 de proceso secundario (código), datos, la pila 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, 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 que el núcleo de la llamada ejecutora unix borre el texto del proceso hijo, los datos, la pila 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})
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.
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:
fork()
devuelve el identificador de proceso (pid) del proceso hijo en el padrefork()
devuelve 0 en el elemento secundario.
exec()
:
Inicia un nuevo proceso dentro de un proceso. Carga un nuevo programa en el proceso actual, reemplazando el existente.
fork()
+ exec()
:
Cuando se lanza un nuevo programa, primero se fork()
, se crea un nuevo proceso y luego se ejecuta exec()
(es decir, se carga en la memoria y se ejecuta) el programa 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;
}
fork()
divide el proceso actual en dos procesos. O en otras palabras, su programa simpático, lineal y fácil de pensar se convierte de repente en dos programas 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.