Semana 13 — RAID y la interfaz de archivos
Objetivos
- Comparar los niveles RAID 0, 1, 4 y 5 en capacidad, confiabilidad y rendimiento.
- Usar la API de archivos y directorios de Unix y entender inodos (inodes), enlaces duros (hard links) y simbólicos (symbolic links).
Sesión A — RAID
Lecturas:
Temas:
- Interfaz y modelo de fallos (fail-stop).
- RAID 0 (striping), RAID 1 (mirroring), RAID 4 (paridad), RAID 5 (paridad rotada).
- El problema de la escritura pequeña (small-write problem).
Sesión B — Archivos y directorios
Lecturas:
- Cap. 39 Interlude: Files and Directories · código
Temas:
open,read,write,lseek,fsync,rename,stat,unlink.- Tabla de archivos abiertos (open file table), descriptores compartidos tras
fork/dup. - Directorios, enlaces duros vs. simbólicos, contador de referencias.
- Permisos,
mkfsymount.
Laboratorio — raid.py y la API de archivos
raid.py: preguntas 1–5 del cap. 38.- Tarea de código del cap. 39: implementar
mystat,myls -l,mytail -ny una búsqueda recursiva tipofind.
Entregas
- P4 entrega.
- P5 publicado: Archivos grandes y enlaces simbólicos en xv6 (lab
fsdel MIT). Entrega en semana 16.
Preguntas de repaso
- ¿Cuántas E/S cuesta una escritura pequeña en RAID 4? ¿Por qué RAID 5 mejora el rendimiento de escrituras aleatorias?
- ¿Por qué se llama
unlinky nodelete? - Si dos procesos abren el mismo archivo por separado, ¿comparten el desplazamiento (offset)? ¿Y un padre y un hijo después de
fork?
Código de la semana
Compilar dentro del contenedor del curso con make o gcc -Wall.
Ejemplos
Los programas que usan los apuntes y la clase.
// atomic.c — reemplazar el contenido de un archivo de forma atómica:
// escribir un temporal, fsync, rename, fsync del directorio.
//
// Uso: ./atomic ARCHIVO TEXTO reemplaza ARCHIVO por TEXTO
// ./atomic ARCHIVO TEXTO crash se "cae" antes del rename
//
// Después de un fallo, ARCHIVO tiene el contenido viejo o el nuevo completo,
// nunca una mezcla ni un archivo vacío.
#include <fcntl.h>
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static void die(const char *msg) { perror(msg); exit(1); }
int main(int argc, char *argv[]) {
if (argc < 3) { fprintf(stderr, "uso: %s ARCHIVO TEXTO [crash]\n", argv[0]); return 1; }
char tmp[4096], dir[4096];
snprintf(tmp, sizeof tmp, "%s.tmp", argv[1]);
snprintf(dir, sizeof dir, "%s", argv[1]);
// 1. Escribir la versión nueva completa en un archivo temporal
int fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) die("open");
size_t n = strlen(argv[2]);
if (write(fd, argv[2], n) != (ssize_t)n || write(fd, "\n", 1) != 1)
die("write");
// 2. Forzarla al disco: sin esto, el rename podría
// llegar al disco antes que los datos
if (fsync(fd) < 0) die("fsync");
close(fd);
printf("escrito y sincronizado: %s\n", tmp);
if (argc > 3 && strcmp(argv[3], "crash") == 0) {
printf("*** caída simulada antes de rename ***\n");
fflush(stdout);
_exit(1); // termina sin limpiar nada
}
// 3. rename es atómico: el nombre apunta al inodo
// viejo o al nuevo, nunca a una mezcla
if (rename(tmp, argv[1]) < 0) die("rename");
// 4. El cambio de nombre vive en el directorio:
// sincronizarlo también
int dfd = open(dirname(dir), O_RDONLY | O_DIRECTORY);
if (dfd < 0) die("open dir");
if (fsync(dfd) < 0) die("fsync dir");
close(dfd);
printf("rename %s -> %s listo\n", tmp, argv[1]);
return 0;
}// raid5.c — un arreglo RAID-5 de juguete en memoria: paridad XOR,
// falla de un disco, reconstrucción y escritura pequeña.
//
// Uso: ./raid5 [DISCO_QUE_FALLA] (0..4, por defecto 2)
//
// Cinco "discos" de NSTRIPES bloques de BS bytes. Paridad rotada al estilo
// de OSTEP (fig. 38.7): en la franja s la paridad vive en el disco
// N-1 - (s % N) y los datos siguen a su derecha, dando la vuelta.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 5 // discos
#define NSTRIPES 4 // franjas (stripes)
#define BS 8 // bytes por bloque
#define NDATA ((N - 1) * NSTRIPES) // bloques lógicos de datos
static unsigned char disk[N][NSTRIPES][BS];
static int failed = -1; // disco caído (-1: ninguno)
static int nreads, nwrites; // E/S físicas contadas
static int parity_disk(int s) { return N - 1 - (s % N); }
// Bloque lógico a -> (disco, franja)
static void map(int a, int *d, int *s) {
*s = a / (N - 1);
*d = (parity_disk(*s) + 1 + a % (N - 1)) % N;
}
static void xor_into(unsigned char *dst, const unsigned char *src) {
for (int i = 0; i < BS; i++)
dst[i] ^= src[i];
}
static void read_blk(int d, int s, unsigned char *buf) {
if (d == failed) { fprintf(stderr, "leer disco caído\n"); exit(1); }
memcpy(buf, disk[d][s], BS);
nreads++;
}
static void write_blk(int d, int s, const unsigned char *buf) {
memcpy(disk[d][s], buf, BS);
nwrites++;
}
// Paridad de la franja s = XOR de sus N-1 bloques de datos.
static void full_parity(int s, unsigned char *p) {
memset(p, 0, BS);
for (int d = 0; d < N; d++)
if (d != parity_disk(s))
xor_into(p, disk[d][s]);
}
static void print_bits(const char *label, unsigned char c) {
printf(" %-7s", label);
for (int b = 7; b >= 0; b--)
putchar(c >> b & 1 ? '1' : '0');
printf(" (0x%02x '%c')\n", c, c >= 32 && c < 127 ? c : '.');
}
int main(int argc, char *argv[]) {
const char *text = "Si un disco falla, la paridad XOR reconstruye sus "
"bloques: basta leer los otros cuatro discos de la "
"franja y combinarlos con XOR";
unsigned char saved[NSTRIPES][BS], p[BS];
int fail = argc > 1 ? atoi(argv[1]) : 2;
if (fail < 0 || fail >= N) { fprintf(stderr, "disco 0..%d\n", N - 1); return 1; }
// 1. Escribir los datos franja por franja (escrituras de franja completa)
for (int a = 0; a < NDATA; a++) {
int d, s;
map(a, &d, &s);
size_t off = (size_t)a * BS;
memset(disk[d][s], '.', BS);
if (off < strlen(text)) {
size_t n = strlen(text) - off;
memcpy(disk[d][s], text + off, n < BS ? n : BS);
}
}
for (int s = 0; s < NSTRIPES; s++) {
full_parity(s, p);
memcpy(disk[parity_disk(s)][s], p, BS);
}
printf("Distribución (bloque lógico o P = paridad):\n ");
for (int d = 0; d < N; d++) printf(" disco%d", d);
for (int s = 0; s < NSTRIPES; s++) {
printf("\n f%d: ", s);
for (int d = 0; d < N; d++) {
if (d == parity_disk(s)) { printf(" P%d", s); continue; }
for (int a = 0; a < NDATA; a++) {
int dd, ss;
map(a, &dd, &ss);
if (dd == d && ss == s) printf(" %6d", a);
}
}
}
// 2. Paridad con bits reales: primer byte de cada bloque de la franja 0
printf("\n\nFranja 0, primer byte de cada bloque:\n");
for (int d = 0; d < N; d++) {
char label[16];
snprintf(label, sizeof label, d == parity_disk(0) ? "P0" : "D%d", d);
print_bits(label, disk[d][0][0]);
}
// 3. Falla un disco: se pierde todo su contenido
for (int s = 0; s < NSTRIPES; s++)
memcpy(saved[s], disk[fail][s], BS);
memset(disk[fail], 0, sizeof disk[fail]);
failed = fail;
printf("\nFalla el disco %d. Reconstrucción:\n", fail);
nreads = nwrites = 0;
int ok = 0;
for (int s = 0; s < NSTRIPES; s++) {
unsigned char rec[BS] = {0}, tmp[BS];
// XOR de los N-1 sobrevivientes
for (int d = 0; d < N; d++) {
if (d == fail) continue;
read_blk(d, s, tmp);
xor_into(rec, tmp);
}
int same = memcmp(rec, saved[s], BS) == 0;
ok += same;
if (fail == parity_disk(s)) { // era paridad: se muestra en hex
printf(" f%d: P%d =", s, s);
for (int i = 0; i < BS; i++) printf(" %02x", rec[i]);
} else {
printf(" f%d: \"%.*s\"", s, BS, rec);
}
printf(" %s\n", same ? "== original" : "DISTINTO");
}
printf(" %d/%d bloques recuperados, %d lecturas\n", ok, NSTRIPES, nreads);
for (int s = 0; s < NSTRIPES; s++) // disco de reemplazo
memcpy(disk[fail][s], saved[s], BS);
failed = -1;
// 4. Escritura pequeña (método sustractivo): bloque lógico 6
int a = 6, d, s;
unsigned char old[BS], newb[BS], pold[BS];
map(a, &d, &s);
memcpy(newb, "NUEVO!!!", BS);
nreads = nwrites = 0;
int pd = parity_disk(s);
read_blk(d, s, old); // dato viejo
read_blk(pd, s, pold); // paridad vieja
// P_nueva = P_vieja ^ D_viejo ^ D_nuevo
xor_into(pold, old);
xor_into(pold, newb);
write_blk(d, s, newb); // dato nuevo
write_blk(pd, s, pold); // paridad nueva
full_parity(s, p);
printf("\nEscritura pequeña al bloque %d:\n", a);
printf(" dato en disco %d, paridad en disco %d\n", d, pd);
printf(" %d lecturas + %d escrituras = %d E/S\n",
nreads, nwrites, nreads + nwrites);
printf(" paridad sustractiva %s recalculada\n",
memcmp(p, disk[pd][s], BS) == 0 ? "==" : "!=");
return 0;
}Compilación
Cómo se compilan y ejecutan los ejemplos.
# Semana 13: RAID y la interfaz de archivos.
# make compila todo
# make test arma un árbol de prueba en /tmp/w13 y corre cada programa
# make clean borra los binarios
CFLAGS = -Wall -Wextra -g
PROGS = raid5 shareoff atomic mystat myls mytail myfind
# Las soluciones aún no publicadas (code/.ocultos) no están en el repositorio:
# se omiten los programas sin fuente y sus líneas en las pruebas.
PROGS := $(foreach p,$(PROGS),$(if $(wildcard $(p).c),$(p)))
RUN = $(if $(wildcard $(1).c),./$(1),@: sin $(1).c)
T = /tmp/w13
all: $(PROGS)
$(PROGS): %: %.c
$(CC) $(CFLAGS) -o $@ $<
# Árbol de prueba: archivo, enlace duro, enlace simbólico, enlace roto,
# subdirectorios y un archivo de 2000 líneas.
setup:
rm -rf $(T)
mkdir -p $(T)/docs/notas $(T)/src
seq -f "línea %g" 1 2000 > $(T)/log.txt
echo hola > $(T)/docs/a.txt
ln $(T)/docs/a.txt $(T)/docs/b.txt
ln -s a.txt $(T)/docs/sim
ln -s nada.txt $(T)/docs/roto
touch $(T)/src/main.c $(T)/src/util.c $(T)/docs/notas/clase.md
chmod 600 $(T)/docs/notas/clase.md
test: $(PROGS) setup
./raid5 2
./shareoff $(T)/off.txt
$(call RUN,mystat) $(T)/docs/a.txt $(T)/docs/sim
$(call RUN,mystat) $(T)/docs
$(call RUN,myls) $(T)/docs
$(call RUN,myls) -l $(T)/docs
$(call RUN,mytail) -3 $(T)/log.txt
$(call RUN,myfind) $(T)
$(call RUN,myfind) $(T) -name '*.c'
$(call RUN,myfind) $(T) -type l
./atomic $(T)/config.txt "version 1"
./atomic $(T)/config.txt "version 2" crash || true
cat $(T)/config.txt
ls $(T)
./atomic $(T)/config.txt "version 2"
cat $(T)/config.txt
clean:
rm -f $(PROGS)
.PHONY: all setup test clean