← Sistemas de Archivos y E/SBloque 5 · Inodos y estructura en disco · Tema 17 de 50

Tema 17

El volumen y el inodo

Hasta ahora trabajamos con rutas, descriptores y caché. En este bloque observaremos las estructuras que permiten convertir un nombre en un archivo y encontrar sus datos: superbloque, mapas de bits, tablas de inodos y bloques. Dos programas en C mostrarán metadatos y la vida de un archivo después de cambiar o quitar sus nombres. También examinaremos una imagen ext4 propia y usaremos un simulador para relacionar las asignaciones, los enlaces y los descriptores abiertos.

1. Volumen, sistema de archivos y montaje

Un proceso escribe open("datos.txt", ...), pero el sistema necesita resolver dónde está ese nombre, qué objeto identifica y cómo se obtiene su contenido. Para estudiar esa organización separaremos el espacio que sirve de respaldo, el formato que lo administra y el lugar donde se incorpora al árbol de nombres.

Volumen: espacio de almacenamiento presentado como una unidad lógica. Puede corresponder a una partición, un volumen lógico o una imagen de archivo. Un sistema de archivos organiza nombres, metadatos, asignación y datos sobre un respaldo. Un montaje hace accesible una instancia en el espacio de nombres del sistema.

capas conceptuales · una posible organización local
dispositivo o conjunto de dispositivos
               │
        volumen / partición / imagen
               │
       formato de sistema de archivos
               │
          instancia montada
               │
       /proyectos/curso/datos.txt
               │ resolución de la ruta
          directorios → inodo → datos

La correspondencia no tiene por qué ser uno a uno: puede haber agregación de dispositivos, subvolúmenes, montajes de red y vistas adicionales de un mismo sistema de archivos. El ejemplo ext4 del laboratorio usa deliberadamente un respaldo sencillo: un archivo regular que contiene una imagen, sin montarlo.

ConceptoQué describeQué no debe suponerse
DispositivoUn respaldo expuesto por el sistema.Que contenga un único sistema de archivos.
VolumenUna unidad lógica de capacidad.Que equivalga siempre a un disco físico completo.
FormatoReglas y estructuras para organizar la información.Que ext4, XFS, Btrfs y FAT tengan el mismo diseño.
MontajeUna incorporación al árbol visible de rutas.Que un punto de montaje sea el superbloque grabado en disco.

Linux presenta una interfaz común para muchos formatos. Que stat() devuelva un número de inodo no obliga a que el sistema de archivos tenga en disco una tabla idéntica a la de ext4. En este tema usaremos un modelo tipo UNIX para razonar y ext4 como ejemplo concreto de estructuras persistentes.

También distinguiremos un inodo persistente de las estructuras que el kernel mantiene en memoria. La caché y los objetos de la capa VFS permiten trabajar con formatos distintos; consultar sus metadatos mediante una llamada no equivale a leer directamente una entrada de una tabla de disco.

2. El superbloque y la geometría

Para interpretar los bytes de un volumen hace falta información de contexto: tipo y versión del formato, tamaño de bloque, capacidades y características habilitadas. El superbloque reúne información global necesaria para entender y administrar esa instancia.

Superbloque: estructura de metadatos globales del sistema de archivos. En un formato concreto puede contener geometría, identificación, contadores, estado y características. No almacena por sí solo todos los nombres ni toda la lista de bloques de cada archivo.

Clase de informaciónPara qué sirve
Identidad y firmaReconocer una instancia y el formato esperado.
GeometríaInterpretar tamaños, cantidades y divisiones en grupos.
CaracterísticasDeterminar qué variantes del formato deben comprenderse.
Contadores y estadoAyudar al seguimiento del espacio y del estado del sistema de archivos.
Referencias de organizaciónEncontrar o interpretar otras estructuras según las reglas del formato.

En ext4 el diseño incluye superbloque, descriptores, bitmaps, tablas y journal, entre otras estructuras. La documentación de estructuras globales de ext4 organiza esas piezas. No conviene memorizar una secuencia de cajas como si toda variante tuviera las mismas posiciones.

Capacidad bruta y capacidad para contenido

Si un modelo tiene 24 bloques de 1024 bytes, su capacidad bruta es 24 × 1024 = 24576 bytes. Si siete bloques quedan ocupados por estructuras y directorio, solo diecisiete quedan disponibles para los datos que el modelo asigna a archivos regulares: 17408 bytes. Ese cálculo todavía ignora los límites de cantidad de inodos y de entradas del directorio.

modelo del simulador · organización elegida, no layout de ext4
bloque 0        superbloque del modelo
bloque 1        descriptor de organización
bloque 2        bitmap de bloques
bloque 3        bitmap de inodos
bloques 4 y 5   tabla de 8 entradas de 256 bytes
bloque 6        contenido del directorio raíz del modelo
bloques 7..23   espacio asignable a archivos regulares

Total: 24 bloques de 1024 bytes; 17 bloques de datos inicialmente libres.

La tabla se incluye dentro del volumen: reservar espacio para metadatos consume capacidad aunque todavía no existan archivos de usuario. El simulador usa bloques completos para bitmaps muy pequeños porque representa estructuras separadas; no pretende ser un formato eficiente para 24 bloques.

La posición del superbloque y sus copias de respaldo se interpretan mediante el formato. Una copia de metadatos puede ayudar a recuperar una estructura dañada, pero no reemplaza una copia de seguridad de los datos ni garantiza recuperación ante toda clase de fallo.

3. Grupos y descriptores de grupo

Un volumen grande no tiene por qué administrar todos sus recursos mediante una única tabla gigante situada al comienzo. ext4 divide el espacio en grupos de bloques para organizar asignación y metadatos y favorecer la localidad. Su descripción de alto nivel explica esa organización.

Un descriptor de grupo proporciona información del grupo, como ubicaciones de bitmaps y tabla de inodos. La documentación de descriptores de ext4 muestra que la posición de esas estructuras puede variar y que las características del formato afectan su distribución.

