¿Qué sucede exactamente cuando ejecuto un archivo en mi shell?

32

Entonces, pensé que tenía una buena comprensión de esto, pero solo 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 argumentsen mi shell y somefilepresiono Intro (y existe en el cwd, y tengo permisos de lectura + ejecución somefile), ¿qué sucede debajo del capó?

Yo pensaba que la respuesta fue:

  1. El shell hace una llamada al sistema exec, pasando el camino asomefile
  2. El kernel examina somefiley mira el número mágico del archivo para determinar si es un formato que el procesador puede manejar
  3. Si el número mágico indica que el archivo está en un formato que el procesador puede ejecutar, entonces
    1. se crea un nuevo proceso (con una entrada en la tabla de procesos)
    2. somefilese lee / se asigna a la memoria. Se crea una pila y la ejecución salta al punto de entrada del código de somefile, con ARGVinicializado a una matriz de los parámetros (a char**, ["some","arguments"])
  4. Si el número mágico es un shebang, se 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/bashor /bin/perl) y somefilese pasa aSTDIN
  5. 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 de ejecución"

Sin embargo, alguien me dijo que si el archivo es texto sin formato, el shell intenta ejecutar los comandos (como si hubiera escrito bash somefile). No lo creía, pero lo intenté y fue correcto. Así que claramente tengo algunas ideas erróneas 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 ...)

Josh
fuente
1
No hay un sustituto perfecto para mirar el código fuente para una comprensión completa.
Comodín el
1
@Wildcard, eso es lo que estoy haciendo en este momento, en realidad :-) Si puedo, responderé mi propia pregunta
Josh
1
source somefileSin ./somefileembargo, es muy diferente de un nuevo proceso que se bifurca .
thrig
@ thrig sí, estoy de acuerdo. Pero no pensé que eso ./somefileharía que bash ejecutara los comandos somefilesi el archivo no tuviera un número mágico. Pensé que solo mostraría un error, y en su lugar parece que efectivamentesource somefile
Josh
Estoy equivocado de nuevo, puedo confirmar que si se somefiletrata de un archivo de texto, se genera un nuevo shell si intento ejecutarlo. Un archivo se echo $$comporta de manera diferente si lo ejecuto vs fuente.
Josh

Respuestas:

32

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 lo entendiste bien y que la ejecución de un archivo que contiene una lista simple de comandos, sin un shebang, debe ser manejada por el shell. La página de manual de execve (2) contiene el código fuente de un programa de prueba, execve; usaremos eso para ver qué sucede sin un shell. Primero, escriba un script de prueba que testscr1contenga

#!/bin/sh

pstree

y otro testscr2, que contiene solo

pstree

Haga que ambos sean ejecutables y verifique que ambos se ejecuten desde un shell:

chmod u+x testscr[12]
./testscr1 | less
./testscr2 | less

Ahora intente nuevamente, usando execve(suponiendo que lo haya construido en el directorio actual):

./execve ./testscr1
./execve ./testscr2

testscr1todavía funciona, pero testscr2produce

execve: Exec format error

Esto muestra que el shell se maneja de manera testscr2diferente. Sin embargo, no procesa el script en sí, todavía lo /bin/shhace; esto se puede verificar canalizando testscr2a 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, zshque comenzó less, y un segundo shell, simple sh( dashen mi sistema), para ejecutar el script, que se ejecutó pstree. En zshesto se maneja en zexecvein 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 el núcleo también habrá hecho), y si eso falla, intenta ejecutar el archivo con shtal de 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);
        }

bashtiene el mismo comportamiento, implementado execute_cmd.ccon un comentario útil (como lo señala taliezin ):

Ejecute un comando simple que se define en un archivo de disco en alguna parte.

  1. fork ()
  2. conectar tuberías
  3. busca el comando
  4. hacer redirecciones
  5. execve ()
  6. Si execvefalla, vea si el archivo tiene establecido el modo ejecutable. Si es así, y no es un directorio, ejecute su contenido como un script de shell.

POSIX define un conjunto de funciones, conocido como las exec(3)funciones , que se 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 núcleo.

Stephen Kitt
fuente
Esto es fantástico y tiene el detalle que estaba buscando, ¡gracias!
Josh
12

