La E/S de bloques y por qué necesita una capa entera
Qué separa a un dispositivo de bloques de uno de carácter, por qué un array direccionable de sectores con latencia enorme y coste de seek no puede ir directo al driver, cómo el sector de 512 bytes es la unidad universal aunque el bloque físico sea mayor, y por qué el block layer se interpone entre el page cache y el driver como cintura estrecha que agrupa, reordena y trocea la E/S.
Un teclado entrega bytes uno tras otro y jamás pides “el byte número mil”: lo lees o no lo lees, en orden. Un disco es lo contrario: un array gigante de bloques numerados donde saltas a cualquier posición, pero cada salto puede costar milisegundos —una eternidad para una CPU que mide en nanosegundos—. Esa doble diferencia, direccionamiento aleatorio y latencia brutal, es la razón de que Linux no deje que un read() a disco llegue directo al driver. Entre el sistema de archivos y el hardware se interpone una capa entera cuyo único oficio es esconder y amortizar esa latencia: agrupar peticiones vecinas, reordenarlas, trocearlas y planificarlas. Ese es el block layer, y este nivel lo abre por su razón de ser.
- Distinguir la E/S de carácter —un flujo secuencial— de la de bloques —un array direccionable—.
- Entender por qué la latencia y el coste de seek obligan a agrupar y reordenar.
- Situar el block layer como cintura entre el page cache y el driver.
- Conocer el sector de 512 bytes como unidad universal del kernel.
Carácter frente a bloque: dos contratos distintos
Un dispositivo de carácter es un flujo: cada read() y cada write() que hace el proceso baja por el VFS y aterriza directamente en el file_operations de tu driver, byte a byte, sin noción de posición. Es el modelo del puerto serie, el terminal, el generador aleatorio. Un dispositivo de bloques es un array de bloques de tamaño fijo, direccionables al azar por su índice —el LBA, logical block address—, y respaldados por el page cache. Es el modelo del disco, el SSD, la partición.
/* dispositivo de caracter: el driver ve cada lectura tal cual */
static const struct file_operations char_fops = {
.owner = THIS_MODULE,
.read = mi_char_read, /* byte a byte, secuencial, sin direccion */
.write = mi_char_write,
};
/* dispositivo de bloques: NO hay .read ni .write */
static const struct block_device_operations blk_fops = {
.owner = THIS_MODULE,
.open = mi_blk_open,
.release = mi_blk_release,
.ioctl = mi_blk_ioctl,
/* la E/S no llega como llamada: llega como struct bio ya construida */
};
Fíjate en la ausencia clamorosa: block_device_operations no tiene read ni write. Cuando un proceso hace read() sobre /dev/nvme0n1 o sobre un archivo montado en él, el dato no cae en tu driver. Cae en el page cache (nivel 24), y solo cuando el kernel decide que hay que tocar el medio físico —un fallo de cache, un O_DIRECT, un writeback— fabrica una petición de E/S y la baja por el block layer. Tu driver de bloques nunca ve un read(): ve struct bio y struct request, ya agrupadas y planificadas por la capa que estudiamos aquí.
Dispositivo de carácter
Flujo secuencial de bytes sin posición. Cada read() y write() llega directo al driver por file_operations. Sin cache, sin fusión, sin reordenar. Es el puerto serie, el terminal, /dev/random.
Dispositivo de bloques
Array de bloques direccionable al azar por su LBA. La E/S pasa por el page cache y el block layer, que la fusiona, reordena y trocea. El driver solo ve bio y request. Es el disco, el SSD, la partición.
Por qué el bloque no puede ir directo: latencia, seek y agrupación
La asimetría de latencia lo explica todo. Un acceso a RAM cuesta unos 100 ns; un seek en un disco rotacional, más de 10 millones de ns —cinco órdenes de magnitud—. Incluso un NVMe moderno, que no tiene cabezal que mover, paga por cada comando un coste de doorbell, de descriptor y de interrupción de completado que ronda la decena de microsegundos. En ambos mundos, la conclusión es la misma: una operación grande y contigua es dramáticamente más barata que muchas pequeñas y dispersas.
De ahí nace el trabajo del block layer, que es cuádruple. Fusiona peticiones a sectores adyacentes en una sola, más grande, para amortizar el coste por comando. Reordena con un planificador de E/S (nivel 36) para servir juntas las que caen cerca. Trocea las que exceden el máximo que el hardware acepta de un tiro. Y contabiliza cada transferencia para las estadísticas de /proc/diskstats y el control de ancho de banda por cgroup. Nada de esto tendría sentido en un dispositivo de carácter, donde cada byte es único e irrepetible; en el bloque, donde el mismo sector puede pedirse mil veces y los vecinos viajan mejor juntos, es la diferencia entre un sistema usable y uno inservible.
/* sector_t SIEMPRE en unidades de 512 bytes, valga lo que valga el bloque fisico */
bio->bi_iter.bi_sector = 2048; /* offset de 1 MiB en el dispositivo */
set_capacity(disk, 20971520); /* 10 GiB = 20971520 sectores de 512 B */
El sector de 512 bytes: la unidad que nunca cambia
Aquí vive una convención que confunde a todo recién llegado y conviene fijar de una vez. En el block layer, un sector es siempre de 512 bytes, sin excepción, aunque el disco físico use bloques de 4096. El campo bi_sector y el argumento de set_capacity se cuentan siempre en esos sectores de 512 B, que son una unidad de direccionamiento del kernel, no una propiedad del medio. El tamaño real del bloque lógico del dispositivo se declara aparte, en logical_block_size de sus queue_limits, y puede ser 512 o 4096; pero la aritmética de direcciones del block layer usa siempre el sector de 512.
struct queue_limits lim = {
.logical_block_size = 4096, /* el bloque real del medio: un disco 4Kn */
.physical_block_size = 4096,
.max_hw_sectors = 2048, /* tope por peticion, en sectores de 512 B = 1 MiB */
};
Separar la unidad de direccionamiento (512 fijos) del tamaño de bloque real (variable) es lo que permite que el mismo código maneje un disquete antiguo y un NVMe de 2026 sin cambiar una línea de aritmética. Confundir ambos es la fuente clásica de bugs de “mi disco tiene la mitad o el doble de capacidad”.
El lugar del block layer en la pila
Puesto todo junto, la geometría es la de una cintura. Por arriba, cualquier sistema de archivos —ext4, xfs, btrfs— y el page cache producen struct bio. Por abajo, cualquier driver de almacenamiento —nvme, virtio-blk, scsi— consume struct request. En medio, el block layer traduce del uno al otro fusionando, planificando y troceando, sin que el filesystem sepa qué hardware hay debajo ni el driver sepa qué filesystem hay encima.
flowchart TD APP[Proceso llama a read o write] --> VFS[Capa VFS] VFS --> FS[Sistema de archivos ext4 o xfs] FS --> PC[Page cache] PC --> BL[Block layer] subgraph Block layer BIO[bio unidad de transferencia] --> RQ[struct request agrupada] RQ --> SCH[Planificador de peticiones] end BL --> BIO SCH --> DRV[Block driver nvme o scsi] DRV --> HW[Disco o SSD] style BL fill:#89b4fa,color:#11111b style HW fill:#a6e3a1,color:#11111b
Con O_DIRECT (nivel 24) el page cache se saltea, pero el block layer no: la bio se construye directamente sobre las páginas de usuario y baja por la misma cintura. No hay puerta trasera; todo tránsito hacia el medio persistente pasa por aquí.
Da un paso atrás y verás que toda esta capa es la respuesta de la ingeniería a un solo hecho físico intratable: entre la CPU y el almacenamiento persistente hay un abismo de latencia de cinco o seis órdenes de magnitud que ninguna tecnología ha cerrado ni cerrará pronto. Un dispositivo de carácter no necesita block layer porque su flujo es irrepetible y secuencial: no hay nada que fusionar, reordenar ni cachear, cada byte se consume una vez y desaparece. El bloque es lo opuesto en las tres dimensiones que importan —es direccionable, así que el mismo dato puede pedirse muchas veces; es aleatorio, así que el orden de servicio cambia el coste; y es lentísimo, así que agrupar paga—. Esas tres propiedades juntas hacen que entre el productor de E/S y el consumidor tenga que existir un intermediario que piense, y ese intermediario es el block layer. No es una capa de fontanería que reenvía peticiones: es un optimizador que reescribe el flujo de E/S para que el hardware lento parezca menos lento, exactamente igual que el page cache reescribe los accesos para que la RAM parezca infinita. Cuando en los cuatro niveles siguientes veas la bio, la arquitectura multi-cola, el camino de una petición y el registro de un gendisk, no los leas como estructuras sueltas: léelos como las cuatro piezas de esa única máquina de amortizar el abismo. La bio es cómo se describe una transferencia para poder manipularla; blk-mq es cómo se manipula sin que el paralelismo se ahogue en un cerrojo; el camino de la petición es la línea de montaje que la fusiona y planifica; y el gendisk es cómo se conecta esa fábrica a un medio concreto. Entender el block layer es entender que la persistencia rápida no la da el hardware solo: la da el hardware más una capa de software que lleva medio siglo aprendiendo a esconder su lentitud.
- Con
lsblk -tobservaLOG-SECyPHY-SECde tus discos, y explica por qué el kernel sigue direccionando en sectores de 512 aunquePHY-SECsea 4096. - Ejecuta
cat /proc/diskstatsantes y después de undd if=/dev/zero of=archivo bs=1M count=100, y localiza qué contadores subieron: son la contabilidad que hace el block layer. - Argumenta en tres líneas por qué
block_device_operationsno tiene.readni.writemientras quefile_operationssí, en términos de dónde nace la E/S. - Compara un
ddconbs=512y otro conbs=1Msobre el mismo dispositivo y correlaciona la diferencia de tiempo con el trabajo de fusión que se ahorra el segundo. - Razona qué papel juega
O_DIRECTrespecto al page cache y por qué, aun así, no se salta el block layer.