esquema conceptual · recursos distribuidos
información global del sistema de archivos
                    │
            descriptores de grupos
             │          │          │
           grupo 0    grupo 1    grupo 2 ...
             │          │          │
         seguimiento de bloques e inodos
         tablas y regiones asignadas según el formato

La localidad puede reducir desplazamientos y trabajo de búsqueda al mantener datos y metadatos relacionados cerca. En almacenamiento moderno cambian las penalizaciones físicas, pero siguen importando la organización, el tamaño de operaciones y el trabajo necesario para encontrar y actualizar estructuras.

La cantidad de bloques por grupo, tamaño de inodo y características no deben deducirse solo del nombre “ext4”. El laboratorio leerá lo que informa la imagen creada. Los valores dependen de las opciones de creación, del tamaño del respaldo y de la versión y configuración de las herramientas.

Una vista simplificada necesita una advertencia precisa

Un dibujo con “superbloque, mapas, tabla, datos” ayuda a entender responsabilidades. Para calcular una posición real hay que consultar las reglas y descriptores efectivos. Afirmar que la tabla empieza siempre en un bloque fijo puede producir una lectura completamente distinta de lo que se buscaba.

En los programas usaremos interfaces de metadatos, y en la imagen emplearemos herramientas que conocen el formato. No escribiremos un parser de bytes basado en suposiciones sobre posiciones ni abriremos dispositivos del equipo para buscar firmas.

4. Mapas de bits: qué está ocupado

Un mapa de bits representa estados mediante bits. En el modelo usaremos uno para cada bloque del volumen y otro para cada entrada de la tabla de inodos. Un bit uno significa ocupado y un cero, disponible. La convención de representación debe conocerse antes de interpretar una secuencia impresa.

ejemplo · dos recursos distintos
bitmap de inodos del modelo, entradas 1..8:
    1 1 0 0 0 0 0 0
    │ │
    │ └─ un archivo ocupa la entrada 2
    └─── raíz reservada en la entrada 1 del modelo

bitmap de bloques, posiciones 0..23:
    1111111 11000000000000000
    │       │
    │       └─ dos bloques de un archivo
    └───────── siete bloques reservados de estructura/directorio

La referencia de bitmaps de ext4 distingue seguimiento de bloques e inodos y describe condiciones de inicialización que importan al leerlos. El dibujo anterior es el estado de un modelo didáctico, sin esas optimizaciones ni variantes.

Lo que un bit no dice

Un bit de bloque ocupado no contiene el nombre del archivo propietario. Un bit de inodo ocupado tampoco contiene el tamaño ni el listado de sus bloques. Los mapas responden a la disponibilidad; la relación con el objeto se encuentra en otras estructuras.

EstructuraPregunta principalInformación insuficiente por sí sola
Bitmap de bloques¿Qué unidades están disponibles para asignación?A qué archivo pertenece cada unidad.
Bitmap de inodos¿Qué entradas pueden asignarse?Qué nombres tiene el objeto.
Inodo y mapa de datos¿Qué metadatos y regiones corresponden al objeto?Todos los nombres que apuntan a él.
Directorio¿Qué objeto identifica cada nombre en este directorio?El inventario global de espacio libre.

Crear un archivo puede requerir una entrada de directorio, un inodo y espacio adicional según el contenido y el formato. Son recursos relacionados, pero distintos. Tener bloques disponibles no garantiza que se pueda crear cualquier cantidad de archivos.

Asignar y liberar implica mantener consistencia entre estructuras. Si un bloque aparece simultáneamente libre y referenciado, otro archivo podría recibirlo. Si figura ocupado sin una referencia recuperable, puede perderse capacidad utilizable. Los mecanismos reales de consistencia y recuperación evitan o reparan estados según su diseño; no se reducen a cambiar un solo bit.

5. La tabla de inodos

En un diseño con tabla, el número de inodo identifica una entrada. En ext4 las tablas pertenecen a los grupos y cada entrada tiene un tamaño indicado por el formato. La documentación de inodos de ext4 describe la relación entre número, grupo y posición dentro de la tabla.

cálculo ilustrativo · entradas numeradas desde uno
Supuestos del ejemplo:
    inodos por grupo = 128
    tamaño de inodo = 256 bytes
    tamaño de bloque = 1024 bytes
    número de inodo = 130

grupo = (130 - 1) / 128 = 1              división entera
índice local = (130 - 1) % 128 = 1
desplazamiento local = 1 × 256 = 256 bytes

La entrada está a 256 bytes del inicio de la tabla del grupo 1.
Ese inicio se obtiene del descriptor del grupo, no de este cálculo solo.

Una tabla con 128 entradas de 256 bytes ocupa como mínimo 32768 bytes, equivalentes a 32 bloques de 1024 en este ejemplo. Una entrada de 256 bytes no es un bloque completo: varias entradas pueden compartir un bloque de tabla.

Si una entrada está libre, el espacio físico de la tabla puede seguir reservado. Liberar el inodo permite reutilizar su entrada; no significa que el volumen reduzca automáticamente la tabla o convierta ese bloque de tabla en contenido de usuario.

La numeración tiene un ámbito

Para identificar un objeto observado con stat en este laboratorio se comparará el par (st_dev, st_ino). Un número de inodo aislado puede repetirse en otro sistema de archivos. También puede reutilizarse después de liberar un objeto, de modo que no es un identificador histórico eterno.

El simulador reserva su entrada 1 para la raíz por comodidad. Eso no describe la numeración de ext4: la raíz de ext4 usa el inodo 2 y existen otros números reservados. Separar ambas convenciones evita interpretar la tabla del modelo como una reproducción literal de un volumen real.

Otros formatos pueden asignar metadatos de forma dinámica en árboles y exponer igualmente números de inodo a la interfaz. La idea de “objeto identificado dentro de un sistema de archivos” es más general que una tabla fija con un número de posiciones decidido al crear el volumen.

6. Qué guarda un inodo

El inodo describe el objeto: tipo, permisos, propietario, tamaño, enlaces y otra información necesaria para administrar o localizar su contenido. Las interfaces devuelven una vista de esos metadatos, sin exponer obligatoriamente su representación exacta en disco.