En parte, esto depende de la execfunción familiar particular que se use. execve, como Stephen Kitt ha demostrado en detalle, solo ejecuta archivos en el formato binario correcto o scripts que comienzan con un shebang adecuado.

Sin embargo , execlpy execvpvaya un paso más allá: si el shebang no era correcto, el archivo se ejecuta con /bin/shLinux. De man 3 exec:

Special semantics for execlp() and execvp()
   The execlp(), execvp(), and execvpe() functions duplicate the actions
   of the shell in searching for an executable file if the specified
   filename does not contain a slash (/) character.

   If the header of a file isn't recognized (the attempted execve(2)
   failed with the error ENOEXEC), these functions will execute the
   shell (/bin/sh) with the path of the file as its first argument.  (If
   this attempt fails, no further searching is done.)

POSIX ( esto es algo mío) es compatible con esto :

Una posible fuente de confusión observada por los desarrolladores estándar es sobre cómo los contenidos de un archivo de imagen de proceso afectan el comportamiento de la familia de funciones ejecutivas. La siguiente es una descripción de las acciones tomadas:

  1. Si el archivo de imagen de proceso es un ejecutable válido (en un formato que es ejecutable y válido y que tiene los privilegios apropiados) para este sistema, entonces el sistema ejecuta el archivo.

  2. Si el archivo de imagen de proceso tiene los privilegios apropiados y está en un formato que es ejecutable pero no válido para este sistema (como un binario reconocido para otra arquitectura), entonces este es un error y errno está configurado en [EINVAL] (ver más adelante JUSTIFICACIÓN en [EINVAL]).

  3. Si el archivo de imagen de proceso tiene los privilegios apropiados pero no se reconoce de otra manera:

    1. Si se trata de una llamada a execlp () o execvp (), invocan a un intérprete de comandos suponiendo que el archivo de imagen de proceso es un script de shell.

    2. Si no se trata de una llamada a execlp () o execvp (), se produce un error y errno se establece en [ENOEXEC].

Esto no especifica cómo se obtiene el intérprete de comandos, por lo tanto, pero no especifica que se deba dar un error. Supongo, por lo tanto, que los desarrolladores de Linux permitieron ejecutar dichos archivos /bin/sh(o esto ya era una práctica común y simplemente siguieron su ejemplo).

FWIW, la página de manual de FreeBSDexec(3) también menciona comportamientos similares:

 Some of these functions have special semantics.

 The functions execlp(), execvp(), and execvP() will duplicate the actions
 of the shell in searching for an executable file if the specified file
 name does not contain a slash ``/'' character. 
 If the header of a file is not recognized (the attempted execve()
 returned ENOEXEC), these functions will execute the shell with the path
 of the file as its first argument.  (If this attempt fails, no further
 searching is done.)

AFAICT, sin embargo, no hay usos comunes de shell execlpo execvpdirectamente, presumiblemente para un control más fino sobre el medio ambiente. Todos implementan la misma lógica usando execve.

muru
fuente
3
También me gustaría añadir que, al menos en Linux, execl, execlp, execle, execv, execvpy execvpeson todas frontales a la execvellamada al sistema; los primeros son proporcionados por la biblioteca C, el núcleo solo conoce execve(y execveathoy en día).
Stephen Kitt
@StephenKitt Eso explica por qué no pude encontrar una página de manual para esas funciones en la sección 2. de man7.org
muru
6

Esto podría ser una adición a la respuesta de Stephen Kitt, como un comentario de la bashfuente en el archivo execute_cmd.c:

Ejecute un comando simple que se define en un archivo de disco en alguna parte.

1. fork ()
2. connect pipes
3. look up the command
4. do redirections
5. execve ()
6. If the execve failed, see if the file has executable mode set.  

Si es así, y no es un directorio, ejecute su contenido como un script de shell.

taliezin
fuente
0

El fichero es ejecutado como un script de shell, se no de origen (por ejemplo, las variables establecidas en el archivo ejecutado no afectan exterior). Probablemente vestigial del pasado brumoso, cuando había un shell y un formato ejecutable. No es un ejecutable, debe ser un script de shell.

vonbrand
fuente
2
Has entendido mal mi pregunta. ¿Qué pasa en detalle? Como mínimo, necesito entender qué comprueba si hay un shebang, ¿es eso exec()o el caparazón? Quiero significativamente más internos
Josh