¿Cómo ejecuta un shell un programa?

11

Si compilo un programa usando gcc e intento ejecutarlo desde el shell bash, ¿cuál es la secuencia exacta de pasos seguidos por bash para ejecutarlo?

Sé que fork(), execve(), loader, dynamic linker(y otras cosas) están involucrados, pero puede alguien dar una secuencia exacta de pasos y alguna referencia de lectura adecuado?

Editar:

De las respuestas, parece que la pregunta podría implicar muchas posibilidades. Quiero limitarme a un caso simple:

(test.c solo imprime hello world)

$ gcc test.c -o test
$ ./test

¿Cuáles serán los pasos en el caso anterior ( ./test), específicamente relacionados con el programa de inicio bash en algún proceso secundario, cargando, vinculando, etc.?

Jake
fuente
44
Los invito a leer lwn.net/Articles/630727
cuonglm
3
¿Por qué no probar 'strace bash -c' test '?
Sergiy Kolodyazhnyy
2
Parece que un libro de texto decente de Sistemas Operativos sería un buen recurso para el OP. Intentar aprender cómo funcionan los sistemas operativos haciendo preguntas individuales como esta probablemente no sea un proceso productivo.
Barmar
Sería útil ver un ejemplo mínimo de un shell: brennan.io/2015/01/16/write-a-shell-in-c
jinawee

Respuestas:

5

Bueno, la secuencia exacta puede variar, ya que puede haber un alias o función de shell que primero se expande / interpreta antes de que se ejecute el programa real, y luego las diferencias para un nombre de archivo calificado ( /usr/libexec/foo) frente a algo que se buscará en todos los directorios de la PATHvariable de entorno (solo foo). Además, los detalles de la ejecución pueden complicar las cosas, ya que foo | bar | zotrequiere más trabajo para el shell (cierto número de fork(2), dup(2)y, por supuesto pipe(2), entre otras llamadas al sistema), mientras que algo así exec fooes mucho menos trabajo ya que el shell simplemente se reemplaza con el nuevo programa (es decir, no lo hace fork). También son importantes los grupos de procesos (especialmente el grupo de procesos en primer plano, todos los PID que comenSIGINTcuando alguien comienza a mezclar en Ctrl+ C, sesiones, y si el trabajo se ejecutará en segundo plano, monitoreado ( foo &) o en segundo plano, ignorado ( foo & disown). Los detalles de redirección de E / S también cambiarán las cosas, por ejemplo, si el shell ( foo <&-) cierra la entrada estándar o si un archivo se abre como stdin ( foo < blah).

straceo similar será informativo sobre las llamadas específicas del sistema realizadas a lo largo de este proceso, y debe haber páginas de manual para cada una de esas llamadas. La lectura adecuada a nivel de sistema sería cualquier número de capítulos de "Programación avanzada en el entorno UNIX" de Stevens, mientras que un libro de shell (por ejemplo, "De Bash a Z Shell") cubrirá el lado de shell de las cosas con más detalle.

thrig
fuente
Edité la pregunta para reducirla a un caso simple
Jake el
1

Suponiendo un shell de ejemplo de libro de texto (para mayor claridad del código) que ya se está ejecutando (por lo que se hace el enlazador dinámico), los comandos que menciona requerirán que el shell realice las siguientes llamadas al sistema:

  • leer: obtiene el siguiente comando en este caso gcc
  • fork: se necesitan dos procesos, asumimos que el padre tiene pid 500 y el niño para ilustración.
  • el padre llamará a wait (501), mientras que el niño llamará a exec. En este punto, el shell ya no se ejecuta en pid 501. gcc realiza muchas llamadas al sistema, como mínimo abrir, cerrar, leer, escribir, chmod, fork, exec, esperar y salir.
  • cuando gcc llama a la salida, la espera volverá, se llama a escribir para mostrar el mensaje y el proceso se repetirá.

Los comandos más complicados, por supuesto, agregan más complicaciones a esta secuencia básica. Dos ejemplos más simples de complicaciones básicas son la redirección io básica en la que se inserta una secuencia abierta, cerrada y duplicada entre la bifurcación y el exec y procesos en segundo plano donde se omite la espera (y se agrega otra espera a un controlador sigchld).

hildred
fuente
Pequeña adición: la pregunta se refiere a la carga y la vinculación dinámica. Todo el código que está vinculado estáticamente, es decir, realmente incluido en el archivo del programa, lo realiza el núcleo antes de que se inicie el programa. Las bibliotecas cargadas dinámicamente, es decir, archivos separados, son manejadas por el propio programa antes de iniciar main (). El código para esto es agregado automáticamente por gcc.
Stig Hemmer