InformaciónCampo observable habitualInterpretación
Identidadst_dev, st_inoInstancia y número del objeto observado.
Tipo y modost_modeArchivo regular, directorio, enlace u otro tipo; bits de permisos.
Propietariost_uid, st_gidIdentificadores de usuario y grupo.
Tamañost_sizePara un archivo regular, longitud lógica en bytes.
Enlaces durosst_nlinkCantidad de enlaces de nombre; no cuenta descriptores abiertos.
Tiemposst_atim, st_mtim, st_ctimAcceso, modificación de datos y cambio de estado.
Asignación visiblest_blocks, st_blksizeContabilidad de asignación y tamaño preferido de E/S, con significados distintos.

La referencia inode(7) describe estos campos. ctime no es “fecha de creación”: cambia con modificaciones del estado del inodo. Para obtener una fecha de nacimiento, cuando el sistema de archivos la informa, se necesita otra información, como la expuesta por statx.

Qué no pertenece a esa lista

El nombre de una entrada pertenece al directorio que la contiene. El offset de lectura pertenece a la descripción de archivo abierto. El número de descriptor pertenece a la tabla del proceso. Ninguno de esos conceptos debe confundirse con un campo único y permanente del inodo.

responsabilidades · objeto y estado de apertura
proceso: fd 3 ──→ descripción de archivo abierto
                       │ offset y banderas de estado
                       ▼
                    inodo
                       │ tamaño, tipo, dueño, enlaces, mapa de datos
                       ▼
                 contenido del objeto

directorio: "informe.txt" ────────────────────→ ese inodo

El mapa de datos puede usar punteros o estructuras más elaboradas, como extensiones. Además, ciertas formas de contenido pequeño pueden almacenarse dentro de estructuras de metadatos. Por eso “el inodo siempre apunta a un bloque externo por cada fragmento” sería una simplificación excesiva.

En este tema basta comprender la separación de responsabilidades. El tema 18 desarrollará esquemas de asignación y punteros indirectos; no necesitamos anticipar aquí su cálculo completo para entender qué objeto devuelve una consulta de metadatos.

7. Dónde está el nombre

Un directorio relaciona nombres con objetos. En ext4 sus entradas contienen el nombre y el número de inodo, entre otra información de organización. La documentación de entradas de directorio muestra ese vínculo y la posibilidad de que varias entradas apunten al mismo inodo.

dos nombres · un solo objeto
directorio del laboratorio
    "original.txt" ──┐
                     ├──→ inodo X ──→ contenido
    "alias.txt" ─────┘

enlaces de nombre del archivo: 2
archivos de contenido distintos: 1
copias nuevas del contenido por crear alias: ninguna

Un enlace duro añade otra entrada al mismo objeto, dentro de los límites y condiciones del sistema de archivos. No hay un nombre “auténtico” grabado en el inodo que convierta al otro en una copia subordinada. Cambiar el contenido a través de un enlace afecta al mismo objeto visto por el otro.

Un enlace simbólico funciona de otra manera: es un objeto con un texto de ruta que se resuelve al seguirlo. No aumenta el número de enlaces duros del destino. Al quitarlo se elimina ese enlace, no el destino al que apuntaba.

Renombrar y borrar un nombre

Renombrar dentro del laboratorio cambia una entrada sin convertir el objeto en un archivo nuevo. Un descriptor que ya estaba abierto sigue refiriéndose al objeto. Al eliminar un nombre con unlink, disminuye el conteo de enlaces; el contenido puede seguir accesible mediante otro nombre o un descriptor abierto.

El manual de unlink(2) explica la continuidad del archivo abierto después de quitar su último nombre. En el ejemplo regular, al cerrar la última referencia abierta y no quedar enlaces, sus recursos pueden liberarse. Otros tipos de referencias y mecanismos de recuperación pueden añadir condiciones en un sistema real.

Cero nombres no significa cero referencias.

st_nlink cuenta enlaces de nombre, no descriptores. Un fstat() sobre un descriptor conservado puede mostrar cero enlaces y un tamaño distinto de cero. El experimento de la sección 10 hará visible ese estado.

Resolver una ruta completa requiere atravesar componentes y respetar permisos de búsqueda. El inodo del archivo final no contiene necesariamente la cadena completa con la que un proceso llegó hasta él; puede haber varios caminos al mismo objeto.

8. Bloques, tamaño lógico y asignación

El tamaño lógico indica hasta dónde llega el contenido del archivo en su espacio de offsets. La asignación indica qué recursos de almacenamiento se le contabilizan. En un modelo sin huecos y con asignación inmediata, un archivo de S bytes necesita ceil(S / B) bloques de datos de tamaño B, salvo reglas especiales para el archivo vacío.

modelo sin huecos · bloques de 1024 bytes
tamaño lógico    bloques de datos del modelo    capacidad asignada
     0                         0                         0
     1                         1                      1024
  1024                         1                      1024
  1025                         2                      2048
  3072                         3                      3072

La tabla cuenta bloques de contenido; no añade metadatos del volumen.

En un sistema real intervienen huecos, asignación diferida, compresión, compartición y otros mecanismos. El tema 19 estudiará espacio e inodos agotados y archivos dispersos. Aquí evitaremos confundir una fórmula del simulador con una contabilidad universal de todo archivo.

Tres unidades distintas en las herramientas

DatoUnidad que hay que revisarPrecaución
st_sizeBytes lógicos para archivos regulares.No es el número de bloques físicos usados.
st_blocks en LinuxUnidades de 512 bytes.No se multiplica automáticamente por st_blksize.
f_blocks de statvfsUnidades de f_frsize.No es la cantidad de archivos ni de inodos.
f_files de statvfsCantidad de inodos informada por esa interfaz.Su significado útil depende del sistema de archivos.

La información agregada corresponde al sistema de archivos que contiene la ruta consultada, no al tamaño del subdirectorio. Un archivo de imagen tiene además dos perspectivas: el archivo exterior ocupa recursos en el sistema anfitrión y su contenido describe un sistema de archivos interior. Consultar statvfs sobre la ruta de la imagen no interpreta el superbloque que hay dentro.

