3 puntos por GN⁺ 2024-04-09 | Aún no hay comentarios. | Compartir por WhatsApp
  • Explorando el mundo de abstracciones oculto detrás del programa moderno Hello World

    • Este artículo trata sobre un programa Hello World escrito en C. Entre los lenguajes de alto nivel, en los que no hace falta pensar en qué hace el lenguaje antes de que el programa realmente funcione en un intérprete/compilador/JIT, C es el de más bajo nivel.
    • Originalmente se intentó escribir para que cualquiera con experiencia en programación pudiera entenderlo, pero parece que ayuda tener al menos conocimientos de C o ensamblador.
  • Inicio del programa Hello World

    • Todo el mundo está familiarizado con el programa Hello World. En Python, probablemente el primer programa que escribiste fue algo como print('Hello World!').
    • En este artículo veremos Hello World escrito en el lenguaje de programación C. En C no puedes invocar un intérprete para ejecutar el programa. Primero debes ejecutar un compilador para convertirlo en código máquina que el procesador de la computadora pueda ejecutar directamente.
  • Análisis de nuestro programa

    • Si analizamos el archivo del programa compilado, podemos ver que es un ejecutable ELF y que es para la arquitectura de conjunto de instrucciones x86-64.
    • Un ejecutable ELF es el equivalente en Linux a un archivo .exe de Windows.
    • x86-64 es la arquitectura de CPU usada en las PC desde la introducción de la IBM PC en 1981.
    • Este archivo contiene código máquina, el único lenguaje que la CPU puede entender.
  • Análisis del código ensamblador

    • Buscamos el entry point, que es la dirección de inicio del programa, y analizamos el código ensamblador.
    • El lenguaje ensamblador es una representación legible para humanos del código máquina.
    • Se puede ver código de inicialización agregado automáticamente por el compilador, o más precisamente por el enlazador, y también que llama a la función __libc_start_main.
    • Pero ese código no está definido en nuestro programa, sino en otro lugar.
  • Biblioteca estándar de C

    • La función __libc_start_main está definida en libc.so.6, la biblioteca estándar de C de nuestro sistema.
    • La biblioteca estándar de C es un conjunto de rutinas y funciones que usan casi todos los programas de nuestra computadora.
    • La biblioteca de C realiza tareas de inicialización y luego llama a la función main() que escribimos. Cuando main() retorna, finaliza el programa con el código de salida que proporcionamos.
  • Análisis de la función main()

    • En la función main() se configura el stack frame, se establece la dirección de la cadena Hello World como argumento de la llamada a función y luego se llama a puts().
    • puts() apareció en lugar de printf() porque el compilador aplicó una optimización. printf() es complejo, pero puts() simplemente imprime una cadena sin formato.
  • La cadena Hello World

    • La cadena tiene la forma de "Hello World!" seguida por un terminador NULL.
    • En C no hay información de longitud asociada a las cadenas, por lo que se usa un terminador NULL para marcar su final. Si no hubiera terminador NULL, el programa leería memoria no permitida y terminaría con un Segmentation Fault.
    • Debido a una optimización del compilador, el salto de línea (\n) usado en printf() fue eliminado. puts() agrega un salto de línea después de imprimir la cadena.
  • La función puts()

    • La función puts() vuelve a llamar código dentro de la biblioteca estándar.
    • Si observamos el código de glibc, podemos ver que las llamadas siguen el orden _IO_puts -> _IO_new_file_xsputn, pero el código es complejo y difícil de explicar.
    • En el caso de musl libc, es un poco más simple. Las llamadas siguen la secuencia puts -> fputs -> fwrite -> __fwritex -> __stdio_write -> syscall.
  • Llamadas al sistema

    • Por grande que sea la biblioteca de C, no puede comunicarse directamente con el hardware. Eso solo puede hacerlo el kernel.
    • Por eso, una llamada a puts() termina finalmente pidiéndole al sistema operativo que haga algo. Aquí se trata de escribir la cadena en el flujo de salida.
    • musl libc usa la llamada al sistema writev, que permite escribir varios buffers de una sola vez.
    • Una llamada al sistema se realiza configurando los parámetros en registros y ejecutando la instrucción syscall. Entonces el control pasa al kernel, que lee los parámetros y ejecuta la llamada al sistema.
  • Kernel

    • El kernel de Linux debe realizar la acción solicitada mediante la llamada al sistema. La llamada al sistema write le indica al kernel que escriba en un archivo o flujo abierto del sistema de archivos.
    • write recibe tres parámetros: el file descriptor al que se va a escribir, el buffer que se va a escribir y la cantidad de bytes a escribir.
    • Dónde se escribe realmente depende de la situación. Si es un emulador de terminal, aparece como una terminal virtual (pty); si es un inicio de sesión remoto, se entrega a sshd; si es una terminal física, va a un adaptador serial-USB. Si es una consola de framebuffer, el kernel renderiza el texto y lo muestra en la pantalla.
  • Conclusión

    • Los sistemas de software modernos funcionan de forma muy compleja y sofisticada sobre el hardware, así que intentar comprender por completo una pequeña tarea que realiza la computadora termina siendo inútil.
    • Para explicarlo todo hubo que omitir muchas partes.
    • Enviar el mensaje Hello World es solo una de las innumerables llamadas al sistema y programas que se están ejecutando ahora mismo en la computadora.

La opinión de GN⁺

  • Es un texto que muestra cómo cada capa de un sistema de cómputo, mediante abstracciones, oculta la complejidad de la capa inferior y permite a los desarrolladores crear aplicaciones con comodidad.
  • Al mismo tiempo, te hace notar cuántas cosas ocurren por debajo para que se ejecute una sola línea de una aplicación, y también por qué depurar es tan difícil.
  • Creo que todo programador debería conocer bien al menos el sistema que está por debajo del lenguaje que usa principalmente. No hace falta saberlo todo, pero sí es importante entender cómo funciona realmente lo que está abstraído.
  • Incluso si usas lenguajes de alto nivel, estudiar conceptos de programación de sistemas como la estructura de memoria, stack y heap, o las llamadas al sistema, será de gran ayuda para depuración y optimización de rendimiento.
  • Los desarrolladores de aplicaciones casi nunca tendrán que tocar directamente un compilador o la biblioteca de C, pero entender cómo el programa que escribiste termina usando el sistema es, en mi opinión, esencial para convertirse en un buen programador.

Aún no hay comentarios.

Aún no hay comentarios.