flowchart LR
P[Proceso de usuario] --> SC[Llamada al sistema<br>read/write]
SC --> FS[Sistema de archivos]
FS --> BC[Buffer caché<br>del kernel]
BC --> DR[Driver de bloque]
DR --> DISK[(Disco)]
BC -. acierto .-> FS
DISK -. bloque leído .-> BC
9 Buffer Caché en Sistemas Operativos
El acceso a disco es mucho más lento que el acceso a memoria. Por eso, los sistemas operativos clásicos de la familia Unix incorporaron una capa intermedia llamada buffer caché, cuyo objetivo es conservar en memoria copias temporales de bloques de disco. Esta unidad estudia el modelo tradicional de Unix System V: buffers, listas libres, colas hash y las situaciones que aparecen cuando el kernel intenta recuperar un bloque.
9.1 Objetivos de Aprendizaje
- Definir el concepto de buffer caché y ubicarlo dentro del camino de E/S del sistema operativo.
- Explicar la estructura de un buffer en Unix/System V, distinguiendo cabecera y área de datos.
- Reconocer los estados principales de un buffer: ocupado, libre, válido, sucio, solicitado y con error.
- Describir la organización del buffer pool mediante free list y hash queues.
- Analizar las distintas situaciones de recuperación de un buffer cuando el kernel necesita un bloque de disco.
- Evaluar ventajas y desventajas del buffer caché frente al acceso directo al dispositivo.
9.2 Concepto de Buffer Caché
El buffer caché es una zona de memoria del kernel que guarda copias de bloques de disco recientemente usados. Cuando un proceso lee un archivo, el sistema operativo no necesariamente va al disco en cada operación: primero consulta si el bloque solicitado ya está en memoria. Si está, lo entrega desde la caché; si no está, lo trae desde el disco y lo conserva para futuros accesos.
En Unix System V, la unidad básica de esta caché es el buffer: una estructura que representa un bloque de un dispositivo de almacenamiento. Cada buffer tiene una cabecera con metadatos y un área de datos donde se guarda la copia del bloque.
Idea clave. El buffer caché explota la localidad de referencia: si un bloque fue usado hace poco, es probable que vuelva a usarse pronto; y si se accedió a un bloque, es probable que los bloques cercanos también se necesiten.
El buffer caché se ubica entre el sistema de archivos y el controlador de disco. Para el sistema de archivos, ofrece bloques lógicos; para el dispositivo, emite operaciones reales de lectura y escritura.
Figura 1: El buffer caché intermedia entre el sistema de archivos y el dispositivo de bloques.
9.2.1 Lecturas y escrituras con caché
En una lectura, el kernel busca el bloque en el buffer caché:
- Si el bloque está presente y válido, ocurre un acierto de caché (cache hit) y no se accede al disco.
- Si el bloque no está presente, ocurre un fallo de caché (cache miss): el kernel toma un buffer libre, solicita el bloque al disco y luego entrega los datos.
En una escritura, el kernel puede escribir el bloque de inmediato al disco o dejarlo marcado como escritura diferida (delayed write). En este último caso, el proceso continúa sin esperar la operación física; el bloque se escribirá más tarde, cuando el kernel necesite reutilizar ese buffer o cuando un mecanismo de sincronización fuerce el vaciado.
Riesgo de la escritura diferida. Si el sistema se apaga o falla antes de volcar los buffers sucios al disco, pueden perderse datos recientes. Por eso existen llamadas como sync, fsync y políticas periódicas de escritura.
9.3 Estructura del Buffer
Un buffer no es solo un arreglo de bytes. En Unix/System V se divide conceptualmente en dos partes:
- Cabecera del buffer (buffer header): contiene metadatos que permiten al kernel administrar el bloque.
- Área de datos: contiene la copia del bloque físico o lógico leído desde el dispositivo.
La cabecera es la parte que participa en las estructuras de control del kernel: listas libres, colas hash, banderas de estado y punteros de encadenamiento.
flowchart TB
B[Buffer]
B --> H[Cabecera]
B --> D[Área de datos<br>bloque de disco]
H --> Dev[Dispositivo<br>dev]
H --> Blk[Número de bloque<br>blkno]
H --> Flags[Banderas<br>busy, valid, dirty]
H --> Ptr[Punteros<br>hash y free list]
H --> Err[Estado de error<br>si existe]
Figura 2: Estructura conceptual de un buffer: cabecera administrativa y área de datos.
9.3.1 Campos típicos de la cabecera
Aunque los nombres exactos varían entre implementaciones, una cabecera de buffer suele contener:
| Campo | Función |
|---|---|
Dispositivo (dev) |
Identifica el disco o dispositivo de bloques. |
Número de bloque (blkno) |
Identifica qué bloque del dispositivo representa. |
| Área de datos | Dirección en memoria donde está la copia del bloque. |
| Banderas (flags) | Indican el estado del buffer: ocupado, válido, sucio, etc. |
| Punteros hash | Enlazan el buffer dentro de una cola hash. |
| Punteros libres | Enlazan el buffer dentro de la lista de buffers disponibles. |
Tabla 1: Campos habituales de una cabecera de buffer.
Ejemplo. Un buffer puede representar el bloque 120 del disco sda. Su cabecera guarda dev = sda y blkno = 120; su área de datos contiene los bytes reales de ese bloque. Si otro proceso pide el mismo bloque, el kernel puede encontrarlo por esos metadatos sin ir al disco.
9.4 Estados de un Buffer
Los buffers cambian de estado a medida que el kernel los usa. En System V estos estados se representan con banderas (flags) en la cabecera. Las banderas no son excluyentes: un mismo buffer puede estar, por ejemplo, válido y sucio al mismo tiempo.
| Estado | Significado |
|---|---|
| Libre | El buffer no está siendo usado por ningún proceso del kernel. |
| Ocupado (busy) | El buffer está reservado para una operación; no puede reutilizarse. |
| Válido (valid/done) | El área de datos contiene una copia correcta de un bloque. |
| Inválido | El contenido del área de datos no debe usarse. |
| Sucio (dirty/delayed write) | Fue modificado en memoria y todavía no se escribió al disco. |
| Solicitado (wanted) | Algún proceso duerme esperando que este buffer se libere. |
| Con error | La última operación de E/S asociada al buffer falló. |
Tabla 2: Estados principales de un buffer en un buffer caché clásico.
9.4.1 Libre no significa vacío
Un punto importante es que libre no significa “sin contenido”. Un buffer libre puede seguir conteniendo una copia válida de un bloque de disco. Está libre porque ningún proceso lo tiene reservado en ese instante, pero si otro proceso pide el mismo bloque, el kernel puede reutilizarlo como acierto de caché.
Hecho crítico. Un buffer puede estar al mismo tiempo en una cola hash y en la free list. La cola hash permite encontrarlo por (dispositivo, bloque); la free list indica que está disponible para ser tomado si se necesita.
9.4.2 Buffer sucio y escritura diferida
Un buffer sucio contiene datos más nuevos que los del disco. Esto ocurre cuando el kernel modifica un bloque y decide postergar la escritura física. La técnica mejora el rendimiento porque agrupa escrituras y evita operaciones innecesarias, pero introduce una ventana de riesgo: durante un tiempo, la copia correcta está solo en memoria.
9.5 Estructura del Buffer Pool
El buffer pool es el conjunto total de buffers que el kernel reserva para el buffer caché. No se administra como una colección desordenada, sino mediante dos estructuras simultáneas:
- Colas hash (hash queues), para encontrar rápido si un bloque ya está en caché.
- Lista libre (free list), para elegir qué buffer puede reutilizarse.
Estas dos estructuras responden preguntas distintas:
- Las hash queues responden: “¿tengo en memoria el bloque
(dev, blkno)?”. - La free list responde: “¿qué buffer puedo tomar ahora para otra operación?”.
flowchart LR
subgraph HQ[Hash queues]
H0[hash 0] --> B1[buf A<br>dev 1 blk 10]
H1[hash 1] --> B2[buf B<br>dev 1 blk 25]
H2[hash 2] --> B3[buf C<br>dev 2 blk 8]
end
subgraph FL[Free list]
FHead[Cabeza] --> B3
B3 --> B1
B1 --> FTail[Cola]
end
Figura 3: Un mismo buffer puede estar en una cola hash y, si no está ocupado, también en la lista libre.
9.5.1 Por qué se necesitan dos estructuras
Si el kernel usara solo una lista lineal de buffers, cada lectura obligaría a recorrer todos los buffers buscando el bloque. Eso sería lento. Las colas hash reducen la búsqueda: el kernel calcula una función hash con el dispositivo y el número de bloque, y revisa solo la cola correspondiente.
La lista libre, en cambio, permite aplicar una política de reemplazo. Usualmente se usa una idea cercana a LRU (Least Recently Used): los buffers liberados se agregan al final y los candidatos a reutilización se toman del principio.
9.6 Free List y Hash Queues
La free list es la lista de buffers que no están ocupados. Cuando una operación necesita un buffer y el bloque no está en caché, el kernel toma un candidato de esta lista. Si la lista está vacía, no hay buffers disponibles y el proceso debe esperar.
Las hash queues son listas que agrupan buffers según una función hash aplicada al par (dispositivo, número de bloque). Esto permite localizar rápidamente un buffer si ya representa el bloque solicitado.
9.6.1 Funcionamiento conjunto
Cuando el kernel necesita el bloque (dev, blkno), realiza una búsqueda en dos niveles:
- Calcula la cola hash correspondiente.
- Recorre esa cola buscando una cabecera con el mismo dispositivo y número de bloque.
- Si la encuentra y no está ocupada, reserva ese buffer.
- Si no la encuentra, toma un buffer de la free list y lo reasigna al nuevo bloque.
Separación de responsabilidades. Las hash queues optimizan la búsqueda; la free list optimiza la reutilización. Juntas permiten que el buffer caché sea rápido tanto para aciertos como para fallos.
9.6.2 Operaciones sobre buffers
El kernel usa operaciones típicas para administrar buffers:
| Operación | Propósito |
|---|---|
getblk |
Obtener un buffer para un bloque específico. |
bread |
Leer un bloque, usando caché si ya está disponible. |
bwrite |
Escribir un buffer al dispositivo. |
bdwrite |
Marcar un buffer para escritura diferida. |
brelse |
Liberar un buffer y devolverlo a la lista libre. |
Tabla 3: Operaciones clásicas asociadas al buffer caché de Unix.
9.7 Recuperación de un Buffer
La recuperación de un buffer es el proceso por el cual el kernel obtiene un buffer para representar un bloque determinado. En Unix/System V, el algoritmo clásico se estudia a través de getblk(dev, blkno).
El objetivo de getblk es devolver un buffer ocupado que represente el bloque solicitado. Para lograrlo, el kernel debe contemplar varias situaciones.
9.7.1 Situación 1: el bloque está en caché y el buffer está libre
Es el caso más favorable. El kernel encuentra el bloque en su cola hash y el buffer no está ocupado.
- Lo retira de la free list, porque dejará de estar disponible.
- Lo marca como ocupado.
- Lo devuelve al solicitante.
No hay acceso al disco: es un acierto de caché.
9.7.2 Situación 2: el bloque está en caché, pero el buffer está ocupado
El kernel encuentra el buffer en la cola hash, pero otro proceso lo está usando. En ese caso no puede reutilizarlo ni leer otra copia del mismo bloque, porque generaría inconsistencias.
La solución clásica es:
- Marcar el buffer como solicitado (wanted).
- Dormir el proceso actual hasta que el buffer se libere.
- Al despertar, repetir la búsqueda desde el inicio.
Por qué se reinicia la búsqueda. Al despertar, el estado global pudo cambiar: el buffer podría haberse liberado, movido o reasignado. Por eso el algoritmo vuelve al comienzo en lugar de continuar con supuestos antiguos.
9.7.3 Situación 3: el bloque no está en caché y no hay buffers libres
Si el bloque no aparece en la cola hash y la free list está vacía, el kernel no tiene dónde cargarlo. El proceso debe dormir esperando que algún buffer sea liberado por otra operación. Cuando despierte, también debe reintentar desde el inicio.
9.7.4 Situación 4: el bloque no está en caché y el candidato está sucio
El kernel toma un buffer de la free list, pero descubre que está marcado para escritura diferida. Ese buffer contiene datos modificados que todavía no llegaron al disco, por lo que no puede sobrescribirse directamente.
La respuesta es:
- Marcar el buffer como ocupado temporalmente.
- Iniciar una escritura asíncrona para volcarlo al disco.
- Buscar otro buffer libre para satisfacer la solicitud actual.
Esta situación muestra el costo oculto de la escritura diferida: mejora el rendimiento normal, pero a veces obliga a escribir antes de poder reutilizar memoria.
9.7.5 Situación 5: el bloque no está en caché y hay un buffer libre limpio
Este es el caso normal de un fallo de caché. El kernel encuentra un buffer libre que puede reutilizarse sin escribirlo antes.
- Lo retira de la free list.
- Lo quita de la cola hash donde estaba por su bloque anterior.
- Cambia su dispositivo y número de bloque al nuevo
(dev, blkno). - Lo inserta en la nueva cola hash.
- Lo marca como ocupado e inválido hasta que la lectura termine.
- Si la operación es una lectura, solicita el bloque al disco.
flowchart TD
A[Solicitar bloque<br>dev, blkno] --> B{¿Está en hash queue?}
B -- Sí --> C{¿Buffer libre?}
C -- Sí --> D[Retirar de free list<br>marcar busy]
C -- No --> E[Marcar wanted<br>dormir y reintentar]
B -- No --> F{¿Hay buffer libre?}
F -- No --> G[Dormir hasta que<br>se libere alguno]
F -- Sí --> H{¿Candidato sucio?}
H -- Sí --> I[Escritura asíncrona<br>buscar otro]
H -- No --> J[Reasignar cabecera<br>insertar en hash]
J --> K[Leer del disco<br>si hace falta]
Figura 4: Situaciones principales al recuperar un buffer con un algoritmo tipo getblk.
9.7.6 Liberación de un buffer
Cuando el kernel termina de usar un buffer, ejecuta una operación equivalente a brelse. Esta operación:
- Desmarca el estado ocupado.
- Despierta procesos que esperaban por ese buffer o por la free list.
- Devuelve el buffer a la lista libre, salvo que alguna política especial indique lo contrario.
Si el buffer contiene datos válidos, sigue en su cola hash. Por eso, aunque quede libre, puede servir como acierto de caché para una lectura posterior.
9.8 Ventajas y Desventajas del Buffer Caché
El buffer caché fue una de las optimizaciones centrales de Unix porque reduce de forma drástica la cantidad de operaciones físicas de disco. Sin embargo, no es una solución perfecta: consume memoria, agrega complejidad y puede diferir escrituras críticas.
9.8.1 Ventajas
- Menor latencia de lectura: los bloques ya presentes en memoria se entregan sin acceder al dispositivo.
- Menos operaciones físicas: varias lecturas repetidas del mismo bloque se resuelven con una sola lectura real.
- Mejor rendimiento de escritura: la escritura diferida permite agrupar operaciones y evitar escrituras que luego serían sobrescritas.
- Interfaz uniforme: el sistema de archivos trabaja con bloques lógicos sin preocuparse por los detalles inmediatos del dispositivo.
- Aprovechamiento de localidad: funciona muy bien con accesos secuenciales y repetidos, comunes en directorios, inodos y archivos usados frecuentemente.
9.8.2 Desventajas
- Consumo de memoria del kernel: cada buffer ocupa espacio que no puede usarse para otros fines.
- Riesgo ante fallos: los buffers sucios pueden perderse si el sistema cae antes de escribirlos al disco.
- Complejidad de sincronización: varios procesos pueden competir por el mismo bloque y el kernel debe coordinar esperas, bloqueos y despertares.
- Reemplazo imperfecto: una política tipo LRU puede expulsar buffers que pronto volverán a necesitarse.
- Doble caché en sistemas modernos: si también existe caché de páginas, puede haber duplicación de datos entre el buffer caché y la page cache.
| Aspecto | Beneficio | Costo |
|---|---|---|
| Lecturas repetidas | Evita accesos al disco. | Requiere memoria reservada. |
| Escrituras diferidas | Agrupa y acelera escrituras. | Puede perder datos ante fallos. |
| Hash queues | Búsqueda rápida por bloque. | Mayor complejidad interna. |
| Free list | Reutilización ordenada de buffers. | Puede quedarse vacía bajo carga. |
Tabla 4: Balance general del buffer caché.
Vigencia moderna. El buffer caché clásico de System V es un modelo histórico, pero sus ideas siguen presentes: caché de bloques, escritura diferida, colas de búsqueda y políticas de reemplazo. Muchos sistemas modernos integran estas funciones con la caché de páginas (page cache) para evitar duplicaciones.
9.9 Resumen
- El buffer caché guarda en memoria copias de bloques de disco para reducir operaciones físicas de E/S.
- En Unix/System V, cada buffer tiene una cabecera administrativa y un área de datos con la copia del bloque.
- Los estados principales incluyen libre, ocupado, válido, inválido, sucio, solicitado y con error.
- El buffer pool se organiza con dos estructuras complementarias: hash queues para encontrar bloques y free list para elegir buffers reutilizables.
- Un buffer libre puede seguir conteniendo datos válidos y permanecer en una cola hash.
- La recuperación de buffers contempla varios casos: acierto libre, acierto ocupado, lista libre vacía, candidato sucio y candidato limpio reutilizable.
- La escritura diferida mejora el rendimiento, pero introduce riesgo de pérdida de datos si el sistema falla antes de sincronizar.
- El buffer caché mejora notablemente el rendimiento de E/S, aunque consume memoria y agrega complejidad al kernel.