Los contadores globales pueden cambiar por actividad ajena. Restar dos fotografías alrededor de un archivo pequeño no garantiza obtener exactamente sus costos de metadatos. El laboratorio comprobará identidades y contenido concretos, y utilizará contadores agregados como contexto.

9. Inspeccionar archivo y volumen en C

El primer programa compara lstat(), que observa el enlace simbólico final si lo hay, con stat(), que lo sigue. Después consulta estadísticas del sistema de archivos que contiene el destino. No abre el archivo para leer datos ni necesita que sea un archivo regular.

Estas llamadas requieren comprobar errores y permisos de búsqueda de la ruta; consultar metadatos no equivale a tener permiso para leer el contenido. La referencia de stat(2) describe las vistas de ruta, enlace y descriptor, y advierte que una consulta no es una instantánea transaccional de todos los cambios concurrentes.

inodo-comun.h · impresión sin truncar identificadores y cierre controlado
#ifndef INODO_COMUN_H
#define INODO_COMUN_H
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>

static const char *tipo_inodo(mode_t m)
{
    if (S_ISREG(m)) return "regular";
    if (S_ISDIR(m)) return "directorio";
    if (S_ISLNK(m)) return "simbolico";
    if (S_ISFIFO(m)) return "fifo";
    if (S_ISCHR(m)) return "caracter";
    if (S_ISBLK(m)) return "bloques";
    if (S_ISSOCK(m)) return "socket";
    return "otro";
}

static int foto_inodo(const char *fase, const struct stat *s)
{
    return printf("fase=%s dev=%ju ino=%ju tipo=%s enlaces=%ju bytes=%jd "
                  "bloques512=%jd blksize=%jd uid=%ju gid=%ju permisos=%04jo "
                  "mtime=%jd.%09ld ctime=%jd.%09ld\n",
                  fase, (uintmax_t)s->st_dev, (uintmax_t)s->st_ino,
                  tipo_inodo(s->st_mode), (uintmax_t)s->st_nlink,
                  (intmax_t)s->st_size, (intmax_t)s->st_blocks,
                  (intmax_t)s->st_blksize, (uintmax_t)s->st_uid,
                  (uintmax_t)s->st_gid, (uintmax_t)(s->st_mode & 07777),
                  (intmax_t)s->st_mtim.tv_sec, s->st_mtim.tv_nsec,
                  (intmax_t)s->st_ctim.tv_sec, s->st_ctim.tv_nsec) < 0 ? -1 : 0;
}

static inline int cerrar_inodo(int *fd)
{
    int viejo = *fd;
    *fd = -1;
    return viejo == -1 ? 0 : close(viejo);
}
#endif
inspeccionar-inodo.c · dos vistas del objeto y estadísticas del montaje
#define _POSIX_C_SOURCE 200809L
#define _FILE_OFFSET_BITS 64
#include <stdint.h>
#include <sys/statvfs.h>
#include "inodo-comun.h"

static int capacidad(const char *nombre, uintmax_t unidades, uintmax_t unidad)
{
    if (unidad && unidades > UINTMAX_MAX / unidad) {
        fprintf(stderr, "Capacidad fuera del rango de impresión\n"); return -1;
    }
    return printf("%s=%ju\n", nombre, unidades * unidad) < 0 ? -1 : 0;
}

int main(int argc, char **argv)
{
    struct stat enlace, destino;
    struct statvfs v;
    if (argc != 2) { fprintf(stderr, "Uso: %s RUTA\n", argv[0]); return 1; }
    if (lstat(argv[1], &enlace) == -1) { perror("lstat"); return 1; }
    if (foto_inodo("lstat", &enlace) == -1) return 1;
    if (stat(argv[1], &destino) == -1) { perror("stat del destino"); return 1; }
    if (foto_inodo("stat", &destino) == -1) return 1;
    if (statvfs(argv[1], &v) == -1) { perror("statvfs"); return 1; }
    if (printf("volumen frsize=%ju bsize=%ju bloques=%ju libres=%ju disponibles=%ju "
               "inodos=%ju inodos_libres=%ju inodos_disponibles=%ju\n",
               (uintmax_t)v.f_frsize, (uintmax_t)v.f_bsize,
               (uintmax_t)v.f_blocks, (uintmax_t)v.f_bfree, (uintmax_t)v.f_bavail,
               (uintmax_t)v.f_files, (uintmax_t)v.f_ffree, (uintmax_t)v.f_favail) < 0)
        return 1;
    if (capacidad("capacidad_total_bytes", (uintmax_t)v.f_blocks, v.f_frsize) == -1 ||
        capacidad("capacidad_disponible_bytes", (uintmax_t)v.f_bavail, v.f_frsize) == -1)
        return 1;
    if (fflush(stdout) == EOF) { perror("salida"); return 1; }
    return 0;
}

statvfs informa contadores agregados y una unidad f_frsize para los bloques. Libres y disponibles para usuarios sin privilegios no son necesariamente iguales. La referencia de statvfs(3) advierte que no todos los campos tienen significado útil en todo sistema de archivos.

El programa imprime permisos en octal, identificadores con tipos suficientemente amplios y tiempos como segundos y nanosegundos. st_dev se muestra como un identificador numérico, no como una ruta a un dispositivo. Dos consultas sucesivas pueden observar momentos distintos; el laboratorio presupone un espacio de prueba sin modificaciones concurrentes.

Si el enlace simbólico está roto, lstat puede imprimir sus metadatos y luego stat fallar. En ese caso el programa termina con fracaso, conservando lo que ya había informado. No convierte la falta de destino en un objeto de tamaño cero ni en estadísticas inventadas.

10. Observar la vida de un inodo

El segundo programa crea un directorio nuevo y privado. Dentro crea un archivo, añade un enlace duro, renombra uno de sus nombres y quita ambos. Conserva el descriptor hasta después de la última eliminación para consultar y leer el objeto sin nombre.

