Entrada/salida y dispositivos
"Si no tuvieras dispositivos, el SO sería sólo un programa. Lo que lo hace SO es manejar todo lo que está afuera de la CPU."
Qué vas a aprender en este capítulo
Tu computadora interactúa con el mundo: discos, redes, teclados, pantallas, USB, GPUs. El kernel orquesta esa interacción. Vas a aprender la taxonomía de dispositivos, las tres formas en que la CPU se comunica con ellos (polling, interrupciones, DMA), cómo se organizan los drivers en Linux, cómo el SO planifica lecturas en discos, y por qué la idea unix "todo es un archivo" sigue siendo tan elegante después de 50 años.
7.1 La idea: una abstracción para todos
💡 Intuición
Un teclado, un disco, una impresora, una placa de red, una webcam — son piezas de hardware completamente distintas. Si el SO tuviera que tratarlas cada una con su propia API, tu programa sería imposible de escribir.
Unix tomó una decisión radical: todos los dispositivos son archivos. Leés el teclado con
read(). Escribís a la impresora conwrite(). La red, el sonido, hasta el reloj — todos archivos en/dev.
Eso te da una API uniforme. Un programa que copia archivos también puede copiar bytes desde una webcam a un archivo de video, o desde la red a la pantalla. Las syscalls son las mismas; los detalles los maneja el driver correspondiente.
Ejemplo cotidiano:
$ cat /dev/random | head -c 16 # 16 bytes aleatorios del kernel
$ cat /dev/null > archivo.log # vaciás un log
$ ls > /dev/tty # imprime a tu terminal
/dev/random, /dev/null, /dev/tty parecen archivos pero son interfaces a dispositivos o servicios del kernel. La uniformidad simplifica todo: una vez que entendés read/write, ya manejás "casi todo".
7.2 Categorías de dispositivos
📐 Fundamento
Unix los divide en tres tipos:
Dispositivos por bloques (block devices)
- Datos en bloques de tamaño fijo (típicamente 512 bytes o 4 KB).
- Acceso aleatorio: podés ir directamente al bloque N.
- Ejemplos: discos (HDD, SSD, NVMe), particiones, dispositivos RAID.
- En
/devaparecen como/dev/sda,/dev/sdb1,/dev/nvme0n1.
Dispositivos por caracteres (character devices)
- Datos como flujo de bytes, sin estructura fija.
- Acceso secuencial: leer byte tras byte.
- Ejemplos: teclados, mouse, terminales, puertos serie.
- En
/dev:/dev/tty1,/dev/random,/dev/null,/dev/zero.
Dispositivos especiales (pseudo-devices)
- No corresponden a hardware real, pero el kernel los expone como archivos.
- Ejemplos:
/dev/null— descarta todo lo que se le escriba./dev/zero— devuelve ceros infinitos al leer./dev/random,/dev/urandom— números aleatorios.
ls -l muestra el tipo en el primer carácter:
| Letra | Tipo |
|---|---|
- |
archivo regular |
d |
directorio |
l |
symlink |
b |
block device |
c |
character device |
p |
pipe (FIFO) |
s |
socket |
$ ls -l /dev/sda /dev/null /dev/tty
brw-rw---- 1 root disk 8, 0 may 5 /dev/sda
crw-rw-rw- 1 root root 1, 3 may 5 /dev/null
crw-rw-rw- 1 root tty 5, 0 may 5 /dev/tty
Los números (8, 0) y (1, 3) son major y minor — identifican qué driver y qué instancia.
7.3 Cómo se comunica la CPU con los dispositivos
📐 Fundamento
Tres mecanismos clásicos:
1. Polling (consulta activa)
La CPU pregunta al dispositivo si está listo, en un loop:
while (!dispositivo.listo()) {
; // espera ocupada
}
leer_dato();
Ventaja: simple. Desventaja: gasta CPU mientras espera. Si el dispositivo es lento (segundos), tirás millones de ciclos al vacío.
Útil para dispositivos ultrarrápidos donde el overhead de las interrupciones es mayor que el de pollear.
2. Interrupciones
El dispositivo interrumpe a la CPU cuando termina:
- La CPU le dice al dispositivo "empezá".
- La CPU se va a hacer otra cosa.
- Cuando el dispositivo termina, levanta una señal de interrupción.
- La CPU pausa lo que hacía, salta al manejador de interrupciones (ISR — interrupt service routine).
- El ISR atiende, y la CPU vuelve a lo que hacía.
Ventaja: la CPU se libera mientras el dispositivo trabaja. Desventaja: cada interrupción cuesta (cambio de contexto). Si hay muchas, interrupt storm y todo va lento.
Ejemplo: el teclado. Cada tecla que apretás genera una interrupción.
3. DMA (Direct Memory Access)
Para transferencias grandes, ni polling ni interrupciones por byte sirven — la CPU se ahogaría. DMA es un controlador especializado que mueve datos entre dispositivo y memoria sin involucrar a la CPU.
Ciclo:
- La CPU programa al controlador DMA: "transferí 1 MB del disco al buffer en RAM 0x80000".
- La CPU se va.
- DMA hace la transferencia, accediendo directamente a la RAM.
- Cuando termina, una sola interrupción avisa a la CPU.
Ventaja: la CPU dedica casi cero ciclos al transporte de datos. Discos, redes, GPUs — todos usan DMA.
El paisaje moderno: una placa de red de 10 Gbps mueve gigabytes por segundo sin pisar la CPU. Sin DMA, eso sería físicamente imposible.
7.4 Drivers
📐 Fundamento
Un driver es código del kernel que sabe cómo hablar con un tipo específico de hardware. Cada modelo de placa de red, cada SSD, cada GPU tiene su driver.
Estructura típica en Linux:
+------------------+
| Aplicación | ← read(), write()
+------------------+
| VFS | ← traduce a operaciones de dispositivo
+------------------+
| Driver genérico | ← p.ej. "block layer" o "tty layer"
+------------------+
| Driver específico| ← p.ej. driver de NVMe Samsung
+------------------+
| Hardware |
+------------------+
Por eso podés cambiar tu disco SATA por uno NVMe sin que cambien tus programas — la capa de bloques abstrae los detalles.
Drivers en Linux: módulos. Pueden estar:
- Compilados al kernel (built-in, siempre cargados).
- Como módulos (
.ko), cargables coninsmod/modprobey descargables conrmmod.
$ lsmod # módulos cargados
$ modprobe nombre_modulo # cargar
$ modinfo nombre # info del módulo
Trabajar sin reiniciar. Cuando enchufás un USB, el kernel detecta el evento, busca un driver compatible, lo carga. Comando dmesg muestra el log:
$ dmesg | tail -5
[12345.678] usb 2-1: new high-speed USB device number 4
[12345.700] usb-storage 2-1: USB Mass Storage device detected
[12345.701] sd 6:0:0:0: [sdb] 30277632 512-byte logical blocks
7.5 Planificación de disco (HDD)
📐 Fundamento
En un disco mecánico (HDD), los datos están en platos giratorios y un cabezal se mueve para leer/escribir. El movimiento del cabezal es caro (milisegundos). Si recibís 100 pedidos de bloques en posiciones aleatorias, el orden importa muchísimo.
Algoritmos clásicos
FCFS (orden de llegada). Simple, pero el cabezal va y viene sin sentido.
SSTF (Shortest Seek Time First). Atender el más cercano al cabezal actual. Eficiente, pero starvation posible para extremos.
SCAN (elevator). Cabezal va en una dirección hasta el extremo, después al revés. Como un ascensor. Razonable y justo.
C-SCAN. Variante: solo atiende en una dirección, vuelve al inicio sin atender. Más equitativo aún.
Ejemplo. Cabezal en 50, pedidos: 10, 22, 75, 90, 35.
| Algoritmo | Orden | Movimientos totales |
|---|---|---|
| FCFS | 50→10→22→75→90→35 | 40+12+53+15+55 = 175 |
| SSTF | 50→35→22→10→75→90 | 15+13+12+65+15 = 120 |
| SCAN (subiendo) | 50→75→90→100→35→22→10 | 25+15+10+65+13+12 = 140 |
En SSDs
No hay cabezal. Acceso aleatorio es casi tan rápido como secuencial. Los algoritmos de scheduling se simplifican mucho: ya no es necesario ordenar por posición, solo por prioridad.
Linux moderno usa multiqueue I/O scheduler (mq-deadline, kyber, bfq, none):
$ cat /sys/block/sda/queue/scheduler
[none] mq-deadline kyber bfq
Para un SSD, none suele ser óptimo (entregar en orden, no reordenar). Para un HDD, bfq o mq-deadline.
7.6 Comandos útiles
🛠️ En la práctica
Listar discos y particiones:
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 465.8G 0 disk
├─sda1 8:1 0 512M 0 part /boot/efi
└─sda2 8:2 0 465.3G 0 part /
Tasa de E/S:
$ iostat -x 1 5
Device r/s w/s rkB/s wkB/s await
sda 12.5 8.3 245.2 142.7 2.13
await = latencia promedio en ms. Si crece, hay contención.
Procesos haciendo I/O:
$ sudo iotop
Muestra en tiempo real qué procesos leen/escriben más.
Inspeccionar un dispositivo:
$ sudo smartctl -a /dev/sda # salud del disco (S.M.A.R.T.)
$ hdparm -I /dev/sda # información del HDD
$ nvme list # lista NVMe
Ver interrupciones:
$ cat /proc/interrupts | head
CPU0 CPU1 CPU2 CPU3
0: 42 0 0 0 IO-APIC 2-edge timer
1: 211 102 0 0 IO-APIC 1-edge i8042
...
i8042 es el teclado/mouse PS/2. Cada vez que apretás algo, esos contadores crecen.
7.7 Buffering y caching
📐 Fundamento
Para mitigar la lentitud de los dispositivos, el SO usa buffers y cachés.
Buffering. Acumular datos en RAM antes de transferir. Razones:
- Diferencia de velocidad. El productor produce más rápido que el consumidor (o al revés).
- Diferencia de tamaño de bloques. El programa escribe 10 bytes; el disco solo acepta bloques de 4 KB.
- Atomicidad. Acumular hasta tener un bloque completo y escribirlo de una vez.
Caching. Mantener datos recientes en RAM por si se vuelven a usar.
- Read cache. Lo viste con la page cache: la primera lectura va al disco, las siguientes son instantáneas.
- Write cache (write-back). Las escrituras van a RAM y eventualmente al disco. Rápido pero arriesgado.
- Write-through. Cada escritura va al disco. Lento pero seguro.
sync y fsync:
fsync(fd); // forzar que los datos del fd lleguen a disco
sync(); // todos los datos pendientes
Programas críticos (bases de datos, transacciones) llaman fsync antes de confirmar al usuario.
7.8 Protocolos modernos: NVMe, NIC, USB
🛠️ En la práctica
NVMe (Non-Volatile Memory Express). Reemplazo de SATA para SSDs. Múltiples colas paralelas (hasta 64K), bajo overhead, latencias de microsegundos. Aprovecha que SSD no necesita el modelo "una cola, un cabezal" de HDD.
Cada CPU tiene su propia cola → no hay contención entre cores. Por eso un NVMe moderno hace 1+ millón de IOPS.
NIC modernas (red). 10/25/100 Gbps requieren:
- DMA scatter-gather (transferir páginas no contiguas).
- TSO (TCP Segmentation Offload) — la NIC fragmenta paquetes, no la CPU.
- RSS (Receive Side Scaling) — diferentes flujos a diferentes CPUs.
Sin esos features de hardware, una NIC de 10 Gbps saturaría una CPU completa solo para mover bytes.
USB. Bus serial complejo. El kernel implementa USB host controller + drivers por clase (mass storage, HID — teclado/mouse, video, audio, ...). Cuando enchufás un mouse USB nuevo, el kernel detecta su descriptor, identifica que es HID, carga el driver HID genérico — sin necesidad de instalar nada.
7.9 Ejercicios
✏️ Ejercicio 7.1 — Identificar tipos
Para cada uno, decí si es block, character o pseudo:
a. /dev/sda1
b. /dev/random
c. /dev/null
d. /dev/tty
e. /dev/loop0
f. /dev/nvme0n1
Solución
a. block (partición de disco) b. pseudo / character (genera bytes aleatorios) c. pseudo / character (descarte) d. character (terminal) e. block (loopback — un archivo se "monta" como bloque) f. block (NVMe SSD)
✏️ Ejercicio 7.2 — Calcular movimientos del cabezal
Cabezal en pista 100. Pedidos: 23, 89, 132, 42, 187, 90, 67. Calculá el movimiento total con FCFS, SSTF y SCAN (dirección inicial: hacia arriba).
Solución
FCFS: 100→23→89→132→42→187→90→67. 541.
SSTF: elegir el más cercano cada vez.
- 100 → 89 (11)
- 89 → 90 (1)
- 90 → 67 (23)
- 67 → 42 (25)
- 42 → 23 (19)
- 23 → 132 (109)
- 132 → 187 (55)
Total: 11+1+23+25+19+109+55 = 243.
SCAN (subiendo, asumiendo límite 200):
- 100 → 132 → 187 → 200 (límite, gira) → 90 → 89 → 67 → 42 → 23. Total: 277.
(Variantes incluyen no llegar al límite si no hay pedidos más allá. Resultados pueden variar 10-20% según convención.)
✏️ Ejercicio 7.3 — DMA vs polling
Tu programa lee 1 GB de un SSD. La CPU corre a 3 GHz. El SSD entrega 1 GB/s.
a. Sin DMA, con polling, ¿qué % de ciclos se gastan polleando? b. Con DMA + 1 sola interrupción al final, ¿qué % se gasta en transferir?
Solución
a. Polling continuo durante 1 segundo de transferencia. La CPU gasta ~100% de su tiempo preguntando "¿listo?" en vez de hacer trabajo útil. Catastrófico para sistemas multitarea.
b. Una interrupción al final cuesta ~1 µs. En 1 segundo de transferencia, eso es 1/1,000,000 = 0.0001% del tiempo. La CPU está libre el resto.
Conclusión: DMA es decisivo para transferencias grandes.
7.10 Para profundizar
- Tanenbaum & Bos cap. 5.
- OSTEP "I/O Devices" (cap 36-37).
- Linux Device Drivers (Corbet, Rubini) — el libro para escribir drivers.
- Próximo capítulo: Seguridad y permisos — cómo el SO protege a unos de otros.
Definiciones nuevas: dispositivo de bloques, de caracteres, polling, interrupción, ISR, DMA, driver, módulo del kernel, scheduling de disco, FCFS, SSTF, SCAN, page cache, fsync, NVMe.