¿Cuál es la exec()
función y su familia? ¿Por qué se utiliza esta función y cómo funciona?
Por favor, alguien explique estas funciones.
¿Cuál es la exec()
función y su familia? ¿Por qué se utiliza esta función y cómo funciona?
Por favor, alguien explique estas funciones.
Respuestas:
De manera simplista, en UNIX, tiene el concepto de procesos y programas. Un proceso es un entorno en el que se ejecuta un programa.
La idea simple detrás del "modelo de ejecución" de UNIX es que hay dos operaciones que puede realizar.
La primera es to fork()
, que crea un proceso completamente nuevo que contiene un duplicado (en su mayoría) del programa actual, incluido su estado. Hay algunas diferencias entre los dos procesos que les permiten determinar cuál es el padre y cuál es el hijo.
El segundo es para exec()
, que reemplaza el programa en el proceso actual con un programa completamente nuevo.
A partir de esas dos operaciones simples, se puede construir todo el modelo de ejecución de UNIX.
Para agregar más detalles a lo anterior:
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 hace casi 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). Solo un proceso llama, fork()
pero dos procesos regresan de esa llamada; suena extraño pero es realmente bastante elegante
El nuevo proceso (llamado hijo) obtiene un ID de proceso (PID) diferente y tiene el PID del proceso anterior (el padre) como su PID padre (PPID).
Debido a que los dos procesos ahora están ejecutando exactamente el mismo código, necesitan poder decir cuál es cuál - el código de retorno de fork()
proporciona esta información - el niño obtiene 0, el padre obtiene el PID del niño (si fork()
falla, no se crea el hijo y el padre recibe un código de error).
De esa manera, el padre conoce el PID del niño y puede comunicarse con él, matarlo, esperarlo, etc. (el niño siempre puede encontrar su proceso padre con una llamada a getppid()
).
La exec()
llamada reemplaza todo el contenido actual del proceso con un nuevo programa. Carga el programa en el espacio de proceso actual y lo ejecuta desde el punto de entrada.
Por lo tanto, fork()
y a exec()
menudo se usan en secuencia para ejecutar un nuevo programa como hijo de un proceso actual. Los shells suelen hacer esto cada vez que intenta 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 la línea de comandos, E / S estándar, etc.
Pero no es necesario que se usen juntos. Es perfectamente aceptable que un programa llame fork()
sin seguimiento exec()
si, por ejemplo, el programa contiene tanto código principal como 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 bifurcan una copia de sí mismos para procesar una solicitud específica mientras el padre vuelve a escuchar. Para esta situación, el programa contiene tanto el código principal como el secundario.
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()/waitpid()
para el niño. Simplemente pueden cargar al niño directamente en su espacio de proceso actual con exec()
.
Algunas implementaciones de UNIX tienen un sistema optimizado fork()
que usa lo que ellos llaman copia en escritura. Este es un truco para retrasar la copia del espacio de proceso fork()
hasta que el programa intente cambiar algo en ese espacio. Esto es útil para aquellos programas que usan solo fork()
y no exec()
porque no tienen que copiar un espacio de proceso completo. Bajo Linux, fork()
solo hace una copia de las tablas de páginas y una nueva estructura de tareas, exec()
hará el trabajo duro de "separar" la memoria de los dos procesos.
Si exec
se llama siguiente fork
(y esto es lo que sucede principalmente), eso provoca una escritura en el espacio del proceso y luego se copia para el proceso hijo, antes de que se permitan las modificaciones.
Linux también tiene un vfork()
, aún más optimizado, que comparte casi todo entre los dos procesos. Por eso, existen ciertas restricciones en lo que puede hacer el niño, y el padre se detiene hasta que el niño llama exec()
o _exit()
.
El padre debe detenerse (y el hijo no puede regresar de la función actual) ya que los dos procesos incluso comparten la misma pila. Esto es un poco más eficiente para el caso de uso clásico de fork()
seguido inmediatamente por exec()
.
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 donde bash
se usa el shell 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
exec
utiliza la utilidad para redirigir la E / S del proceso actual? ¿Cómo se usó el caso "nulo", ejecutando exec sin un argumento, para esta convención?
exec
es el medio para reemplazar el programa actual (el shell) en este proceso por otro, entonces no especificar ese otro programa con el que reemplazarlo puede significar simplemente que no desea reemplazarlo.
exec
ser llamado sin un programa. Pero es un poco extraño en este escenario, ya que la utilidad original de redirigir a un nuevo programa, un programa que en realidad se exec
eliminaría, desaparece y tiene un artefacto útil, que redirige el programa actual, que no se está exec
ejecutando o iniciando. de cualquier manera, en su lugar.
Las funciones de la familia exec () tienen diferentes comportamientos:
Puedes mezclarlos, por lo tanto tienes:
Para todos ellos, el argumento inicial es el nombre de un archivo que se va a ejecutar.
Para obtener más información, lea la página del manual de exec (3) :
man 3 exec # if you are running a UNIX system
execve()
de su lista, que está definida por POSIX, y agregó execvpe()
, que no está definida por POSIX (principalmente por razones de precedentes históricos; completa el conjunto de funciones). De lo contrario, una explicación útil de la convención de nomenclatura para la familia, un complemento útil de paxdiablo , una respuesta que explica más sobre el funcionamiento de las funciones.
execvpe()
(et al) no aparece execve()
; tiene su propia página de manual separada (al menos en Ubuntu 16.04 LTS); la diferencia es que las otras exec()
funciones de la familia se enumeran en la sección 3 (funciones) mientras que execve()
se enumeran en la sección 2 (llamadas al sistema). Básicamente, todas las demás funciones de la familia se implementan en términos de una llamada a execve()
.
La exec
familia de funciones hace que su proceso ejecute un programa diferente, reemplazando el programa anterior que estaba ejecutando. Es decir, si llamas
execl("/bin/ls", "ls", NULL);
luego, el ls
programa se ejecuta con la identificación del proceso, el directorio de trabajo actual y el usuario / grupo (derechos de acceso) del proceso que llamó execl
. Posteriormente, el programa original ya no se ejecuta.
Para iniciar un nuevo proceso, fork
se utiliza la llamada al sistema. Para ejecutar un programa sin tener que reemplazar el original, es necesario fork
, a continuación exec
.
cuál es la función ejecutiva y su familia.
La exec
familia es la función de todas las funciones que se utilizan para ejecutar un archivo, tales como execl
, execlp
, execle
, execv
, y execvp
.Son todos los interfaces a execve
y proporcionar diferentes métodos de llamarlo.
¿Por qué se usa esta función?
Las funciones Exec se utilizan cuando desea ejecutar (lanzar) un archivo (programa).
Y, cómo funciona.
Funcionan sobrescribiendo la imagen del proceso actual con la que inició. Reemplazan (al finalizar) el proceso que se está ejecutando actualmente (el que llamó al comando exec) con el nuevo proceso que se ha iniciado.
Para más detalles: vea este enlace .
exec
se usa a menudo junto con fork
, lo que vi que también preguntó, por lo que discutiré esto con eso en mente.
exec
convierte el proceso actual en otro programa. Si alguna vez viste Doctor Who, entonces esto es como cuando se regenera: su cuerpo viejo es reemplazado por uno nuevo.
La forma en que esto sucede con su programa y exec
es que muchos de los recursos que el kernel del sistema operativo verifica para ver si el archivo al que está pasando exec
como argumento del programa (primer argumento) es ejecutable por el usuario actual (ID de usuario del proceso haciendo la exec
llamada) y, si es así, reemplaza el mapeo de memoria virtual del proceso actual con una memoria virtual del nuevo proceso y copia los datos argv
y envp
que se pasaron en la exec
llamada en un área de este nuevo mapa de memoria virtual. Varias otras cosas también pueden suceder aquí, pero los archivos que estaban abiertos para el programa que llamó exec
todavía estarán abiertos para el nuevo programa y compartirán el mismo ID de proceso, pero el programa que llamó exec
cesará (a menos que el ejecutivo falle).
La razón por la que esto se hace de esta manera es que al separar la ejecución de un nuevo programa en dos pasos como este, puede hacer algunas cosas entre los dos pasos. Lo más común es asegurarse de que el nuevo programa tenga ciertos archivos abiertos como ciertos descriptores de archivo. (recuerde aquí que los descriptores de archivo no son lo mismo que FILE *
, pero son int
valores que el kernel conoce). Haciendo esto puedes:
int X = open("./output_file.txt", O_WRONLY);
pid_t fk = fork();
if (!fk) { /* in child */
dup2(X, 1); /* fd 1 is standard output,
so this makes standard out refer to the same file as X */
close(X);
/* I'm using execl here rather than exec because
it's easier to type the arguments. */
execl("/bin/echo", "/bin/echo", "hello world");
_exit(127); /* should not get here */
} else if (fk == -1) {
/* An error happened and you should do something about it. */
perror("fork"); /* print an error message */
}
close(X); /* The parent doesn't need this anymore */
Esto logra ejecutar:
/bin/echo "hello world" > ./output_file.txt
desde el shell de comandos.
Cuando un proceso usa fork (), crea una copia duplicada de sí mismo y este duplicado se convierte en el hijo del proceso. El fork () se implementa usando la llamada al sistema clone () en Linux, que regresa dos veces desde el kernel.
Entendamos esto con un ejemplo:
pid = fork();
// Both child and parent will now start execution from here.
if(pid < 0) {
//child was not created successfully
return 1;
}
else if(pid == 0) {
// This is the child process
// Child process code goes here
}
else {
// Parent process code goes here
}
printf("This is code common to parent and child");
En el ejemplo, asumimos que exec () no se usa dentro del proceso hijo.
Pero un padre y un hijo difieren en algunos de los atributos de PCB (bloque de control de proceso). Estos son:
Pero, ¿qué pasa con la memoria infantil? ¿Se crea un nuevo espacio de direcciones para un niño?
Las respuestas en el no. Después de la bifurcación (), tanto el padre como el hijo comparten el espacio de direcciones de memoria del padre. En Linux, estos espacios de direcciones se dividen en varias páginas. Solo cuando el niño escribe en una de las páginas de la memoria principal, se crea un duplicado de esa página para el niño. Esto también se conoce como copia al escribir (copiar las páginas principales solo cuando el niño escribe en ellas).
Comprendamos copia sobre escritura con un ejemplo.
int x = 2;
pid = fork();
if(pid == 0) {
x = 10;
// child is changing the value of x or writing to a page
// One of the parent stack page will contain this local variable. That page will be duplicated for child and it will store the value 10 in x in duplicated page.
}
else {
x = 4;
}
Pero, ¿por qué es necesaria la copia por escrito?
La creación de un proceso típico se lleva a cabo mediante la combinación fork () - exec (). Primero entendamos qué hace exec ().
El grupo de funciones Exec () reemplaza el espacio de direcciones del niño con un nuevo programa. Una vez que se llama a exec () dentro de un niño, se creará un espacio de direcciones separado para el niño que es totalmente diferente al del padre.
Si no hubiera copia en el mecanismo de escritura asociado con fork (), se habrían creado páginas duplicadas para el niño y todos los datos se habrían copiado en las páginas del niño. Asignar nueva memoria y copiar datos es un proceso muy costoso (requiere tiempo del procesador y otros recursos del sistema). También sabemos que, en la mayoría de los casos, el niño llamará a exec () y eso reemplazaría la memoria del niño con un nuevo programa. Así que la primera copia que hicimos habría sido un desperdicio si la copia en escritura no estuviera allí.
pid = fork();
if(pid == 0) {
execlp("/bin/ls","ls",NULL);
printf("will this line be printed"); // Think about it
// A new memory space will be created for the child and that memory will contain the "/bin/ls" program(text section), it's stack, data section and heap section
else {
wait(NULL);
// parent is waiting for the child. Once child terminates, parent will get its exit status and can then continue
}
return 1; // Both child and parent will exit with status code 1.
¿Por qué el padre espera el proceso del hijo?
¿Por qué es necesaria la llamada al sistema exec ()?
No es necesario usar exec () con fork (). Si el código que ejecutará el hijo está dentro del programa asociado con el padre, no se necesita exec ().
Pero piense en casos en los que el niño tiene que ejecutar varios programas. Tomemos el ejemplo del programa shell. Soporta múltiples comandos como find, mv, cp, date, etc. ¿Será correcto incluir el código de programa asociado con estos comandos en un programa o hacer que el niño cargue estos programas en la memoria cuando sea necesario?
Todo depende de su caso de uso. Tiene un servidor web que dio una entrada x que devuelve 2 ^ x a los clientes. Para cada solicitud, el servidor web crea un nuevo hijo y le pide que calcule. ¿Escribirá un programa separado para calcular esto y usará exec ()? ¿O simplemente escribirás código de cálculo dentro del programa principal?
Por lo general, la creación de un proceso implica una combinación de llamadas fork (), exec (), wait () y exit ().
Las exec(3,3p)
funciones reemplazan el proceso actual por otro. Es decir, el proceso actual se detiene y en su lugar se ejecuta otro, tomando algunos de los recursos que tenía el programa original.