La ruta recibida debe ser nueva: mkdir rechaza un directorio existente en lugar de reutilizar archivos ajenos. Las operaciones se realizan con el descriptor de ese directorio y nombres fijos. El ensayo es de un solo proceso; no coordina otros procesos del mismo usuario que decidan modificar ese espacio.

vida-inodo.c · enlaces de nombre y una referencia abierta
#define _POSIX_C_SOURCE 200809L
#define _FILE_OFFSET_BITS 64
#include <fcntl.h>
#include <string.h>
#include "inodo-comun.h"

static int entregar_inodo(int fd, const char *b, size_t n)
{
    size_t p = 0;
    while (p < n) {
        ssize_t k = write(fd, b + p, n - p);
        if (k > 0) { p += (size_t)k; continue; }
        if (k == -1 && errno == EINTR) continue;
        if (k == 0) errno = EIO;
        return -1;
    }
    return 0;
}

static int mostrar_fd(const char *fase, int fd, nlink_t enlaces,
                      const struct stat *identidad)
{
    struct stat s;
    if (fstat(fd, &s) == -1) { perror("fstat"); return -1; }
    if (s.st_dev != identidad->st_dev || s.st_ino != identidad->st_ino ||
        s.st_nlink != enlaces || s.st_size != identidad->st_size) {
        fprintf(stderr, "Identidad, enlaces o tamaño inesperados\n"); return -1;
    }
    return foto_inodo(fase, &s);
}

