1. Cerrar con evidencias y límites explícitos
El curso comenzó preguntando cómo convertir bloques numerados en archivos con nombre. El recorrido llegó hasta formatos reales, planificación, dispositivos y operaciones asíncronas, y terminó con dos proyectos ejecutables. Ahora interesa conectar esas piezas y comprobar qué puedes explicar y reproducir.
Evidencia de aprendizaje: una explicación que distingue los conceptos pertinentes, una prueba reproducible con resultado esperado y una descripción de lo que esa prueba todavía no demuestra.
Abrir los 50 temas, aprobar unas preguntas o ejecutar un programa una vez son señales diferentes. Ninguna se convierte automáticamente en dominio de todas las garantías del almacenamiento. Este cierre mantiene separadas navegación, evaluación conceptual y comprobación práctica.
Al terminar deberías poder tomar un problema de archivos, identificar la capa implicada, elegir una observación útil y evitar afirmar más de lo que permite el resultado. Los ejercicios de este tema proporcionan un punto de partida revisable.
2. Mapa del curso: de una ruta a los datos persistentes
| Trayecto | Preguntas que deberías resolver | Volver a estudiar |
|---|---|---|
| Archivo, ruta y descriptor. | ¿Qué objeto abrí, quién mantiene su posición y cómo trato una transferencia parcial? | Temas 1–3, 4–7 y 8–11. |
| Caché y persistencia. | ¿La llamada copió bytes, confirmó almacenamiento o publicó un nombre? | Temas 12–16 y 35. |
| Inodos, nombres y protección. | ¿Qué se comparte, qué se libera y qué comprobación corresponde al uso real? | Temas 17–25 y 26–29. |
| Montaje y consistencia. | ¿Qué capa está observando el proceso y qué recupera cada mecanismo? | Temas 30–35. |
| Formatos y unidades. | ¿Cómo decodifico campos y valido posiciones sin confundir sectores con bloques? | Temas 36–40 y 46. |
| Planificación y eventos. | ¿Qué representan recorrido, latencia, disponibilidad y finalización? | Temas 41–44 y 45–47. |
| Proyectos integradores. | ¿Puedo conservar invariantes, recuperar contenido y explicar fallos de publicación? | Tema 48 y 49. |
3. Descriptor, descripción abierta e inodo
El descriptor es un entero de la tabla del proceso. La descripción de archivo abierta mantiene estado como posición y banderas de apertura. El inodo identifica el objeto dentro de su sistema de archivos. Confundir esos niveles produce errores al duplicar o compartir accesos.
| Operación | Objeto abierto | Posición |
|---|---|---|
| open dos veces sobre la misma ruta estable. | Puede referir al mismo inodo mediante dos descripciones abiertas. | Independiente por apertura. |
| dup de un descriptor. | Otra entrada para la misma descripción abierta. | Compartida entre los dos descriptores. |
| pread con offset explícito. | Opera sobre el descriptor recibido. | No modifica su posición corriente. |
| FD_CLOEXEC. | Bandera del descriptor para un exec exitoso. | No es una posición ni una bandera de estado compartida del objeto abierto. |
dup(2) especifica la posición compartida y que FD_CLOEXEC no se copia como bandera activa del descriptor duplicado. pread(2) permite dar la posición de esa lectura sin cambiar la corriente. El experimento del cierre contrasta ambas propiedades con bytes conocidos.
4. Nombres, enlaces y vida del objeto
Un nombre es una entrada de directorio. Un enlace duro agrega otro nombre al inodo; un enlace simbólico almacena una ruta que se resolverá posteriormente. El nombre, el objeto y las referencias abiertas tienen ciclos de vida relacionados, pero distintos.
rename cambia la relación de nombres según su contrato; los descriptores ya abiertos no se convierten automáticamente en accesos al archivo nuevo que se publique bajo una ruta. unlink retira un nombre. Si había una referencia abierta al último archivo sin nombres, esa referencia puede seguir operando hasta cerrarse.
La referencia unlink(2) describe la permanencia del objeto abierto. El programa final observa st_nlink=0 y todavía lee el siguiente byte. Eso no implica que una nueva apertura por la ruta retirada pueda encontrarlo.
Para comparar identidad entre rutas se usa st_dev junto con st_ino. Para conocer el número representado por un nodo de dispositivo se interpreta st_rdev. Un mismo número de inodo aislado no tiene significado global entre todos los sistemas de archivos.
5. Lo escrito, lo visible y lo durable
| Observación | Conclusión que permite | Qué falta para otra garantía |
|---|---|---|
| write retorna n positivo. | La llamada aceptó ese tramo según su interfaz. | Completar lo pendiente y definir la confirmación de persistencia. |
| Se completó fsync del archivo. | Se solicitó y confirmó esa sincronización conforme al contrato del sistema. | Si se cambió un nombre, sincronizar también el directorio pertinente. |
| El destino de rename ya es visible. | Nuevas aperturas pueden observar la publicación. | Confirmar la sincronización del directorio para la durabilidad del nombre. |
| El comando devuelve error tras publicar. | Hubo un fallo, pero la nueva versión puede estar visible. | Consultar el diagnóstico y el estado antes de repetir una creación o un borrado. |
fsync(2) explica la diferencia entre archivo y directorio. El protocolo estudiado en el tema 35 y aplicado en los proyectos separa escritura, publicación y confirmación.
Atomicidad, durabilidad y exclusión entre escritores responden preguntas diferentes. Un reemplazo atómico no evita que dos escritores calculen desde una versión vieja. Un journal no sustituye un respaldo independiente, y un checksum no reconstruye por sí solo lo que detecta como diferente.
6. Unidades, estructuras y validación de formatos
Al leer un formato externo hay que fijar orden de bytes, anchos, offsets, límites y características admitidas. Leer una estructura C nativa no resuelve automáticamente relleno o endianess. Los temas 40 y 46 separaron bytes externos de valores validados.
¿Cuál es el objeto observado y dónde empieza?
¿Qué unidad usa cada dirección: bytes, sector lógico o bloque del formato?
¿Cabe toda la región antes de sumar o multiplicar?
¿Qué comprueban firma, checksum y relaciones entre campos?LBA 1 está en el byte 512 con sector lógico 512 y en el byte 4096 con sector lógico 4096. El sector físico y bs de dd no sustituyen esa unidad. Una partición abierta como objeto ya tiene su propio origen; no se suma dos veces su posición en el disco padre.
La firma 0xEF53 no acredita por sí sola ext4 íntegro. Una GPT primaria con CRC correcto no significa que se haya verificado su respaldo. FAT32, exFAT, ext4 y NTFS tienen límites y semánticas diferentes, que además dependen del proveedor y del entorno de uso.
7. Permisos, rutas y carreras de comprobación
Una comprobación sobre una ruta y un uso posterior no forman una operación indivisible. access o stat no reservan el objeto para la siguiente apertura. Para usar un archivo se abre con las restricciones pertinentes y se inspecciona el descriptor mediante fstat.
O_NOFOLLOW sobre la apertura del componente final no resuelve todas las rutas de una jerarquía adversaria. Los programas del curso declaran directorios de laboratorio confiables y delimitan las comprobaciones que realizan; esa condición es parte del contrato.
Los permisos ordinarios, ACL, atributos, credenciales y opciones del montaje pueden intervenir de manera distinta. Poder consultar metadatos no prueba poder abrir el contenido. Copiar bytes tampoco demuestra que se hayan conservado propietario, ACL o flujos nombrados.
Una evaluación útil consiste en explicar qué operación se intenta, qué comprobación corresponde a ese uso y qué error se obtuvo. Los temas 26–29 ofrecen los ejemplos de referencia para revisar protección y TOCTOU.
8. Rendimiento y concurrencia sin confundir métricas
| Distinción | Ejemplo de explicación correcta |
|---|---|
| Latencia e IOPS. | Concurrencia y tamaño de operación deben acompañar la comparación; no se invierte cualquier latencia para obtener capacidad global. |
| Secuencial y aleatorio. | El posicionamiento pesa en HDD; la pila y las cachés pueden alterar las solicitudes que llegan al medio. |
| Profundidad solicitada y alcanzada. | iodepth no demuestra por sí solo esa cantidad de operaciones dentro del hardware. |
| Disponibilidad y finalización. | epoll invita a intentar una operación; un CQE de lectura informa el resultado de esa solicitud. |
| EAGAIN y EOF. | Falta de progreso inmediato no significa que la fuente haya terminado. |
Para repetir una medición registra patrón, tamaño, rango, concurrencia, motor, estado de caché y punto de confirmación. Si se cambia más de una condición a la vez, la diferencia observada no identifica una causa única.
Los modelos FCFS, SSTF y de barridos sirven para razonar sobre el recorrido bajo supuestos explícitos. Los planificadores reales y SSD/NVMe requieren considerar otras capas. Las pruebas de rendimiento tampoco sustituyen la comprobación de contenido y errores.
9. Qué demuestra el mini sistema de archivos
El proyecto MINIFS48 relaciona nombre, inodo, punteros y bitmap en una imagen de 32768 bytes. Sus límites permiten probar por separado capacidad por archivo, cantidad de entradas y espacio de datos.
- Un archivo vacío ocupa inodo y entrada, sin bloques de datos.
- Los punteros reconstruyen contenido no contiguo y permiten reutilizar huecos.
- El validador contrasta referencias y mapa incluso cuando el CRC fue recalculado.
- Las modificaciones se publican como una imagen completa y los fallos posteriores pueden dejar visible la nueva versión.
La evidencia es importar y exportar bytes exactos, provocar agotamiento y rechazos sin modificar la imagen original, y contrastar la carga reproducible con otro modelo. El proyecto no se monta como ext4 ni acredita una implementación de VFS o un sistema de archivos general.
10. Qué demuestra el respaldo incremental
El proyecto de respaldo construye versiones completas, comparte archivos verificados y restaura a inodos independientes. Sus pruebas separan los objetos almacenados de los metadatos lógicos de cada versión.
- Igual tamaño y mtime no bastan para decidir contenido igual.
- Los enlaces duros se crean entre versiones; el origen se copia.
- chmod y escrituras sobre un inodo compartido afectan a todos sus nombres; no se editan versiones publicadas.
- Una restauración se compara con contenido, modos y tiempos del modelo de esa versión.
- Retirar una antecesora no obliga a reproducir una cadena para restaurar la versión completa siguiente.
La evidencia incluye restaurar, editar el resultado sin alterar el repositorio y rechazar bases corruptas o rutas inválidas. Las versiones comparten riesgos físicos: no sustituyen una copia independiente del medio.
11. Experimento integrador en C
Guarda cierre50.c o descarga el fuente. Recibe un directorio existente y crea dentro una carpeta privada con nombre único. Solo usa sus propios archivos y retira esa carpeta al terminar por el camino normal.
Escribe ABCDEFGH, compara dup con una apertura independiente, observa pread, renombra y retira el último nombre, y sigue leyendo por un descriptor abierto. No crea procesos ni canales IPC: muestra las propiedades que luego habrá que administrar al compartir descriptores entre procesos.
#define _POSIX_C_SOURCE 200809L
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
static int escribir_ocho(int fd)
{
const char *p = "ABCDEFGH";
size_t faltan = 8;
while (faltan) {
ssize_t n = write(fd, p, faltan);
if (n > 0 && (size_t)n <= faltan) { p += n; faltan -= (size_t)n; }
else if (n == -1 && errno == EINTR) continue;
else { if (n >= 0) errno = EIO; return -1; }
}
return 0;
}
static int leer_valor(int fd, off_t posicion, char esperado, const char *etiqueta)
{
char c;
ssize_t n;
do { n = posicion < 0 ? read(fd, &c, 1) : pread(fd, &c, 1, posicion); }
while (n == -1 && errno == EINTR);
if (n != 1 || c != esperado) { if (n >= 0) errno = EIO; return -1; }
printf("%s=%c\n", etiqueta, c);
return 0;
}
int main(int argc, char **argv)
{
if (argc != 2) { fprintf(stderr, "Uso: %s directorio-existente\n", argv[0]); return 2; }
char carpeta[4096];
int n = snprintf(carpeta, sizeof carpeta, "%s/cierre50.XXXXXX", argv[1]);
if (n < 0 || (size_t)n >= sizeof carpeta) { fprintf(stderr, "Ruta demasiado larga\n"); return 2; }
if (!mkdtemp(carpeta)) { perror("mkdtemp"); return 1; }
int dir = -1, original = -1, duplicado = -1, independiente = -1, estado = 1;
if (chmod(carpeta, 0700)) goto fallo;
dir = open(carpeta, O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
if (dir == -1) goto fallo;
original = openat(dir, "original", O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC | O_NOFOLLOW, 0600);
if (original == -1 || fchmod(original, 0600)) goto fallo;
if (escribir_ocho(original) || fsync(original) || fsync(dir)) goto fallo;
if (lseek(original, 0, SEEK_SET) == -1) goto fallo;
duplicado = dup(original);
if (duplicado == -1) goto fallo;
int banderas = fcntl(duplicado, F_GETFD);
if (banderas == -1) goto fallo;
printf("DUP_CLOEXEC_INICIAL=%d\n", !!(banderas & FD_CLOEXEC));
if (fcntl(duplicado, F_SETFD, banderas | FD_CLOEXEC)) goto fallo;
banderas = fcntl(duplicado, F_GETFD);
if (banderas == -1) goto fallo;
printf("DUP_CLOEXEC_FINAL=%d\n", !!(banderas & FD_CLOEXEC));
independiente = openat(dir, "original", O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
if (independiente == -1) goto fallo;
if (leer_valor(original, -1, 'A', "LECTURA_ORIGINAL") ||
leer_valor(duplicado, -1, 'B', "LECTURA_DUP") ||
leer_valor(original, 6, 'G', "PREAD_POS6")) goto fallo;
off_t posicion = lseek(original, 0, SEEK_CUR);
if (posicion == -1) goto fallo;
if (posicion != 2 || lseek(duplicado, 0, SEEK_CUR) != 2) { errno = EIO; goto fallo; }
printf("POSICION_COMPARTIDA=%" PRIdMAX "\n", (intmax_t)posicion);
if (leer_valor(independiente, -1, 'A', "LECTURA_INDEPENDIENTE")) goto fallo;
posicion = lseek(independiente, 0, SEEK_CUR);
if (posicion == -1) goto fallo;
if (posicion != 1 || lseek(original, 0, SEEK_CUR) != 2) { errno = EIO; goto fallo; }
printf("POSICION_INDEPENDIENTE=%" PRIdMAX "\n", (intmax_t)posicion);
struct stat a, b;
if (fstat(original, &a) || fstat(independiente, &b)) goto fallo;
if (a.st_dev != b.st_dev || a.st_ino != b.st_ino) { errno = EIO; goto fallo; }
puts("MISMO_INODO=si");
if (renameat(dir, "original", dir, "publicado")) goto fallo;
if (fstatat(dir, "publicado", &b, AT_SYMLINK_NOFOLLOW)) goto fallo;
if (a.st_dev != b.st_dev || a.st_ino != b.st_ino) { errno = EIO; goto fallo; }
if (unlinkat(dir, "publicado", 0) || fsync(dir)) goto fallo;
if (fstat(original, &a)) goto fallo;
if (a.st_nlink != 0) { errno = EIO; goto fallo; }
printf("ENLACES_TRAS_UNLINK=%" PRIuMAX "\n", (uintmax_t)a.st_nlink);
if (fstatat(dir, "publicado", &b, AT_SYMLINK_NOFOLLOW) != -1 || errno != ENOENT) {
errno = EIO; goto fallo;
}
puts("RUTA_AUSENTE=si");
if (leer_valor(original, -1, 'C', "LECTURA_SIN_NOMBRE")) goto fallo;
puts("RESULTADO=OK");
estado = 0;
goto limpiar;
fallo:
perror("experimento de cierre");
limpiar:
if (independiente != -1 && close(independiente)) estado = 1;
if (duplicado != -1 && close(duplicado)) estado = 1;
if (original != -1 && close(original)) estado = 1;
if (dir != -1) {
if (unlinkat(dir, "original", 0) && errno != ENOENT) estado = 1;
if (unlinkat(dir, "publicado", 0) && errno != ENOENT) estado = 1;
if (close(dir)) estado = 1;
}
if (rmdir(carpeta)) { perror("rmdir de la carpeta propia"); estado = 1; }
if (fflush(stdout) == EOF) estado = 1;
return estado;
}
El programa devuelve 0 al completar el experimento, 1 ante fallo y 2 por uso incorrecto o ruta de longitud no admitida. La lectura A por el original y B por el duplicado deja la posición compartida en 2. pread del offset 6 entrega G sin alterarla; la apertura independiente todavía lee A y queda en 1.
Después de unlink, el número de enlaces es cero y la ruta ya no existe, pero el descriptor original entrega C. La última referencia se libera al cerrar. FD_CLOEXEC se configura por separado en el duplicado: no cambia la posición compartida.
Las rutas anteriores a la carpeta privada se consideran confiables y estables. Es una práctica acotada de objetos creados por el proceso, no una utilidad general que recorra y borre árboles ajenos.
12. Verificador final y evidencia exportable
Guarda verificar-cierre50.py o descarga el verificador. Compila en una carpeta temporal y ejecuta tres casos seleccionables: el experimento de descriptores, las pruebas de mini48 y las pruebas del respaldo49.
Busca los fuentes junto al propio script, no por el directorio desde el que se lo invoca. Para el conjunto completo deben estar allí también mini48.c, verificar-mini48.py, respaldo49.py, identidad49.c y verificar-respaldo49.py.
import argparse
from datetime import datetime, timezone
import json
from pathlib import Path
import platform
import shutil
import subprocess
import sys
from tempfile import TemporaryDirectory
def main():
p = argparse.ArgumentParser(description='Comprobaciones finales de los proyectos y descriptores')
p.add_argument('--caso', choices=('todo', 'descriptores', 'mini48', 'respaldo49'), default='todo')
p.add_argument('--cc', default='cc', help='Nombre o ruta del compilador, sin argumentos de shell')
p.add_argument('--informe', help='Archivo JSON nuevo; no sobrescribe una ruta existente')
a = p.parse_args()
if not __debug__:
p.error('Ejecutar sin -O: los verificadores necesitan sus comprobaciones assert')
raiz = Path(__file__).resolve().parent
requisitos = {
'descriptores': ['cierre50.c'],
'mini48': ['mini48.c', 'verificar-mini48.py'],
'respaldo49': ['respaldo49.py', 'identidad49.c', 'verificar-respaldo49.py'],
}
seleccion = list(requisitos) if a.caso == 'todo' else [a.caso]
compilador = shutil.which(a.cc)
informe = dict(fecha_utc=datetime.now(timezone.utc).isoformat(),
sistema=platform.system(), kernel=platform.release(),
python=platform.python_version(), compilador=compilador, casos=[])
def ejecutar(cmd, esperado=0, cwd=None):
r = subprocess.run(cmd, capture_output=True, text=True, timeout=60, cwd=cwd)
if r.returncode != esperado:
raise RuntimeError(f'Estado {r.returncode}, esperado {esperado}: {r.stderr.strip()}\n{r.stdout.strip()}')
return r.stdout
with TemporaryDirectory(prefix='cierre50-') as d:
tmp = Path(d)
trabajo = tmp / 'ensayos'; trabajo.mkdir()
for caso in seleccion:
faltan = [n for n in requisitos[caso] if not (raiz / n).is_file()]
registro = dict(caso=caso)
if platform.system() != 'Linux' or not compilador or faltan:
registro.update(estado='NO_EJECUTADO', detalle={
'requiere_linux': platform.system() != 'Linux',
'falta_compilador': not bool(compilador), 'faltan_fuentes': faltan})
informe['casos'].append(registro)
print('[NO EJECUTADO]', caso, json.dumps(registro['detalle'], ensure_ascii=True))
continue
try:
fuente = {'descriptores': 'cierre50.c', 'mini48': 'mini48.c', 'respaldo49': 'identidad49.c'}[caso]
binario = tmp / caso
ejecutar([compilador, '-std=c11', '-O2', '-Wall', '-Wextra', '-Wpedantic', '-Werror',
str(raiz / fuente), '-o', str(binario)])
if caso == 'descriptores':
esperado = dict(DUP_CLOEXEC_INICIAL='0', DUP_CLOEXEC_FINAL='1',
LECTURA_ORIGINAL='A', LECTURA_DUP='B', PREAD_POS6='G',
POSICION_COMPARTIDA='2', LECTURA_INDEPENDIENTE='A',
POSICION_INDEPENDIENTE='1', MISMO_INODO='si',
ENLACES_TRAS_UNLINK='0', RUTA_AUSENTE='si',
LECTURA_SIN_NOMBRE='C', RESULTADO='OK')
for _ in range(3):
salida = ejecutar([str(binario), str(trabajo)])
observado = dict(linea.split('=', 1) for linea in salida.splitlines())
if observado != esperado or list(trabajo.iterdir()):
raise RuntimeError('Resultados o limpieza del experimento inesperados')
ejecutar([str(binario)], esperado=2)
ejecutar([str(binario), str(trabajo / 'no-existe')], esperado=1)
elif caso == 'mini48':
ejecutar([sys.executable, '-E', str(raiz / 'verificar-mini48.py'), str(binario)])
else:
ejecutar([sys.executable, '-E', str(raiz / 'verificar-respaldo49.py'),
str(raiz / 'respaldo49.py'), str(binario)])
registro.update(estado='OK', detalle='Fuentes compilados y comprobaciones seleccionadas superadas')
print('[OK]', caso)
except (OSError, ValueError, RuntimeError, subprocess.TimeoutExpired) as e:
registro.update(estado='FALLO', detalle=str(e))
print('[FALLO]', caso, str(e))
informe['casos'].append(registro)
estados = [r['estado'] for r in informe['casos']]
codigo = 1 if 'FALLO' in estados else 3 if 'NO_EJECUTADO' in estados else 0
informe['resultado'] = 'FALLO' if codigo == 1 else 'INCOMPLETO' if codigo == 3 else 'COMPROBACIONES_SUPERADAS'
if a.informe:
try:
with Path(a.informe).open('x', encoding='utf-8') as f:
json.dump(informe, f, ensure_ascii=True, indent=2)
f.write('\n')
except OSError as e:
print('No se pudo crear un informe nuevo:', e, file=sys.stderr)
return 1
print('RESULTADO=' + informe['resultado'])
print('El resultado describe estos casos y este entorno; no acredita por si solo dominio de los 50 temas.')
return codigo
if __name__ == '__main__':
raise SystemExit(main())
| Estado final | Código | Interpretación |
|---|---|---|
| COMPROBACIONES_SUPERADAS | 0 | Todos los casos seleccionados compilaron y pasaron sus comprobaciones. |
| FALLO | 1 | Falló una compilación, ejecución, comprobación o creación del informe solicitado. |
| Uso incorrecto | 2 | Argumentos inválidos o intérprete con optimización que eliminaría asserts. |
| INCOMPLETO | 3 | Algún caso seleccionado no se ejecutó por plataforma, compilador o fuentes ausentes. |
El informe JSON opcional registra entorno, casos y resultados, y se crea sin sobrescribir una ruta existente. Un resultado parcial no se presenta como éxito de los casos omitidos. Se usa Python sin -O porque los verificadores de los proyectos contienen comprobaciones assert.
13. Laboratorio de cierre y resultados esperados
En una terminal Bash de Linux crea un directorio de laboratorio nuevo y guarda los fuentes que utilizarás. No se necesitan permisos de administrador ni un dispositivo físico. Las carpetas y archivos de ensayo se crean dentro de temporales propios.
mkdir laboratorio50
cd laboratorio50
# Guardar aqui cierre50.c y verificar-cierre50.py.
cc -std=c11 -O2 -Wall -Wextra -Wpedantic cierre50.c -o cierre50
./cierre50 .
python3 verificar-cierre50.py --caso descriptoresLa salida debe incluir POSICION_COMPARTIDA=2, POSICION_INDEPENDIENTE=1, ENLACES_TRAS_UNLINK=0, LECTURA_SIN_NOMBRE=C y RESULTADO=OK. La carpeta privada de ensayo ya no debe quedar después de una salida normal exitosa.
# Guardar tambien los cinco fuentes de los proyectos 48 y 49 indicados arriba.
python3 verificar-cierre50.py --caso mini48
python3 verificar-cierre50.py --caso respaldo49
python3 verificar-cierre50.py --informe evidencia50.json
python3 -m json.tool evidencia50.jsonEl conjunto completo debe informar [OK] descriptores, [OK] mini48 y [OK] respaldo49 antes de RESULTADO=COMPROBACIONES_SUPERADAS. Si falta un fuente, el bloque se marca NO EJECUTADO y el resultado será INCOMPLETO; descarga el recurso necesario y repite ese caso.
El informe no incluye una prueba de todos los cortes de energía, semánticas de todos los proveedores ni rendimiento de cada dispositivo. Conserva junto a él los fuentes exactos, versiones de herramientas y una explicación de los límites del ensayo.
14. Resolver situaciones combinando capas
La actualización dio error y el contenido nuevo está presente
Localiza la fase del fallo. Si ya se publicó el temporal, puede verse una versión completa aunque falle la confirmación del padre. Comprueba el diagnóstico y el contenido antes de repetir: una segunda creación exclusiva puede encontrar el nombre ya existente. Repasa errores de publicación.
Hay espacio libre, pero no cabe otro archivo
Separa bloques libres, inodos, límites individuales, cuotas y reglas del formato. En MINIFS48, dieciséis archivos vacíos agotan nombres e inodos sin consumir datos; en un formato real pueden intervenir otras restricciones. Contrasta agotamiento y estadísticas del volumen.
La lectura parece muy rápida
Una duración baja puede describir caché, un conjunto pequeño o poca competencia. Registra la frontera y la carga antes de concluir sobre el medio. No atribuyas a búsqueda de HDD una lectura que no llegó al disco. Repasa lectura fría y caliente y medición de colas.
La copia conserva bytes, pero pierde semántica
Compara los requisitos: ACL, nombres, enlaces, flujos o huecos no se deducen de un hash del flujo principal. Una restauración del proyecto49 conserva lo que declara su manifiesto, no atributos que decidió excluir. Consulta comparación de formatos y contrato del respaldo.
Un bucle de eventos deja de avanzar
Distingue EAGAIN de EOF, notificación de nivel frente a flanco y resultado de la operación frente al de esperar. Conserva datos pendientes e identidad de cada solicitud. Los ejemplos del tema 47 permiten reproducir esos estados.
15. Puente a Comunicación entre Procesos
Hasta aquí predominó la relación entre un proceso y archivos o dispositivos. El próximo paso añade otro participante que produce, consume o comparte estado. El contrato debe definir quién posee cada extremo, cómo se representa un mensaje y qué ocurre si alguien termina o deja de avanzar.
Después de fork, los descriptores heredados de padre e hijo se refieren a las mismas descripciones abiertas correspondientes: pueden compartir posición y banderas de estado. Las memorias ordinarias de ambos procesos son espacios separados; heredar un descriptor no implica compartir arbitrariamente variables de C. La referencia fork(2) distingue ambas propiedades.
| Base del curso actual | Pregunta nueva al comunicar procesos |
|---|---|
| read y write pueden ser parciales. | ¿Cómo se enmarca y reconstruye un mensaje si el canal entrega un flujo de bytes? |
| Duplicados y referencias abiertas prolongan la vida del recurso. | ¿Qué descriptores debe cerrar cada participante para que el otro pueda observar finalización? |
| EAGAIN y espera por eventos. | ¿Cómo se evita consumir CPU mientras el otro participante aún no puede producir o aceptar datos? |
| Estados compartidos requieren coordinación. | ¿Qué regla evita carreras al usar memoria compartida o estructuras accesibles por varios procesos? |
| Identidad y permisos. | ¿Quién está autorizado a conectar, enviar, recibir o abrir el mecanismo? |
Tuberías, colas de mensajes, memoria compartida y sockets son mecanismos con contratos diferentes, no variantes intercambiables de un archivo regular. Se estudiarán con sus reglas de comunicación, sincronización y cierre; aquí se prepara el vocabulario sin implementar todavía esos canales.
La ruta de aprendizaje del índice identifica Comunicación entre Procesos como el siguiente paso. Este es el último tema de Sistemas de Archivos y Entrada/Salida.
16. Evaluación interactiva y plan de repaso
Resolver doce escenarios y justificar cada elección
Selecciona una respuesta por escenario y corrige cuando hayas completado las doce. Se mostrará una explicación y el tema relacionado. Las respuestas pueden cambiarse y volver a corregirse; es una práctica local, sin certificación automática.
Evaluación de conceptos conectados
Preparar un repaso basado en evidencias propias
Para cada área indica el nivel de evidencia que puedes aportar. El plan prioriza lo aún no practicado, después lo explicado sin una comprobación propia. Tus elecciones describen una autoevaluación; la página no inspecciona el equipo ni ejecuta los verificadores.
De la revisión conceptual a una prueba reproducible
| Prioridad | Área y evidencia declarada | Actividad concreta | Referencia |
|---|
17. Criterios de salida y continuidad
Un cierre defendible reúne una explicación y una comprobación para cada competencia relevante. Puedes utilizar esta lista para organizar una carpeta de evidencia:
- Explicar descriptor, descripción abierta, nombre e inodo con el experimento y su salida.
- Tratar transferencias parciales, EOF, EINTR y errores sin usar bytes no recibidos.
- Separar visibilidad, sincronización y coordinación entre escritores, incluidos errores posteriores a publicar.
- Justificar unidades y límites antes de interpretar una estructura de disco.
- Describir una medición con carga, concurrencia y frontera explícitas.
- Mostrar un rechazo estructural de una imagen inválida sin modificarla.
- Importar y recuperar contenido exacto con mini48.
- Crear y restaurar versiones del respaldo y comprobar identidad e independencia.
- Registrar qué casos no se ejecutaron y qué condiciones siguen fuera de la prueba.
- Explicar qué se heredaría o compartiría al continuar hacia Comunicación entre Procesos.
Una carpeta útil puede incluir fuentes, órdenes de compilación, salidas, informe JSON y notas de los fallos esperados. No guardes solo una captura de un resultado correcto: conserva cómo repetirlo y qué significaba.
Si una comprobación falla, localiza la capa y vuelve al tema específico. Si el entorno impide una función, registra esa condición y busca un entorno de laboratorio apropiado antes de contar el ejercicio como ejecutado. La visita registrada en el índice sigue siendo solo una ayuda de navegación.
El curso termina aquí. Puedes volver al índice de temas para repasar o consultar la ruta completa de aprendizaje para continuar.
18. Preguntas de repaso y práctica final
1. ¿Un descriptor identifica globalmente un archivo?
No. Es una entrada numérica de la tabla de un proceso; se interpreta dentro de ese contexto.
2. ¿Dos open sobre la misma ruta comparten siempre posición?
No. Pueden apuntar al mismo inodo mediante descripciones abiertas con posiciones independientes.
3. ¿Qué comparte dup?
La descripción abierta y su posición y banderas de estado; no activa automáticamente FD_CLOEXEC en el duplicado.
4. ¿pread cambia la posición corriente?
No. Usa un offset explícito para esa operación.
5. ¿unlink invalida todos los descriptores abiertos?
No. Retira un nombre; las referencias abiertas pueden seguir usando el objeto.
6. ¿write positivo prueba persistencia final?
No. Informa progreso de esa llamada; la garantía de sincronización se define y comprueba por separado.
7. ¿fsync del archivo confirma siempre el nombre publicado?
No. El protocolo de cambio de nombre requiere también sincronizar el directorio pertinente.
8. ¿Error después de publicar significa que nada cambió?
No. La versión nueva puede estar visible aunque su confirmación de durabilidad haya fallado.
9. ¿Una firma o un CRC correctos prueban el volumen entero?
No. Su alcance es específico y deben validarse relaciones, estructuras adicionales y contenido según el objetivo.
10. ¿Qué unidad convierte un LBA en bytes?
El sector lógico del objeto observado, no el físico ni el bloque de ext4.
11. ¿stat previo evita toda carrera al abrir?
No. La ruta puede cambiar; para el objeto usado se comprueba el descriptor y se delimitan las restricciones de resolución.
12. ¿Más profundidad implica siempre mejor caudal?
No. Tras saturación puede agregar espera y empeorar percentiles sin aumentar completaciones.
13. ¿EAGAIN significa EOF?
No. Expresa falta de progreso inmediato para esa operación no bloqueante.
14. ¿Un evento de epoll es una lectura terminada?
No. Indica disponibilidad; hay que intentar la operación y revisar su resultado.
15. ¿Cómo se identifica una finalización de io_uring?
Por la identidad asociada a la solicitud, como user_data, no por su orden supuesto de llegada.
16. ¿Los enlaces duros comparten solo los bytes?
No. Comparten el inodo y sus metadatos físicos; escribir o cambiar permisos afecta a sus nombres.
17. ¿Una restauración debe enlazar al objeto del respaldo?
No en el proyecto49. Se copia para que editar la restauración no modifique las versiones almacenadas.
18. ¿fork implica que todas las variables sean memoria compartida?
No. Los espacios ordinarios están separados; los descriptores heredados sí pueden referirse a las mismas descripciones abiertas.
19. ¿Qué significa INCOMPLETO en el verificador final?
Que algún caso seleccionado no se ejecutó por plataforma, compilador o fuentes ausentes. No cuenta como éxito omitido.
20. ¿Abrir los 50 temas acredita dominio práctico?
No. El registro indica navegación; el dominio requiere explicación, práctica y evidencia con límites reconocidos.
Práctica final
- Dibuja las referencias del experimento antes de dup, después de la apertura independiente y después de unlink.
- Explica por qué se observa la secuencia A, B, G, A y C sin suponer una sola posición para todos los descriptores.
- Compila el experimento y contrasta su salida con el verificador.
- Ejecuta los casos mini48 y respaldo49 y conserva un informe nuevo.
- Provoca un caso no ejecutado usando una carpeta que no contenga los fuentes de otros proyectos y verifica que no se informe éxito completo.
- Explica un rechazo de imagen con CRC recalculado pero referencias inválidas.
- Restaura una versión anterior y comprueba bytes, metadatos declarados e independencia de inodos.
- Documenta una medición de E/S sin confundir caché, capacidad y latencia de aplicación.
- Selecciona dos errores conceptuales de la evaluación y repite sus prácticas de referencia.
- Prepara un inventario de descriptores que debería cerrar cada participante de un futuro programa con varios procesos, sin implementar todavía el canal.
- Redacta qué garantiza y qué no garantiza cada evidencia que conservaste.
- Elige tu siguiente repaso o el paso a Comunicación entre Procesos utilizando la ruta del índice.