Entonces, pensé que tenía una buena comprensión de esto, pero realicé una prueba (en respuesta a una conversación en la que no estaba de acuerdo con alguien) y descubrí que mi comprensión es defectuosa...
Con el mayor detalle posible ¿Qué sucede exactamente cuando ejecuto un archivo en mi shell? Lo que quiero decir es que si escribo:./somefile some arguments
en mi shell y presione regresar (y somefile
existe en el cwd, y tengo permisos de lectura y ejecución en somefile
) entonces, ¿qué sucede debajo del capó?
Yo pensé la respuesta fue:
- El shell hace una llamada del sistema a
exec
, pasando la ruta asomefile
- El núcleo examina
somefile
y mira el número mágico del archivo para determinar si es un formato que el procesador puede manejar - Si el número mágico indica que el archivo está en un formato que el procesador puede ejecutar, entonces
- se crea un nuevo proceso (con una entrada en la tabla de procesos)
somefile
se lee/asigna a la memoria. Se crea una pila y la ejecución salta al punto de entrada del código desomefile
, conARGV
inicializado en una matriz de parámetros (unchar**
,["some","arguments"]
)
- Si el número mágico es un shebang entonces
exec()
genera un nuevo proceso como el anterior, pero el ejecutable utilizado es el intérprete al que hace referencia el shebang (por ejemplo,/bin/bash
o/bin/perl
) ysomefile
se pasa aSTDIN
- Si el archivo no tiene un número mágico válido, se produce un error como "archivo no válido (número mágico incorrecto):error de formato Exec"
Sin embargo, alguien me dijo que si el archivo es texto sin formato, entonces el shell intenta ejecutar los comandos (como si hubiera escrito bash somefile
). No lo creía, pero lo probé y fue correcto. Así que claramente tengo algunos conceptos erróneos sobre lo que realmente sucede aquí y me gustaría entender la mecánica.
¿Qué sucede exactamente cuando ejecuto un archivo en mi shell? (con tanto detalle es razonable…)
Respuesta aceptada:
La respuesta definitiva a "cómo se ejecutan los programas" en Linux es el par de artículos en LWN.net titulados, sorprendentemente, Cómo se ejecutan los programas y Cómo se ejecutan los programas:binarios ELF. El primer artículo aborda los guiones brevemente. (Estrictamente hablando, la respuesta definitiva está en el código fuente, pero estos artículos son más fáciles de leer y proporcionan enlaces al código fuente).
Un poco de experimentación muestra que prácticamente lo hizo bien, y que la ejecución de un archivo que contiene una lista simple de comandos, sin un tinglado, debe ser manejada por el shell. La página de manual de execve(2) contiene código fuente para un programa de prueba, execve; usaremos eso para ver qué sucede sin un caparazón. Primero, escriba un script de prueba, testscr1
, que contiene
#!/bin/sh
pstree
y otro, testscr2
, que contiene solo
pstree
Haz que ambos sean ejecutables y verifica que ambos se ejecuten desde un shell:
chmod u+x testscr[12]
./testscr1 | less
./testscr2 | less
Ahora inténtalo de nuevo, usando execve
(asumiendo que lo construiste en el directorio actual):
./execve ./testscr1
./execve ./testscr2
testscr1
todavía se ejecuta, pero testscr2
produce
execve: Exec format error
Esto muestra que el shell maneja testscr2
diferentemente. Sin embargo, no procesa el script en sí mismo, todavía usa /bin/sh
Para hacer eso; esto se puede verificar canalizando testscr2
a less
:
./testscr2 | less -ppstree
En mi sistema, obtengo
|-gnome-terminal--+-4*[zsh]
| |-zsh-+-less
| | `-sh---pstree
Como puede ver, está el shell que estaba usando, zsh
, que empezó less
, y un segundo shell, simple sh
(dash
en mi sistema), para ejecutar el script, que ejecutó pstree
. En zsh
esto lo maneja zexecve
en Src/exec.c
:el shell usa execve(2)
para intentar ejecutar el comando, y si eso falla, lee el archivo para ver si tiene un shebang, procesándolo en consecuencia (lo que también habrá hecho el núcleo), y si eso falla, intenta ejecutar el archivo con sh
, siempre que no haya leído ningún byte cero del archivo:
for (t0 = 0; t0 != ct; t0++)
if (!execvebuf[t0])
break;
if (t0 == ct) {
argv[-1] = "sh";
winch_unblock();
execve("/bin/sh", argv - 1, newenvp);
}
bash
tiene el mismo comportamiento, implementado en execute_cmd.c
con un comentario útil (como lo señaló taliezin):
Ejecute un comando simple que, con suerte, esté definido en un archivo de disco
en algún lugar.
fork ()
- conectar tuberías
- busque el comando
- hacer redirecciones
execve ()
- Si
execve
falló, vea si el archivo tiene configurado el modo ejecutable.
Si es así, y no es un directorio, entonces ejecute su contenido como
un script de shell.
POSIX define un conjunto de funciones, conocidas como exec(3)
funciones, que envuelven execve(2)
y proporcionar esta funcionalidad también; vea la respuesta de muru para más detalles. En Linux, al menos estas funciones son implementadas por la biblioteca C, no por el kernel.