int main(int argc, char **argv)
{
    static const char texto[] = "inodo vivo\n";
    char recibido[sizeof texto - 1];
    size_t p = 0;
    int dir = -1, fd = -1, estado = 1;
    struct stat inicial, alias;
    if (argc != 2) { fprintf(stderr, "Uso: %s DIRECTORIO_NUEVO\n", argv[0]); return 1; }
    if (mkdir(argv[1], 0700) == -1) { perror("mkdir nuevo"); return 1; }
    dir = open(argv[1], O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
    if (dir == -1) { perror("open directorio"); goto salir; }
    fd = openat(dir, "original.txt", O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
    if (fd == -1) { perror("openat nuevo"); goto salir; }
    if (entregar_inodo(fd, texto, sizeof texto - 1) == -1) { perror("write"); goto salir; }
    if (fstat(fd, &inicial) == -1) { perror("fstat inicial"); goto salir; }
    if (inicial.st_size != (off_t)(sizeof texto - 1) || !S_ISREG(inicial.st_mode)) {
        fprintf(stderr, "Contenido inicial inesperado\n"); goto salir;
    }
    if (mostrar_fd("creado", fd, 1, &inicial) == -1) goto salir;
    if (linkat(dir, "original.txt", dir, "alias.txt", 0) == -1) {
        perror("linkat"); goto salir;
    }
    if (fstatat(dir, "alias.txt", &alias, AT_SYMLINK_NOFOLLOW) == -1) {
        perror("fstatat alias"); goto salir;
    }
    if (alias.st_dev != inicial.st_dev || alias.st_ino != inicial.st_ino) {
        fprintf(stderr, "El alias no identifica el mismo objeto\n"); goto salir;
    }
    if (mostrar_fd("enlazado", fd, 2, &inicial) == -1) goto salir;
    if (renameat(dir, "original.txt", dir, "renombrado.txt") == -1) {
        perror("renameat"); goto salir;
    }
    if (mostrar_fd("renombrado", fd, 2, &inicial) == -1) goto salir;
    if (unlinkat(dir, "alias.txt", 0) == -1) { perror("unlinkat alias"); goto salir; }
    if (mostrar_fd("un_nombre", fd, 1, &inicial) == -1) goto salir;
    if (unlinkat(dir, "renombrado.txt", 0) == -1) { perror("unlinkat ultimo"); goto salir; }
    if (mostrar_fd("sin_nombres", fd, 0, &inicial) == -1) goto salir;
    errno = 0;
    if (fstatat(dir, "renombrado.txt", &alias, AT_SYMLINK_NOFOLLOW) == 0 || errno != ENOENT) {
        fprintf(stderr, "La ruta eliminada no devolvió ENOENT\n"); goto salir;
    }
    while (p < sizeof recibido) {
        ssize_t k = pread(fd, recibido + p, sizeof recibido - p, (off_t)p);
        if (k > 0) { p += (size_t)k; continue; }
        if (k == -1 && errno == EINTR) continue;
        fprintf(stderr, "No se pudo completar la lectura tras unlink\n"); goto salir;
    }
    if (memcmp(recibido, texto, sizeof recibido) != 0) {
        fprintf(stderr, "Contenido distinto tras quitar nombres\n"); goto salir;
    }
    if (puts("lectura_tras_unlink=correcta") == EOF) goto salir;
    if (cerrar_inodo(&fd) == -1) { perror("close archivo"); goto salir; }
    if (cerrar_inodo(&dir) == -1) { perror("close directorio"); goto salir; }
    if (puts("cierre_archivo=correcto") == EOF || fflush(stdout) == EOF) goto salir;
    estado = 0;
salir:
    if (cerrar_inodo(&fd) == -1) { perror("close limpieza archivo"); estado = 1; }
    if (cerrar_inodo(&dir) == -1) { perror("close limpieza directorio"); estado = 1; }
    return estado;
}

Qué demuestra y qué deja fuera

El programa exige conservar el mismo par de identidad y el mismo tamaño en las cinco fases, con enlaces 1, 2, 2, 1, 0. También verifica el alias y el contenido leído después de retirar el último nombre. No exige un valor particular de st_blocks ni que las marcas de tiempo de cada fase sean diferentes.

Se utiliza pread para leer desde cero sin depender del offset que avanzó al escribir. El ejemplo no llama a fsync porque su objetivo es observar identidad y referencias durante la ejecución, no confirmar un protocolo de recuperación frente a una caída.

Si falla una operación, puede quedar un directorio con archivos del ensayo; no se borran automáticamente para ocultar el punto de fracaso. Después del éxito queda el directorio vacío. Al cerrar no se intenta volver a consultar el antiguo número de descriptor: ya podría reutilizarse para otro objeto.

La liberación de capacidad observable puede depender de trabajo diferido, del sistema de archivos y de otras referencias. El programa no promete que una foto global de espacio libre cambie inmediatamente en una cantidad exacta. La propiedad observada es la continuidad del objeto abierto cuando desaparecen sus nombres.

11. Laboratorio de nombres y metadatos

Preparar un directorio de trabajo

En Linux, crea un directorio nuevo en una ubicación de trabajo y guarda allí los tres archivos de C del tema: el encabezado y los dos programas. Las pruebas usan nombres locales y datos pequeños, sin privilegios de administrador.

terminal · contexto y compilación
$ LAB17=$(mktemp -d ./tema17.XXXXXX)
$ cd "$LAB17"
Guarda aquí inodo-comun.h, inspeccionar-inodo.c y vida-inodo.c.
$ uname -r
$ findmnt -T .
$ gcc -std=c11 -O2 -Wall -Wextra -Wpedantic -Werror inspeccionar-inodo.c -o inspeccionar-inodo
$ gcc -std=c11 -O2 -Wall -Wextra -Wpedantic -Werror vida-inodo.c -o vida-inodo

Un objeto con dos nombres y un enlace simbólico

terminal · comparar las vistas de nombres propios
$ python3 -c 'from pathlib import Path; f=Path("datos.txt").open("xb"); f.write(b"hola inodo\n"); f.close()'
$ ln datos.txt alias.txt
$ ln -s datos.txt simbolico.txt
$ ls -li datos.txt alias.txt simbolico.txt
$ ./inspeccionar-inodo datos.txt > datos.log
$ ./inspeccionar-inodo alias.txt > alias.log
$ ./inspeccionar-inodo simbolico.txt > simbolico.log
$ cat datos.log alias.log simbolico.log

En un sistema de archivos que soporte los enlaces del ejemplo, las vistas del archivo y del alias comparten identidad y muestran dos enlaces. lstat del simbólico informa otro objeto; stat sigue el texto de su ruta y devuelve el destino. El simbólico no eleva el conteo de enlaces duros de datos.txt.

No se publican números de inodo prefabricados: los de tu ejecución dependen del sistema. Compara las relaciones entre campos. El tamaño del enlace simbólico corresponde al texto de su destino, no al contenido que se obtiene al seguirlo.

Seguir la vida del objeto

terminal · experimento con nombres retirados
$ ./vida-inodo ciclo > vida.log
$ cat vida.log
$ ls -la ciclo

El último comando muestra el directorio que quedó vacío. No encontrarás dentro el archivo, pero el registro demuestra que el proceso lo leyó cuando su descriptor todavía estaba abierto y ya no quedaban nombres de usuario para buscarlo.

verificar-vida.py · comprobar relaciones, sin fijar números de inodo
from pathlib import Path
import sys

FASES = ("creado", "enlazado", "renombrado", "un_nombre", "sin_nombres")

def verificar(ruta):
    lineas = Path(ruta).read_text(encoding="utf-8").splitlines()
    if len(lineas) != 7 or lineas[-2:] != ["lectura_tras_unlink=correcta", "cierre_archivo=correcto"]:
        raise ValueError("faltan fases o comprobaciones finales")
    identidades = set()
    for linea, fase, enlaces in zip(lineas[:5], FASES, (1, 2, 2, 1, 0)):
        pares = [campo.split("=", 1) for campo in linea.split()]
        f = dict(pares)
        if len(f) != len(pares) or f["fase"] != fase or f["tipo"] != "regular":
            raise ValueError("fase, tipo o campos repetidos incorrectos")
        if int(f["enlaces"]) != enlaces or int(f["bytes"]) != 11:
            raise ValueError("enlaces o tamaño inesperados")
        if int(f["bloques512"]) < 0:
            raise ValueError("asignación negativa")
        identidades.add((int(f["dev"]), int(f["ino"])))
    if len(identidades) != 1:
        raise ValueError("cambió el objeto entre fases")
    print("Cinco fases completas: misma identidad, 11 bytes, enlaces 1/2/2/1/0 y lectura sin nombre correcta")

if len(sys.argv) != 2:
    sys.exit("Uso: python3 verificar-vida.py REGISTRO")
try:
    verificar(sys.argv[1])
except (OSError, ValueError, KeyError, TypeError) as error:
    sys.exit(f"Registro inválido: {error}")
terminal · validar el registro y consultar capacidad agregada
$ python3 verificar-vida.py vida.log
$ df -h .
$ df -i .

Guarda el analizador en el mismo directorio antes de ejecutarlo. Si se omite una fase, cambia la identidad o faltan las comprobaciones finales, el registro se rechaza. Esto valida las relaciones del experimento; no inspecciona después el objeto ya liberado ni certifica su persistencia.

En tmpfs puedes observar relaciones de nombres e inodos, pero no estás leyendo la organización persistente de una tabla ext4. En WSL, montajes de Windows y el sistema de archivos Linux pueden ofrecer capacidades diferentes. Si el entorno rechaza enlaces u otra operación, conserva su error y usa un sistema de archivos que soporte el ensayo para estudiar esa parte.

12. Examinar una imagen ext4 propia

Para acercar el esquema a un formato real crearemos una imagen de 16 MiB. Este apartado requiere las herramientas mke2fs, dumpe2fs y debugfs de e2fsprogs. Si no están instaladas, el resto del tema sigue siendo utilizable; la imagen es un laboratorio adicional.

Los comandos se ejecutan dentro del directorio nuevo del laboratorio. El respaldo se crea con apertura exclusiva para no truncar una imagen existente. Se formatea exclusivamente el archivo local llamado imagen.ext4; no se usa una ruta de dispositivo, no se monta y no se requieren privilegios.

terminal · crear y poblar exclusivamente la imagen del laboratorio
$ command -v mke2fs dumpe2fs debugfs
$ mkdir contenido
$ python3 -c 'from pathlib import Path; f=Path("contenido/saludo.txt").open("xb"); f.write(b"hola\n"); f.close(); g=Path("imagen.ext4").open("xb"); g.truncate(16*1048576); g.close()'
$ mke2fs -q -t ext4 -b 1024 -I 256 -m 0 -d contenido imagen.ext4
$ LC_ALL=C dumpe2fs -h imagen.ext4 > superbloque.txt
$ LC_ALL=C dumpe2fs -g imagen.ext4 > grupos.txt
$ cat superbloque.txt grupos.txt

mke2fs crea el formato y con -d incorpora el contenido preparado. Aquí se eligen bloques de 1024 bytes, entradas de inodo de 256 y cero por ciento de reserva configurada con -m. Las demás características pueden depender de la configuración de la herramienta. Consulta sus opciones en mke2fs(8).

dumpe2fs -h muestra información del superbloque; -g presenta ubicaciones de estructuras por grupo. Su manual explica esas vistas. El archivo no está montado ni cambia durante la inspección, para evitar mezclar una lectura de metadatos con actividad concurrente del sistema de archivos interior.

Del nombre interior al inodo y a sus bloques

terminal · consultas de solo lectura de la imagen
$ debugfs -R 'ls -l /' imagen.ext4
$ debugfs -R 'stat /saludo.txt' imagen.ext4
$ debugfs -R 'blocks /saludo.txt' imagen.ext4
$ ./inspeccionar-inodo imagen.ext4 > imagen-exterior.log
$ cat imagen-exterior.log

debugfs abre por defecto en modo de solo lectura cuando no se solicita -w. Las consultas muestran la raíz interior, los metadatos del archivo de cinco bytes y sus bloques. La referencia de debugfs(8) describe sus comandos y modos.

No fijes en tu informe el número de inodo ni el bloque de datos que viste en otra ejecución. Son asignaciones de esta imagen. Registra la relación: la entrada saludo.txt identifica un inodo interior, ese inodo tiene tamaño cinco y la herramienta conoce cómo interpretar su mapa de datos.

Dos sistemas de archivos, dos perspectivas

ConsultaObjeto observadoTamaño relevante
inspeccionar-inodo imagen.ext4Archivo exterior del sistema anfitrión.16 MiB de longitud lógica.
dumpe2fs -h imagen.ext4Geometría grabada dentro del archivo.Capacidad y unidades del sistema de archivos interior.
debugfs stat /saludo.txtInodo interior de la imagen.Cinco bytes del archivo incorporado.

La imagen se amplió mediante truncate y luego recibió escrituras de formato. Su asignación exterior no tiene por qué coincidir con su longitud lógica. Tampoco el espacio libre interior reduce automáticamente el archivo exterior ni hace que sus bloques coincidan uno a uno con bloques del dispositivo anfitrión.

Un informe breve y verificable

  1. Contexto. Kernel y montaje del directorio que contiene el laboratorio.
  2. Geometría interior. Tamaño de bloque, inodo, cantidades y características informadas por el superbloque.
  3. Organización. Ubicaciones de bitmaps y tabla en la salida por grupos, sin inferirlas del dibujo del modelo.
  4. Relación de nombres. Entrada de saludo, inodo interior y tamaño observado.
  5. Límite. No se montó la imagen ni se investigó recuperación después de una caída; son consultas de un formato estable.

El journal y otras estructuras pueden consumir parte del espacio de la imagen. Por eso no hay que exigir que “bloques libres = bloques totales menos el archivo de cinco bytes”. El formato necesita capacidad para administrarse.

13. Simulador de volumen e inodos

El volumen didáctico tiene 24 bloques de 1024 bytes y ocho inodos. El inodo 1 y los bloques 0 a 6 están reservados para raíz y estructuras. Los archivos regulares pueden utilizar los inodos 2 a 8 y diecisiete bloques de datos. Se asigna inmediatamente, sin huecos, compresión ni journal.

Nombres, objetos, asignaciones y referencias abiertas

superbloque y mapas del modelo · uno indica ocupado
InodoEstadoBytesEnlacesAbiertosBloques
directorio y tabla de descriptores del proceso del modelo
Historial de operaciones

    Recorridos propuestos

    1. Nombre y objeto. Crea datos con 2048 bytes y añade alias como enlace duro. Observa que solo aumenta el conteo de nombres.
    2. Una modificación compartida. Selecciona alias como origen y cambia a 3072 bytes. Ambos nombres siguen apuntando al mismo inodo con tres bloques.
    3. Sin nombre, todavía abierto. Abre datos y quita los dos nombres. El inodo conserva bloques mientras su descriptor está ocupado; ciérralo y observa la liberación.
    4. Archivo vacío. Reinicia y crea varios nombres distintos con tamaño cero. Se ocupan entradas de inodo aunque no se asignen bloques de datos a esos archivos.
    5. Capacidad. Reinicia y crea archivos de 8192, 8192 y 1024 bytes con nombres distintos. Se ocupan los diecisiete bloques; otro archivo no vacío no encuentra capacidad.
    6. Reutilización. Quita el último nombre de un archivo que no esté abierto y crea otro. Una entrada libre puede reutilizarse: no confundas su número con una identidad histórica eterna.

    Reglas didácticas: el directorio admite hasta dieciséis entradas de usuario dentro de su bloque reservado. Los nombres empiezan por una letra ASCII y tienen hasta dieciséis caracteres, con letras, números, punto, guion y guion bajo. No se simulan subdirectorios, enlaces simbólicos, permisos, descriptores duplicados, memoria mapeada ni recuperación.

    Renombrar rechaza un destino existente en este modelo; no implementa el reemplazo de rename de Linux. Cambiar tamaño reserva o libera bloques inmediatamente y no reproduce la semántica de huecos de ftruncate. Si faltan recursos, la operación del modelo se rechaza sin modificar su estado anterior.

    El conteo de descriptores es explícito y separado del de enlaces. Un inodo regular se libera cuando ambos llegan a cero en este universo limitado. Ningún botón crea archivos, formatea almacenamiento ni cambia los mapas de un volumen real: todas las estructuras viven en JavaScript.

    14. Resumen

    15. Preguntas de repaso y práctica

    1. ¿Volumen y montaje son lo mismo?

    No. El volumen es una unidad lógica de respaldo; el montaje incorpora una instancia al espacio de nombres. Puede haber relaciones más complejas que una partición por un único punto de montaje.

    2. ¿Qué responsabilidad tiene el superbloque?

    Reunir información global para interpretar y administrar el sistema de archivos, como geometría, identidad y características. No contiene por sí solo todos los nombres ni todos los mapas de archivos.

    3. ¿Por qué la capacidad bruta no es toda capacidad de contenido?

    El formato necesita almacenar metadatos, estructuras de seguimiento y directorios. También pueden existir reservas y límites independientes, como la cantidad de inodos disponibles.

    4. ¿Qué aporta un descriptor de grupo?

    Información para localizar y administrar recursos del grupo. En ext4 las características afectan su distribución, por lo que no se deben inferir todas las posiciones a partir de un dibujo fijo.

    5. ¿Qué diferencia hay entre los dos bitmaps?

    Uno sigue disponibilidad de bloques y el otro disponibilidad de entradas de inodo. Un archivo vacío puede consumir un inodo sin consumir bloques de contenido en el modelo.

    6. ¿Un bit ocupado identifica al archivo propietario?

    No. Informa ocupación en la convención del mapa. Para relacionar recursos con objetos se necesitan estructuras adicionales, como el inodo, su mapa de datos y las entradas de directorio.

    7. ¿Cómo se calcula la posición local del inodo 130 del ejemplo?

    Con 128 inodos por grupo, pertenece al grupo 1 y tiene índice local 1 al restar primero uno. Con entradas de 256 bytes, queda a 256 bytes del inicio de esa tabla, cuyo inicio se obtiene del descriptor.

    8. ¿Liberar un inodo reduce físicamente su tabla?

    No necesariamente. En un diseño de tabla reservada, la entrada vuelve a estar disponible, mientras el espacio de la tabla sigue perteneciendo a la estructura del formato.

    9. ¿Basta comparar st_ino para identificar objetos?

    Dentro del laboratorio se compara el par st_dev y st_ino. El número aislado puede repetirse en otro sistema de archivos y reutilizarse después de liberar un objeto.

    10. ¿Dónde se guarda el nombre?

    En la entrada del directorio que lo relaciona con un objeto. El inodo no tiene un único nombre completo que represente todas las rutas posibles para llegar a él.

    11. ¿Dónde está el offset de lectura?

    En el estado de la descripción de archivo abierto, no como un único offset del inodo. Diferentes aperturas pueden recorrer posiciones distintas del mismo objeto.

    12. ¿ctime significa creación?

    No. Es tiempo de cambio del estado del inodo. La fecha de nacimiento, cuando se informa, es otro dato y no debe deducirse de ctime.

    13. ¿Crear un enlace duro duplica los datos?

    No. Añade un nombre al mismo objeto bajo las condiciones del sistema de archivos. Ambos nombres observan su contenido y comparten metadatos del archivo.

    14. ¿Un enlace simbólico aumenta st_nlink del destino?

    No. Es un objeto distinto con un texto de ruta. lstat observa el enlace final; stat lo sigue y obtiene metadatos del destino si puede resolverlo.

    15. ¿Puede un archivo tener cero enlaces y seguir siendo legible?

    Sí, si el proceso conserva un descriptor válido en el experimento regular. Quitar el último nombre impide buscarlo por esa entrada, pero no elimina inmediatamente esa referencia abierta.

    16. ¿Se calcula asignación multiplicando st_blocks por st_blksize?

    No. En Linux st_blocks se expresa en unidades de 512 bytes. st_blksize tiene otro significado como tamaño preferido de E/S. Ninguno es por sí solo un mapa físico del dispositivo.

    17. ¿statvfs de imagen.ext4 muestra el sistema de archivos interior?

    No. Consulta el sistema de archivos anfitrión que contiene esa ruta. Para interpretar el formato interior usamos herramientas que leen los bytes de la imagen como ext4.

    18. ¿La raíz del simulador y la de ext4 tienen el mismo número?

    No. El modelo reserva su entrada 1 por comodidad; ext4 utiliza el inodo 2 para la raíz y tiene otras reservas. Las reglas del simulador no deben usarse como posiciones literales del formato real.

    Para practicar

    1. Dibuja las capas entre una ruta, el objeto identificado y sus bloques. Sitúa el nombre, el offset y el número de descriptor en sus estructuras correspondientes.
    2. Calcula la capacidad bruta y la disponible para datos del volumen de 24 bloques. Explica por qué todavía puede fallar una creación con bloques libres.
    3. Repite el cálculo de posición local para inodos 1, 128, 129 y 256 con la geometría de la sección 5.
    4. Ejecuta el inspector sobre el archivo, su alias y el simbólico. Compara identidades sin fijar números de otra máquina.
    5. Crea un enlace simbólico roto en el directorio del ensayo y observa qué parte del informe puede producirse antes del fracaso.
    6. Ejecuta vida-inodo y verifica el registro. Señala el momento en que ya no quedan nombres, pero todavía se conserva contenido accesible.
    7. Elimina una fase de una copia del registro y duplica otra en una segunda. Comprueba que el verificador rechaza ambos casos.
    8. Prueba vida-inodo con un directorio ya existente que contenga un archivo tuyo de prueba. Verifica que no lo reutiliza ni altera su contenido.
    9. En la imagen ext4, localiza tamaño de bloque, tamaño de inodo y ubicaciones de tablas. Separa lo elegido por opciones de lo que dependió de la herramienta.
    10. Explica las perspectivas interior y exterior de la imagen y por qué sus números de inodo pertenecen a ámbitos distintos.
    11. En el simulador, conserva un descriptor después de quitar todos los nombres. Intenta asignar el espacio retenido y después cierra para observar la liberación.
    12. Diseña una comprobación de consistencia del modelo: todo bloque de datos ocupado debe pertenecer a un inodo activo y todo enlace debe referir a una entrada activa.

    En el tema 18 estudiaremos la asignación de bloques y los punteros indirectos: cómo representar el mapa de contenido y cómo calcular límites de tamaño a partir de bloques, punteros y niveles de indirección.