
Cuando ejecutás un programa en Linux con un simple ./app, todo parece inmediato, casi trivial. Escribís el comando, presionás Enter y en cuestión de milisegundos tenés un proceso corriendo. Pero esa aparente simplicidad esconde una secuencia compleja de eventos profundamente coordinados entre el sistema operativo, la memoria, el kernel y el propio binario.
Muchas veces perdemos de vista lo que realmente sucede en el nivel más fundamental. Sin embargo, entender ese “debajo” no solo es interesante: es clave para depurar, optimizar y asegurar sistemas.
Este artículo busca desentrañar ese recorrido: desde que ejecutás un binario hasta que el programa empieza a correr. Vamos a explorar cómo funciona el formato ELF, cómo interviene el kernel, qué rol cumplen las system calls y por qué el dynamic linking es tan importante.
Supongamos el caso más clásico posible:
Hasta acá, todo parece simple. Pero en realidad, el shell no “ejecuta” directamente ese programa. Lo que hace es invocar una llamada al sistema: execve.
Antes de ejecutar un programa, el shell realiza dos pasos fundamentales:
Esto implica algo clave: el proceso que ejecuta tu programa no es el shell, sino una copia del mismo que luego se transforma completamente.
Si corrés:
Vas a ver algo como:
Eso significa que hola es un proceso hijo del shell.
fork() no crea un proceso desde cero, sino una copia del proceso actual. Pero no duplica toda la memoria inmediatamente gracias a un mecanismo llamado copy-on-write.
Esto significa que padre e hijo comparten memoria hasta que uno de los dos la modifica.
En Linux, todo está dividido en dos espacios:
Los programas no pueden acceder directamente al hardware. Necesitan pedirle al kernel que haga cosas por ellos.
Ahí entran las system calls.
Una system call es la forma en que un programa le dice al kernel:
Por ejemplo, cuando tu programa imprime algo, no escribe directamente en la pantalla. Hace una syscall:
Donde:
1 es stdoutint main() {
write(1, “Hola directo al kernel\n”, 23);
return 0;
}
Este programa no usa printf, sino que habla directamente con el kernel.
Cuando compilás un programa, el resultado no es simplemente “código máquina”. Es un archivo estructurado bajo el formato ELF (Executable and Linkable Format).
Este formato define cómo debe cargarse y ejecutarse el programa.
Un archivo ELF tiene tres partes clave:
Los primeros bytes del archivo contienen un identificador único:
Esto corresponde a:
Es lo que le dice al sistema: “esto es un ejecutable ELF”.
Podés verificarlo con:
Salida típica:
El kernel no carga todo el archivo en memoria. Solo carga los segmentos necesarios.
Los más importantes son:
La mayoría de los programas en Linux no son autosuficientes. Dependen de librerías externas como libc.
Esto se llama linking dinámico.
En lugar de incluir todo el código en el binario, se carga en tiempo de ejecución.
Salida:
Esto significa que tu programa necesita libc para funcionar.
Cuando el kernel detecta que el binario necesita librerías, no ejecuta directamente el programa.
Primero ejecuta un intermediario: el dynamic linker.
Este se encarga de:
Recién después de eso se ejecuta tu main().
Dentro del ELF, el contenido se organiza en secciones:
.text: código.data: variables inicializadas.bss: variables no inicializadas.rodata: constantesEsto te muestra todas las secciones.
Cada proceso tiene su propio espacio de memoria virtual.
Pero no toda esa memoria está realmente en RAM.
El uso real se mide con:
En Linux, todo es un archivo. Incluso los procesos.
Cada proceso tiene un directorio:
Ahí podés ver:
Esto muestra los descriptores abiertos.
Todo proceso tiene tres descriptores por defecto:
Redirigir salida:
Acá stdout deja de ser la terminal y pasa a ser un archivo.
strace permite ver las system calls en tiempo real.
Vas a ver cosas como:
execvemmapopenatwriteEsto muestra todo lo que ocurre antes de que veas el output.
Resumiendo todo el proceso:
./holafork()execve()Todo esto ocurre en milisegundos.
Imaginá un servidor que ejecuta múltiples procesos.
Cada vez que hacés:
Pasa exactamente este flujo:
Entender esto te permite:
Detrás de cada ejecución en Linux hay una coreografía precisa entre el usuario, el kernel y el binario. Lo que parece inmediato es en realidad el resultado de décadas de diseño en sistemas operativos.
Entender ELF, las system calls, el rol del kernel y el dynamic linking no es solo conocimiento teórico. Es una herramienta poderosa para cualquier persona que trabaje con sistemas, seguridad o desarrollo.
Cuando algo falla, cuando un programa no arranca o cuando el rendimiento no es el esperado, muchas veces la respuesta está en estos detalles que normalmente ignoramos.
Y ahí es donde deja de ser “magia” y pasa a ser ingeniería.
Créditos a Facu de la Cruz (tty0 en telegram) que nos basamos en un PDF de su autoría.