Sistemas Informáticos: Una Perspectiva para Programadores (Edición Global)
Una revisión profunda sobre cómo los sistemas informáticos ejecutan programas y almacenan información. Este curso cierra la brecha entre la programación de alto nivel y el hardware subyacente, cubriendo la representación a nivel de máquina, arquitectura del procesador, jerarquía de memoria y programación concurrente.
Descripción del curso
📚 Resumen del contenido
Una profundización completa sobre cómo los sistemas informáticos ejecutan programas y almacenan información. Este curso pone de relieve la brecha entre la programación de alto nivel y el hardware subyacente, abarcando la representación a nivel de máquina, la arquitectura del procesador, la jerarquía de memoria y la programación concurrente.
Domina el arte de la programación de sistemas comprendiendo la interfaz entre hardware y software.
Autor: Randal E. Bryant, David R. O'Hallaron
Agradecimientos: Apoyado por los estudiantes e instructores del curso 15-213 en la Universidad Carnegie Mellon. Los agradecimientos incluyen contribuciones de Manasa S. y Mohit Tahiliani.
🎯 Objetivos de aprendizaje
- Identificar cómo se representa la información usando bits y contexto dentro de un sistema.
- Rastrear las cuatro etapas del sistema de compilación desde el código fuente hasta el ejecutable.
- Describir la estructura organizativa del hardware y la naturaleza jerárquica de los dispositivos de almacenamiento.
- Convertir entre notaciones decimal, binaria y hexadecimal y explicar el direccionamiento a nivel de máquina (Endianness).
- Realizar operaciones a nivel de bits y lógicas en C y predecir los resultados de desplazamientos aritméticos.
- Analizar codificaciones de enteros para identificar posibles vulnerabilidades por desbordamiento y errores de conversión.
- Analizar la correspondencia entre construcciones en C (bucles, ramificaciones, procedimientos) y instrucciones de ensamblaje x86-64.
- Descomponer la pila en tiempo de ejecución para explicar cómo se pasan parámetros, se almacenan variables locales y se gestionan llamadas recursivas.
- Evaluar los diseños de memoria para estructuras de datos heterogéneas y aplicar reglas de alineación para calcular los requisitos totales de almacenamiento.
- Definir el estado visible por el programador Y86-64 y codificar/descodificar instrucciones en secuencias de bytes.
🔹 Lección 1: Una visita guiada a los sistemas informáticos
Resumen: Esta lección proporciona una visión general completa sobre cómo los sistemas informáticos representan la información, traducen programas y ejecutan instrucciones mediante interacciones complejas entre hardware y software. Explora el recorrido de un programa desde el código fuente hasta su ejecución, el papel crítico de la jerarquía de memoria para superar la brecha entre procesador y memoria, las abstracciones ofrecidas por el sistema operativo y las leyes matemáticas que rigen el rendimiento del sistema y el paralelismo.
Resultados de aprendizaje:
- Identificar cómo se representa la información usando bits y contexto dentro de un sistema.
- Rastrear las cuatro etapas del sistema de compilación desde el código fuente hasta el ejecutable.
- Describir la estructura organizativa del hardware y la naturaleza jerárquica de los dispositivos de almacenamiento.
🔹 Lección 2: Representación y manipulación de la información
Resumen: Esta lección explora cómo los ordenadores digitales representan y manipulan la información a nivel de bit. Cubre la transición desde la notación hexadecimal y los tamaños de palabra a nivel de máquina hasta las codificaciones complejas de números enteros (sin signo y complemento a dos) y números de punto flotante (IEEE 754). Los estudiantes analizarán las propiedades matemáticas de la aritmética informática, incluyendo las implicaciones de seguridad del desbordamiento y los matices del redondeo en sistemas de precisión finita.
Resultados de aprendizaje:
- Convertir entre notaciones decimal, binaria y hexadecimal y explicar el direccionamiento a nivel de máquina (Endianness).
- Realizar operaciones a nivel de bits y lógicas en C y predecir los resultados de desplazamientos aritméticos.
- Analizar codificaciones de enteros para identificar posibles vulnerabilidades por desbordamiento y errores de conversión.
🔹 Lección 3: Representación a nivel de máquina de programas
Resumen: Esta lección ofrece una profundización completa sobre cómo los programas de alto nivel en C se transforman en código máquina x86-64. Cubre la arquitectura fundamental del procesador, incluyendo registros y pila, la implementación del flujo de control (condicionales, bucles y sentencias switch), la mecánica de llamadas a procedimientos y recursividad, y la representación a nivel de máquina de estructuras de datos complejas como arreglos, structs y unions. Finalmente, aborda la seguridad del sistema mediante análisis de desbordamiento de búferes y las instrucciones especializadas utilizadas para la aritmética de punto flotante.
Resultados de aprendizaje:
- Analizar la correspondencia entre construcciones en C (bucles, ramificaciones, procedimientos) e instrucciones de ensamblaje x86-64.
- Descomponer la pila en tiempo de ejecución para explicar cómo se pasan parámetros, se almacenan variables locales y se gestionan llamadas recursivas.
- Evaluar los diseños de memoria para estructuras de datos heterogéneas y aplicar reglas de alineación para calcular los requisitos totales de almacenamiento.
🔹 Lección 4: Arquitectura del procesador
Resumen: Esta lección explora la arquitectura fundamental de un procesador, centrándose en la transición desde una implementación secuencial (SEQ) hasta una implementación con tubería de alto rendimiento (PIPE) utilizando la Arquitectura de Conjunto de Instrucciones Y86-64 (ISA). Los estudiantes analizarán cómo se codifican las instrucciones, se procesan a través de etapas discretas (Fetch, Decode, Execute, Memory, Write-Back) y cómo se gestionan los peligros de hardware mediante lógica de control, bloqueos y reenvío para maximizar el rendimiento.
Resultados de aprendizaje:
- Definir el estado visible por el programador Y86-64 y codificar/descodificar instrucciones en secuencias de bytes.
- Implementar lógica de control de hardware usando HCL (Lenguaje de Control de Hardware) para circuitos combinacionales y secuenciales.
- Rastrear el flujo de instrucciones a través de las seis etapas de un procesador secuencial e identificar el impacto del reloj.
🔹 Lección 5: Optimización del rendimiento del programa
Resumen: Esta lección explora el enfoque sistemático para mejorar el rendimiento del programa comprendiendo la interacción entre el código de alto nivel, los compiladores optimizadores y las arquitecturas de microprocesadores modernas. Los estudiantes aprenderán a identificar "bloqueadores de optimización" como el aliasing de memoria, aplicar transformaciones de bajo nivel como desenrollado de bucles y reasociación, y utilizar herramientas de perfilado como GPROF para atacar eficazmente los cuellos de botella de rendimiento.
Resultados de aprendizaje:
- Identificar y mitigar bloqueadores de optimización, incluyendo el aliasing de memoria y la sobrecarga de llamadas a procedimientos.
- Cuantificar el rendimiento del programa usando la métrica Ciclos por Elemento (CPE).
- Aplicar transformaciones como desenrollado de bucles, múltiples acumuladores y reasociación para explotar el paralelismo a nivel de instrucción.
🔹 Lección 6: La jerarquía de memoria
Resumen: Esta lección explora el diseño estructural y funcional de la jerarquía de memoria, centrándose en los compromisos entre velocidad, costo y capacidad de almacenamiento. Detalla las tecnologías que impulsan los sistemas modernos —desde SRAM y DRAM hasta Discos y SSDs— y explica cómo el Principio de Localidad (Temporal y Espacial) permite que pequeñas memorias caché rápidas mejoren significativamente el rendimiento del programa. Los estudiantes aprenderán a analizar el mapeo de caché (Directo, Asociativo por Conjuntos, Asociativo Completo) y aplicar técnicas de optimización como reordenamiento de bucles y bloques para escribir código amigable con caché.
Resultados de aprendizaje:
- Distinguir entre tecnologías de memoria SRAM, DRAM, ROM y Flash y sus roles en la jerarquía.
- Calcular la capacidad de almacenamiento de disco y el tiempo total de acceso basándose en geometría y componentes operativos.
- Analizar direcciones de memoria para determinar índices de conjunto de caché, etiquetas y desplazamientos de bloque según diferentes estrategias de mapeo.
🔹 Lección 7: Enlazado
Resumen: Esta lección explora el proceso crítico a nivel del sistema de enlazado, que agrega código y datos en un único archivo que puede cargarse en memoria y ejecutarse. Los estudiantes pasarán del código fuente al binario ejecutable, comprendiendo cómo los enlazadores resuelven referencias simbólicas, fusionan secciones mediante relocalización y gestionan bibliotecas estáticas y dinámicas. La lección concluye con técnicas avanzadas como la interposición de bibliotecas y el código independiente de posición (PIC) usadas en bibliotecas compartidas modernas.
Resultados de aprendizaje:
- Rastrear la transformación de archivos fuente a través del controlador de compilación hasta un ejecutable final.
- Analizar archivos objeto ELF para identificar tipos de símbolos y organización de secciones.
- Aplicar reglas de resolución de símbolos para gestionar nombres duplicados y dependencias en tiempo de enlace.
🔹 Lección 8: Flujo de control excepcional
Resumen: Esta lección explora el Flujo de Control Excepcional (ECF), el mecanismo mediante el cual un sistema informático reacciona ante cambios en el estado del sistema. Examinamos cómo se implementa el ECF en todos los niveles del sistema, desde excepciones disparadas por hardware hasta cambios de contexto y control de procesos a nivel de sistema operativo (fork, wait, execve), pasando por señales y saltos no locales a nivel de software. Los estudiantes aprenderán a gestionar la concurrencia, manejar errores del sistema y escribir código robusto y seguro frente a señales.
Resultados de aprendizaje:
- Distinguir entre las cuatro clases de excepciones a nivel de hardware (Interrupciones, Trampas, Fallos, Abortos) y sus mecanismos de manejo.
- Gestionar ciclos de vida de procesos usando llamadas al sistema para creación (
fork), recolección (waitpid) y ejecución (execve). - Implementar manejadores de señales seguros que consideren la concurrencia, señales no encoladas y seguridad frente a señales asincrónicas.
🔹 Lección 9: Memoria virtual
Resumen: Esta lección explora la Memoria Virtual (MV) como una abstracción fundamental que proporciona a cada proceso un espacio de direcciones grande, contiguo y privado. Cubrimos sus tres funciones principales: una herramienta para el almacenamiento eficiente en DRAM, un mecanismo de gestión y protección de memoria, y una base para el mapeo de memoria. Además, la lección profundiza en la mecánica de la traducción de direcciones (TLB), la asignación dinámica de memoria (gestión de montículo) y los principios de recolección automática de basura, concluyendo con fallos críticos relacionados con la memoria en programación en C.
Resultados de aprendizaje:
- Distinguir entre direccionamiento físico y virtual y describir el papel de la Unidad de Gestión de Memoria (MMU).
- Realizar la traducción de direcciones virtuales a físicas usando Tablas de Página y la Memoria de Traducción de Direcciones (TLB).
- Analizar e implementar estrategias de asignación de memoria dinámica, incluyendo listas implícitas/explícitas y fusión.
🔹 Lección 10: Entrada/Salida a nivel de sistema
Resumen: Esta lección explora la interfaz fundamental entre el sistema operativo Linux y los programas de aplicación para realizar entradas y salidas. Cubre las llamadas al sistema Unix básicas, los diversos tipos de archivos encontrados en el sistema de archivos Linux y las estructuras de datos del kernel utilizadas para gestionarlos. Además, introduce el paquete I/O Robusto (RIO) para manejar "conteos cortos" y proporciona directrices para elegir entre I/O estándar e I/O a nivel de sistema en distintos contextos de programación, como la programación de redes.
Resultados de aprendizaje:
- Implementar operaciones básicas de archivos usando la interfaz Unix I/O (
open,close,read,write). - Diferenciar entre archivos regulares, directorios y enlaces mientras consulta metadatos de archivos usando
stat. - Utilizar el paquete RIO para realizar operaciones de I/O robustas, con buffer y sin buffer.
🔹 Lección 11: Programación de redes
Resumen: Esta lección explora la arquitectura fundamental de aplicaciones basadas en redes, centrada en el modelo de programación Cliente-Servidor y la Internet IP global. Los estudiantes aprenderán a navegar la interfaz de sockets —la API principal para comunicación de red a nivel de sistema— y progresarán hacia la implementación de un servidor web funcional (TINY) capaz de entregar tanto archivos estáticos como contenido dinámico a través de la Interfaz de Puerta Común (CGI).
Resultados de aprendizaje:
- Comprender el ciclo solicitud-respuesta del modelo cliente-servidor y la jerarquía de hardware/software de la Internet IP global.
- Manipular y convertir direcciones IP, nombres de dominio y estructuras de socket usando funciones independientes del protocolo como
getaddrinfo. - Implementar un servidor web iterativo robusto y programas CGI que utilicen control de procesos y redirección de I/O para servir contenido dinámico.
🔹 Lección 12: Programación concurrente
Resumen: Esta lección explora los modelos fundamentales de concurrencia: procesos, multiplexión de E/S y hilos. Proporciona una profundización en la sincronización usando semáforos para resolver condiciones de carrera, patrones arquitectónicos comunes como productor-consumidor y servidores prehilados, y las métricas utilizadas para evaluar el rendimiento paralelo. Finalmente, aborda problemas críticos de fiabilidad, incluyendo seguridad de hilos, reentrancia y prevención de muertes en espera.
Resultados de aprendizaje:
- Distinguir entre modelos de concurrencia basados en procesos, multiplexión de E/S y hilos.
- Aplicar operaciones de semáforos (P y V) para asegurar exclusión mutua y resolver patrones de sincronización.
- Calcular métricas de rendimiento paralelo como aceleración y eficiencia bajo diferentes leyes de escalado.