tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

Rust 43Markdown 24TOML 17Shell 2805 Definiciones

SDD 25: las tasas del kernel medidas, y el MEMCG que falta en las recetas

16 tasas de Linux medidas en momento (KVM, 4 vCPU, bajo carga): piso de
syscall, /proc como API de texto, fork vs espacio de direcciones del padre,
enlazado dinámico, VFS, io_uring, THP, namespaces. Programas en C y salidas
crudas en docs/evidencia/tasas-kernel-2026-08-29/.

Lo que sale de medir y después verificar contra las recetas: los kernels de
hammer se construyen sin CONFIG_MEMCG (no está en x86_64_defconfig y no tiene
default y), y arje/sandokan escriben memory.max descartando el error. Un tope
de memoria pedido por una Card no se aplica y sólo deja un warn.

Tres resultados que contradicen el consejo de manual y quedan documentados:
THP 3x más lento que 4K con defrag=madvise, io_uring 2,8x más lento que pread
sobre tmpfs, y stat cuesta lo mismo en tmpfs que en ext4 (es tasa del VFS).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih

aeeaa6d07fcd98d955e98e3b6779693a80f07cf5 · por Sergio · (hace 1 mes) · padre 2176f26c0 · ver el árbol en este commit

Delta semántico · 64

docs/evidencia/tasas-kernel-2026-08-29/bench_extra.c

docs/evidencia/tasas-kernel-2026-08-29/bench_io.c

docs/evidencia/tasas-kernel-2026-08-29/bench_pin.c

docs/evidencia/tasas-kernel-2026-08-29/bench_proc.c

10 archivos · +1013 −0

diff --git a/docs/25-tasas-del-kernel.md b/docs/25-tasas-del-kernel.md
new file mode 100644
index 00000000..86cd351b
--- /dev/null
+++ b/docs/25-tasas-del-kernel.md
@@ -0,0 +1,379 @@
+# SDD 25 — Las tasas del kernel: qué cobra Linux, cuánto, y por dónde se esquiva
+
+Escrito 2026-08-29. Nace de una pregunta del operador: *«¿hay cómo crear módulos para
+kernel para optimizar cosas que son estándares y por eso no se cambian — por ejemplo una API
+más cercana del control de procesos sin que sandokan use `/proc`? Con tawasuyu ponerse de
+acuerdo para pasar por debajo de mecanismos pesados POSIX.»*
+
+`/proc` y sandokan eran el ejemplo, no el alcance. Lo que sigue es **la lista entera de tasas
+que Linux cobra por compatibilidad histórica, medida en esta máquina**, y para cada una: si
+se esquiva, con qué, y quién paga hoy.
+
+El espejo de este documento para el lado tawasuyu es
+`tawasuyu/HANDOFF-TASAS-KERNEL-DESDE-HAMMER.md` (mismo patrón que ADR 0010 ↔
+`HANDOFF-arranque-grafo.md`).
+
+## 0. La respuesta corta
+
+1. **Un módulo cargable no es una opción en hammer y no hace falta.** Las tres recetas de
+   kernel (`linux-generic.toml:43`, `linux-metal.toml:104`, `linux-metal-dual.toml:51`)
+   configuran `-d MODULES`: kernel monolítico. `linux.toml` no lo apaga pero sólo compila
+   `bzImage`, nunca `make modules`. Cualquier código de kernel propio en hammer es
+   **built-in**, o sea un parche a la receta — que además es lo correcto para el proyecto,
+   porque la fase `configure` **es** la identidad del artefacto (SDD 22) y así queda sellado
+   y atestado junto al kernel.
+2. **Casi todo lo que duele ya tiene salida en mainline y nadie la está usando.** `pidfd`,
+   `cgroup` v2 agregado, `posix_spawn`, `getdents64`, `close_range`, `openat2`. Cero código
+   de kernel, ganancias de 3× a 17× medidas abajo.
+3. **La medición encontró un bug cruzado que no se buscaba**: los kernels de hammer se
+   construyen **sin `CONFIG_MEMCG`**, y arje/sandokan escriben `memory.max` ignorando el
+   error. Un límite de memoria pedido por una Card **no se aplica y nadie se entera**. §4.
+
+## 1. Método y condiciones — leer antes de citar un número
+
+- **Máquina**: `momento`, KVM sobre AMD EPYC-Rome, 4 vCPU, 7,7 GiB RAM, Linux 7.1.4,
+  glibc/Artix. Store en `/dev/sdb` (ext4), árbol en `/dev/sdc` (ext4), `/tmp` tmpfs.
+- **Mitigaciones activas**: retpolines (`spectre_v2`), retbleed, SSBD por prctl. **Sin PTI**
+  (AMD, `meltdown: Not affected`). En un Intel con PTI el piso de syscall sería peor.
+- **La máquina estaba CARGADA**: load 7,3 sobre 4 vCPU, un build de LLVM en vuelo, 773 MiB
+  libres. ⇒ **los absolutos son cotas superiores; la señal está en los cocientes dentro de
+  una misma corrida**, que es como está escrito todo lo de abajo.
+- Cada medida son 7–9 rondas; se reporta **mínimo** (mejor caso, el estimador robusto al
+  ruido del scheduler) y **mediana**. Las corridas marcadas «pinneado» fijan el proceso a un
+  cpu para sacar de en medio la cola del planificador.
+- Fuentes y salidas crudas: `docs/evidencia/tasas-kernel-2026-08-29/`.
+- **Piso de referencia**: una syscall nula (`getppid`) cuesta **78,7 ns**. El vDSO
+  (`clock_gettime`) cuesta 31,2 ns y por syscall 143,8 ns ⇒ **cruzar al kernel son ~110 ns**.
+  Todo lo de abajo se lee en «syscalls equivalentes».
+
+## 2. La lista
+
+### T1 · `fork()` copia el espacio de direcciones del padre
+
+| padre con … | `fork+_exit+wait` | `posix_spawn+wait` |
+|---|---|---|
+| 0 MB tocados | 109 µs | 231 µs |
+| 64 MB | 938 µs | 234 µs |
+| 256 MB | **3 949 µs** | **231 µs** |
+
+≈ **59 ns por página de 4 KiB del padre**, ida y vuelta. `posix_spawn` (que usa
+`CLONE_VFORK|CLONE_VM`) es **plano**: no mira el tamaño del padre. A 256 MB, **17× más
+barato — haciendo estrictamente más trabajo** (además ejecuta un binario).
+
+Es la tasa POSIX por excelencia: `fork` no se puede arreglar sin romper la semántica, y la
+salida está dentro del propio POSIX.
+
+### T2 · `exec` + enlazado dinámico
+
+| | µs/exec |
+|---|---|
+| musl ESTÁTICO | **77,4** |
+| glibc ESTÁTICO | 232,7 |
+| glibc DINÁMICO | 324,3 |
+
+`ld.so` cuesta **+92 µs** (+39%). Pero el salto grande es glibc→musl: **4,2× entre el peor y
+el mejor**, con el mismo `main(){return 0;}`. El corpus de hammer ya está del lado bueno
+(verificado: los binarios del store son `statically linked`); esto **cuantifica** lo que la
+de-Alpinización compró: un build que lanza 20 000 procesos se ahorra ~5 s sólo en arranque.
+
+### T3 · `/proc` es una API de TEXTO, y se paga como tal
+
+| | ns | = syscalls |
+|---|---|---|
+| `getrusage(RUSAGE_SELF)` | 238 | 3 |
+| `clock_gettime(PROCESS_CPUTIME)` | 214 | 3 |
+| `pread`+parse `/proc/self/stat` (fd ya abierto) | 1 271 | 16 |
+| `open`+`read`+parse `/proc/self/stat` | **2 692** | **34** |
+| `open`+`read`+parse `/proc/self/status` (VmRSS) | **4 643** | **59** |
+
+Leer el CPU propio por `/proc` cuesta **11× más** que `getrusage`. Y la mitad del coste es
+abrir la ruta, no el texto (1 271 vs 2 692 con el fd cacheado).
+
+**Barrido completo** (291 procesos vivos): `readdir` solo **106 µs**; `readdir` + `stat` de
+cada pid **991 µs**, o sea **3,4 µs por proceso**.
+
+### T4 · Contabilidad por unidad: cgroup v2 es O(1) donde `/proc` es O(procesos)
+
+Leer `cpu.stat` + `memory.current` + `pids.current` de un cgroup: **4,6 µs**, y **no depende
+de cuántos procesos tenga la unidad**. Sumar `/proc` para esa misma unidad: 3,4 µs × N.
+**El cruce está en N = 2.** Para una unidad de 20 procesos, 15× a favor del cgroup.
+
+### T5 · Liveness por `/proc/<pid>`: la carrera, no la lentitud
+
+| | ns |
+|---|---|
+| `access("/proc/<pid>")` — lo que se usa hoy | 345 |
+| `kill(pid, 0)` | 119 |
+| `pidfd_send_signal(fd, 0)` | 119 |
+
+`pidfd` **no es más rápido que `kill(0)`**; es 2,9× más rápido que la ruta `/proc`. Su valor
+real es otro: **no hay reuso de PID**, y el fd se vuelve legible al morir el proceso ⇒ entra
+en el `epoll` y el supervisor deja de sondear.
+
+### T6 · Señales: la notificación más cara de POSIX
+
+`kill(self,SIGUSR1)` + handler = **1 165 ns = 15 syscalls**. Un `eventfd` en el bucle de
+eventos hace el mismo trabajo por una fracción.
+
+### T7 · Despertar cuesta µs; sincronizar en usuario cuesta ns
+
+| | ns |
+|---|---|
+| `pthread_mutex` lock+unlock sin disputa | **8,6** |
+| RTT `eventfd` (2 hilos, mismo cpu) | 2 507 |
+| RTT `pipe` | 2 956 |
+| RTT `socketpair` AF_UNIX | 3 639 |
+
+**291× entre sincronizar en espacio de usuario y despertar por el kernel.** El bus de arje
+(postcard sobre socket Unix) paga ~3,6 µs por ida y vuelta: aceptable por mensaje, ruinoso
+por campo.
+
+Adyacente: `sched_yield` 174 ns; `nanosleep(1 ms)` se pasa **65 µs** bajo carga ⇒ los lazos
+de control no se construyen sobre `sleep`.
+
+### T8 · `stat()` es tasa del VFS, no del filesystem
+
+| ns/fichero | ext4 | tmpfs |
+|---|---|---|
+| `stat()` por ruta completa | 648 | 714 |
+| `fstatat(dirfd, nombre)` | 439 | 489 |
+| `statx(…, DONT_SYNC)` | 467 | 593 |
+| `openat`+`close` | 899 | 961 |
+| **`getdents64` (barrido, sin stat)** | **168** | **59** |
+
+**ext4 ≈ tmpfs en `stat`.** ⇒ una tormenta de `stat` **no se arregla cambiando de
+filesystem**; se arregla no haciéndola: `getdents64` da el barrido **3,9× más barato en ext4
+y 12× en tmpfs**, y `fstatat` relativo a un `dirfd` ahorra 32% sobre la ruta completa.
+
+**Profundidad de ruta**: 12 componentes extra cuestan +205 ns en ext4 y +203 en tmpfs ⇒
+**~17 ns por componente**, idéntico en ambos. Otra vez: el VFS, no el disco.
+
+### T9 · `openat2` con `RESOLVE_BENEATH` es casi gratis
+
+ext4: 1 025 vs 899 ns (+14%). tmpfs: 763 vs 961 ns (midió *más rápido*, o sea dentro del
+ruido). ⇒ **la resolución hermética de rutas no tiene precio apreciable.** Es material
+directo para harkaq (SDD 16): la garantía «no se sale del árbol» sin demonio auxiliar y sin
+una syscall extra.
+
+### T10 · Materializar un árbol: hardlink vs copia
+
+| 2 000 ficheros de 1 byte | ext4 | tmpfs |
+|---|---|---|
+| `link()` | 9,2 µs | 2,9 µs |
+| `open+read+write+close` | 142,2 µs | 5,0 µs |
+| **cociente** | **15,4×** | 1,7× |
+
+Lo que hace cara la copia es el **journal de ext4**, no el VFS. La estrategia de hardlinks de
+`.dmerge` está bien elegida — y su contrapartida ya está documentada (la caché retiene lo que
+se borra del store).
+
+### T11 · `copy_file_range`/`sendfile` no ganan sobre ext4
+
+64 MiB en caché: `read+write` 2 837 MB/s · `sendfile` 3 345 · `copy_file_range` 3 521. ~20%
+en el mejor caso. **ext4 no tiene reflink**, así que `copy_file_range` cae a una copia dentro
+del kernel. El «copiar en O(1)» que la gente espera **exige un FS con reflink** (xfs/btrfs) —
+sin medir acá, §9.
+
+### T12 · io_uring no es una ganancia universal
+
+| | ns/op |
+|---|---|
+| syscall nula | 78,7 |
+| **io_uring NOP en lote (piso de operación)** | **47,7** |
+| `pread` 4K en caché (ext4) | 540 |
+| io_uring read 4K profundidad 32 (ext4) | 594 |
+| `pread` 4K (tmpfs) | 602 |
+| io_uring read 4K (tmpfs) | **1 672** |
+
+El piso por operación es **0,61× una syscall**: en lote, io_uring amortiza el cruce. Pero
+sobre datos ya cacheados **pierde** — 10% en ext4 y **2,8× en tmpfs**. io_uring paga cuando
+las operaciones son muchas, agrupables y **de verdad bloquean**. Sobre caché es una
+pesimización.
+
+### T13 · THP puede ser 3× PEOR (y acá lo es)
+
+64 MiB anónimos, ns por página de 4 KiB: `MADV_HUGEPAGE` **3 046** · `MADV_NOHUGEPAGE`
+**1 013** · `MAP_POPULATE` **752**.
+
+La causa está en la máquina, no en el kernel:
+`/sys/kernel/mm/transparent_hugepage/defrag = madvise` ⇒ una región con `MADV_HUGEPAGE`
+dispara **compactación síncrona** en el fallo de página, y con 773 MiB libres eso es caro.
+**THP bajo presión de memoria es un pasivo de latencia, no una optimización.** Reproducido en
+las dos corridas independientes (128 MiB y 64 MiB).
+
+`mmap`+`munmap` de 4K: 1 664 ns.
+
+### T14 · El sandbox por namespaces cuesta ~1 ms por proceso
+
+`fork+exec` base 1 649 µs · con `unshare` de 6 namespaces 2 742 µs · con `USER|MNT` 2 050 µs.
+⇒ **+1,09 ms** por el juego completo, **+0,40 ms** por USER|MNT.
+
+Para hammer es ruido por fase de build (segundos a minutos). Sería caro **sólo si se
+enjaulara por-exec**: 20 000 execs × 1,09 ms = 22 s.
+
+### T15 · Cerrar descriptores antes del `exec`
+
+`dup+close` uno a uno: 0,2 µs/fd ⇒ 512 fds = 102 µs. `close_range(lo,hi)`: por debajo del
+ruido. Gratis, y ya está en mainline.
+
+### T16 · Hilo vs proceso
+
+`pthread_create`+`join` = **11,9 µs**, contra 109 µs de `fork+_exit` con el padre vacío:
+**9×**, y la distancia crece con el tamaño del padre (T1).
+
+## 3. Las tres sorpresas
+
+Las anoto aparte porque contradicen el consejo de manual:
+
+1. **THP más lento que 4K** (T13) — el manual dice que THP acelera; con `defrag=madvise` y
+   memoria apretada, triplica el coste del fallo.
+2. **io_uring más lento que `pread`** sobre tmpfs (T12) — el manual dice que io_uring es el
+   futuro; sobre datos cacheados el futuro pierde 2,8×.
+3. **`stat` cuesta lo mismo en tmpfs que en ext4** (T8) — invalida la reacción natural
+   («movamos el árbol a tmpfs y vuela»): el coste está en el VFS.
+
+## 4. El hallazgo cruzado: los kernels de hammer no tienen `MEMCG`
+
+No lo buscaba; salió de verificar la recomendación antes de escribirla.
+
+**Lado hammer.** Las recetas hacen `make ARCH=x86_64 defconfig` y después
+`scripts/config`. Lo que `arch/x86/configs/x86_64_defconfig` trae de contabilidad:
+`CONFIG_TASKSTATS=y`, `TASK_DELAY_ACCT=y`, `CGROUPS=y`, `CGROUP_SCHED=y`, `CGROUP_PIDS=y`,
+`CGROUP_FREEZER=y`, `CONNECTOR=y` ⇒ y `PROC_EVENTS` es `default y` con `depends on
+CONNECTOR=y`, así que **también entra**.
+
+**`CONFIG_MEMCG` no está en el defconfig y no tiene `default y` ⇒ queda en `n` en todos los
+kernels de hammer.** `CONFIG_PSI`, igual.
+
+**Lado tawasuyu.** `sandokan-core::Engine` expone `set_memory_max` / `set_memory_high`
+(`shared/sandokan/sandokan-core/src/engine.rs:60-72`), `sandokan-local` los delega a
+`arje_incarnate::cgroup` (`03_ukupacha/sandokan/sandokan-local/src/lib.rs:675-681`), y ahí:
+
+```rust
+// arje/init/arje-incarnate/src/cgroup.rs:60
+let _ = std::fs::write(abs.join("cpu.weight"), format!("{w}\n"));   // error DESCARTADO
+// :79
+match std::fs::write(&path, format!("{mem}\n")) {
+    Err(e) => tracing::warn!(..., "memory.max write failed"),        // sólo un warn
+```
+
+**Consecuencia.** Sobre un sistema hammer, una Card que pide un tope de memoria arranca
+**sin tope**, y la única huella es una línea de log. Es exactamente la forma de fallo que
+`CLAUDE.md` §3 nombra: *un ausente falla ruidosamente; un vacío llega hasta el final diciendo
+que todo fue bien*. La jaula que no está se comporta igual que la jaula que sí está, hasta
+que hace falta.
+
+Lo que **sí** funciona en un kernel hammer: `cpu.weight` (CGROUP_SCHED), `pids.max`
+(CGROUP_PIDS), `cpu.stat` (rstat del core) y `cgroup.freeze` — este último porque el freezer
+v2 es `obj-y` en `kernel/cgroup/Makefile`, no el `CONFIG_CGROUP_FREEZER` de v1.
+
+## 5. Qué se esquiva SIN escribir kernel
+
+| Tasa | Reemplazo mainline | Ganancia medida |
+|---|---|---|
+| T1 `fork` de padre grande | `posix_spawn` / `CLONE_VFORK` | 17× a 256 MB, y plano |
+| T2 `ld.so` | binario estático musl | 4,2× |
+| T3 `/proc` texto | `getrusage`, `taskstats` | 11× |
+| T4 sumar `/proc` por unidad | agregado de cgroup v2 | O(1) vs O(N), cruce en N=2 |
+| T5 liveness por `/proc` | `pidfd` (+ sin carrera de PID) | 2,9× y correctitud |
+| T3 enumerar procesos | proc connector (netlink): eventos push | deja de sondear |
+| T6 señales | `eventfd`/`signalfd`/`pidfd` | 15 syscalls → 1 |
+| T8 tormenta de `stat` | `getdents64`, `fstatat(dirfd,…)` | 3,9×–12× |
+| T9 hermetismo de rutas | `openat2 RESOLVE_BENEATH` | ~gratis |
+| T10 materializar árbol | `link()` | 15,4× en ext4 |
+| T15 cerrar fds | `close_range` | 102 µs → ~0 |
+
+**Ninguna de estas exige un módulo, un parche ni un kernel propio.** Es la primera conclusión
+del documento y la que ordena todo lo demás.
+
+## 6. Qué exigiría tocar el kernel — y su precio en hammer
+
+Después de agotar §5 quedan tres deseos legítimos que mainline no da:
+
+1. **Una syscall que devuelva un lote de estadísticas por proceso en binario.** Lo más
+   cercano es `taskstats` (por tarea, netlink) y el agregado de cgroup. No existe el «dame
+   los 291 en una llamada».
+2. **Eventos de estado con más contexto que el proc connector.**
+3. **Un plano de control de unidades que no pase por el filesystem.**
+
+Su precio, concreto:
+
+- **Es built-in, no `.ko`** (`-d MODULES`). ⇒ parche a la receta ⇒ entra en `hash_inputs`
+  ⇒ kernel nuevo, artefacto nuevo, y rebase manual en cada versión.
+- **Nunca un número de syscall nuevo**: choca con upstream para siempre. Lo compatible es un
+  char device, un fichero propio en sysfs, o una kfunc BPF.
+- **Con eBPF (que es la forma sin código out-of-tree) el precio es `DEBUG_INFO_BTF`**, hoy
+  apagado en las cuatro recetas con motivo escrito (`linux.toml:32`: *«pahole (BTF), ausente
+  del toolchain»*). Deuda ya medio pagada: dwarves construye (frente libdw/musl cerrado).
+- **El bloqueo de diseño, que no es técnico**: `SDD-SERVIDOR-AJENO.md` promete *«cero
+  invasión — la máquina sigue siendo su Ubuntu de siempre»*. Un sandokan que dependa de un
+  kernel hammer deja de correr ahí. ⇒ **el núcleo se queda portable siempre; lo rápido es un
+  backend opcional detectado en runtime, con `/proc` como respaldo que no se borra.**
+
+## 7. Propuestas — lado hammer
+
+- **H1 · `-e MEMCG -e PSI` en las cuatro recetas de kernel.** Cierra §4 y habilita
+  `memory.current` (T4) y las señales de presión. **Precio declarado**: re-hashea todos los
+  artefactos de kernel ⇒ hacerlo en la misma tanda que cualquier otro cambio de kernel, nunca
+  suelto.
+- **H2 · `MODULES=n` se queda.** Si algún día hace falta código de kernel, es built-in y
+  parcheado en la receta. Queda escrito para no rediscutirlo.
+- **H3 · `harkaq` usa `openat2 RESOLVE_BENEATH|RESOLVE_NO_SYMLINKS`** donde hoy valida rutas
+  a mano (T9, ~gratis).
+- **H4 · Auditar los `Command` de hammer que ponen `pre_exec`**: Rust apaga el camino
+  `posix_spawn` en cuanto hay `pre_exec`, y cae a `fork+exec` (T1). `hammer-build/sandbox.rs`
+  lanza `bwrap` sin `pre_exec` ⇒ está bien hoy; el punto es que sea una regla, no una suerte.
+- **H5 · La materialización de dependencias se queda en hardlink** (T10) y el experimento de
+  reflink (§9) alimenta SDD 18 (wawafs), donde el store como FS vivo cambiaría el cociente.
+- **H6 · No `madvise(MADV_HUGEPAGE)` en el camino de build** mientras `defrag=madvise` y la
+  granja corra apretada de RAM (T13).
+- **H7 · `DEBUG_INFO_BTF` sigue apagado** hasta que haya un consumidor real de BPF; cuando lo
+  haya, la deuda es pahole en el lab y un re-hasheo, no un rediseño.
+
+## 8. Propuestas — lado tawasuyu
+
+Detalle en el handoff espejo. En una línea cada una:
+
+- **W1 · `pidfd` para liveness y señales** en `sandokan-local` (hoy `access("/proc/<pid>")` y
+  `waitpid(WNOHANG)` en sondeo). Se gana correctitud (carrera de PID) antes que velocidad.
+- **W2 · Telemetría por unidad desde el cgroup**, no sumando `/proc` (T4). Con **sonda de
+  capacidad**: si `memory.current` no existe (kernel sin MEMCG), decirlo, no callarlo.
+- **W3 · `arje-incarnate` con `posix_spawn`** donde el `pre_exec` no sea imprescindible (T1),
+  y `close_range` en el que quede.
+- **W4 · Los errores de cgroup dejan de descartarse.** `let _ = write(...)` y `warn!` sobre un
+  límite no aplicado son el bug de §4, no un detalle de estilo.
+- **W5 · `/proc` no se borra nunca**: es el respaldo portable que hace posible
+  SDD-SERVIDOR-AJENO.
+- **W6 · El contrato entre repos es el trait `Engine`, no una ABI de kernel.** hammer publica
+  la lista de símbolos garantizados (verificable con `hammer kernel probe`); tawasuyu elige
+  backend en runtime.
+
+## 9. Lo que NO pude medir (experimentos nombrados)
+
+Ninguno se omite en silencio:
+
+1. **proc connector y `taskstats`**: suscribirse al netlink pide `CAP_NET_ADMIN`; el uid de
+   la sesión es 1001. Experimento: un binario chico corriendo como root en la granja,
+   midiendo latencia de evento `fork/exec/exit` contra el sondeo de `/proc`.
+2. **Copia con reflink** (T11): exige `mkfs` sobre un loop (root). Experimento: xfs y btrfs
+   en loop, `copy_file_range` de 64 MiB, esperado O(1) contra los 2 837 MB/s de ext4.
+3. **Iterador BPF contra `/proc`** (T3): exige BTF y privilegios. Va detrás de H7.
+4. **io_uring con `SQPOLL`**: exige `CAP_SYS_NICE`. Cambiaría el veredicto de T12 para el
+   caso «muchas operaciones que bloquean».
+5. **Coste real de una tanda de hammer en syscalls**: sin `strace`/`perf` en esta máquina no
+   se puede atribuir. Experimento: instalarlos en la granja y contar `stat`/`open` por
+   invocación de compilador, para saber si T8 vale segundos o minutos por receta.
+6. **Un Intel con PTI**: todos los pisos de syscall de acá son de un AMD sin PTI. En el
+   laptop (TigerLake) las mismas medidas darían peor, y ahí T3/T6 duelen más.
+
+## 10. Evidencia
+
+`docs/evidencia/tasas-kernel-2026-08-29/` — cuatro programas en C
+(`bench_proc.c`, `bench_io.c`, `bench_extra.c`, `bench_pin.c`) y sus cinco salidas crudas
+(`proc.txt`, `io-ext4.txt`, `io-tmpfs.txt`, `extra.txt`, `pin.txt`). Se compilan con
+`cc -O2` (`bench_io` pide `-luring`, `bench_proc`/`bench_pin` piden `-lpthread`) y se corren
+sin argumentos salvo `bench_io <dir>` y `bench_pin <estatico> <dinamico> <musl>`.
+
+**Nota de unidad**: en `pin.txt` la sección de fallos de página imprime ns/página × 1000 por
+un factor de escala equivocado en el programa; los valores citados en T13 son los de esa
+salida divididos por 1000, y coinciden con `io-ext4.txt`, que sí está en la unidad correcta.
diff --git a/docs/evidencia/tasas-kernel-2026-08-29/bench_extra.c b/docs/evidencia/tasas-kernel-2026-08-29/bench_extra.c
new file mode 100644
index 00000000..02063073
--- /dev/null
+++ b/docs/evidencia/tasas-kernel-2026-08-29/bench_extra.c
@@ -0,0 +1,92 @@
+/* bench_extra.c — lo que las dos sorpresas piden aislar:
+ *  a) coste de fork EN FUNCION del espacio de direcciones del padre
+ *  b) contabilidad por cgroup v2 vs sumar /proc
+ *  c) tasa del enlazado dinamico en el exec
+ *  d) cierre de descriptores en el spawn  */
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <errno.h>
+#include <spawn.h>
+#include <dirent.h>
+#include <sched.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+extern char **environ;
+static volatile long sink;
+static inline double now_ns(void){ struct timespec t; clock_gettime(CLOCK_MONOTONIC,&t); return t.tv_sec*1e9+t.tv_nsec; }
+static int cmpd(const void*a,const void*b){ double x=*(const double*)a,y=*(const double*)b; return (x>y)-(x<y); }
+#define ROUNDS 7
+static void rep(const char*n,double*r,int c){ qsort(r,c,sizeof(double),cmpd);
+  printf("%-44s %10.1f %10.1f\n",n,r[0],r[c/2]); fflush(stdout); }
+
+static double *area=NULL; static long area_sz=0;
+static void grow(long mb){
+  if(area) munmap(area,area_sz);
+  area_sz=mb*1024L*1024; if(mb==0){area=NULL;return;}
+  area=mmap(0,area_sz,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0);
+  madvise(area,area_sz,MADV_NOHUGEPAGE);          /* tablas de paginas reales, no THP */
+  for(long o=0;o<area_sz;o+=4096) ((char*)area)[o]=1;
+}
+static double t_fork(int n){ double t0=now_ns();
+  for(int i=0;i<n;i++){ pid_t p=fork(); if(p==0)_exit(0); int st; waitpid(p,&st,0);} return (now_ns()-t0)/n/1000; }
+static double t_vfork_exec(int n,const char*prog){ double t0=now_ns();
+  for(int i=0;i<n;i++){ pid_t p=vfork(); if(p==0){char*a[]={(char*)prog,0};execve(prog,a,environ);_exit(127);} int st; waitpid(p,&st,0);} return (now_ns()-t0)/n/1000; }
+static double t_fork_exec(int n,const char*prog){ double t0=now_ns();
+  for(int i=0;i<n;i++){ pid_t p=fork(); if(p==0){char*a[]={(char*)prog,0};execve(prog,a,environ);_exit(127);} int st; waitpid(p,&st,0);} return (now_ns()-t0)/n/1000; }
+static double t_spawn(int n,const char*prog){ double t0=now_ns();
+  for(int i=0;i<n;i++){ pid_t p; char*a[]={(char*)prog,0}; if(posix_spawn(&p,prog,0,0,a,environ)==0){int st;waitpid(p,&st,0);} } return (now_ns()-t0)/n/1000; }
+
+int main(int argc,char**argv){
+  const char *sta = argc>1?argv[1]:"/bin/true";
+  const char *dyn = argc>2?argv[2]:"/bin/true";
+  printf("%-44s %10s %10s\n","BENCH (us/op)","min","mediana");
+  printf("--- a) fork segun el espacio de direcciones del padre ---\n");
+  long mbs[]={0,16,64,256};
+  for(unsigned k=0;k<sizeof mbs/sizeof*mbs;k++){
+    grow(mbs[k]); double r[ROUNDS],r2[ROUNDS],r3[ROUNDS];
+    for(int q=0;q<ROUNDS;q++) r[q]=t_fork(150);
+    for(int q=0;q<ROUNDS;q++) r2[q]=t_fork_exec(120,sta);
+    for(int q=0;q<ROUNDS;q++) r3[q]=t_spawn(120,sta);
+    char n1[80],n2[80],n3[80];
+    snprintf(n1,sizeof n1,"  padre %3ld MB tocados: fork+_exit",mbs[k]);
+    snprintf(n2,sizeof n2,"  padre %3ld MB tocados: fork+exec",mbs[k]);
+    snprintf(n3,sizeof n3,"  padre %3ld MB tocados: posix_spawn(CLONE_VFORK)",mbs[k]);
+    rep(n1,r,ROUNDS); rep(n2,r2,ROUNDS); rep(n3,r3,ROUNDS); }
+  grow(0);
+
+  printf("--- c) tasa del enlazado dinamico en exec ---\n");
+  { double r[ROUNDS]; for(int q=0;q<ROUNDS;q++) r[q]=t_vfork_exec(150,sta); rep("  vfork+exec binario ESTATICO",r,ROUNDS);
+    for(int q=0;q<ROUNDS;q++) r[q]=t_vfork_exec(150,dyn); rep("  vfork+exec binario DINAMICO",r,ROUNDS); }
+
+  printf("--- d) cierre de descriptores antes del exec ---\n");
+  { int base=open("/dev/null",O_RDONLY); int fds[512]; for(int i=0;i<512;i++) fds[i]=dup(base);
+    double r[ROUNDS];
+    for(int q=0;q<ROUNDS;q++){ double t0=now_ns(); for(int i=0;i<512;i++){int f=dup(base); close(f);} r[q]=(now_ns()-t0)/512/1000; }
+    rep("  dup+close x512, uno a uno (us/fd)",r,ROUNDS);
+    for(int q=0;q<ROUNDS;q++){ int a[512]; for(int i=0;i<512;i++) a[i]=dup(base);
+      double t0=now_ns(); sink=syscall(SYS_close_range,a[0],a[511],0); r[q]=(now_ns()-t0)/512/1000; }
+    rep("  close_range(lo,hi) (us/fd)",r,ROUNDS);
+    for(int i=0;i<512;i++) close(fds[i]); close(base); }
+
+  printf("--- b) contabilidad: cgroup v2 vs sumar /proc ---\n");
+  { char cg[512]="", path[600]; FILE*f=fopen("/proc/self/cgroup","r");
+    if(f){ char line[512]; while(fgets(line,sizeof line,f)){ char*p=strstr(line,"0::"); if(p){ p+=3; p[strcspn(p,"\n")]=0; snprintf(cg,sizeof cg,"/sys/fs/cgroup%s",p);} } fclose(f);} 
+    if(cg[0]){ printf("  cgroup: %s\n",cg);
+      const char*campos[]={"cpu.stat","memory.current","pids.current"};
+      double r[ROUNDS];
+      for(int q=0;q<ROUNDS;q++){ int N=2000; double t0=now_ns();
+        for(int i=0;i<N;i++){ for(int c=0;c<3;c++){ snprintf(path,sizeof path,"%s/%s",cg,campos[c]);
+            int fd=open(path,O_RDONLY); if(fd<0)continue; char b[512]; sink=read(fd,b,sizeof b); close(fd);} }
+        r[q]=(now_ns()-t0)/N/1000; }
+      rep("  leer cpu.stat+memory.current+pids.current",r,ROUNDS);
+    } else printf("  sin cgroup v2 legible\n"); }
+  return 0;
+}
diff --git a/docs/evidencia/tasas-kernel-2026-08-29/bench_io.c b/docs/evidencia/tasas-kernel-2026-08-29/bench_io.c
new file mode 100644
index 00000000..def491e8
--- /dev/null
+++ b/docs/evidencia/tasas-kernel-2026-08-29/bench_io.c
@@ -0,0 +1,199 @@
+/* bench_io.c — metadatos de FS, resolucion de rutas, copia, io_uring, fallos de pagina, namespaces. */
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/syscall.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <sys/sendfile.h>
+#include <dirent.h>
+#include <linux/openat2.h>
+#include <liburing.h>
+
+extern char **environ;
+static volatile long sink;
+static inline double now_ns(void){ struct timespec t; clock_gettime(CLOCK_MONOTONIC,&t); return t.tv_sec*1e9+t.tv_nsec; }
+static int cmpd(const void*a,const void*b){ double x=*(const double*)a,y=*(const double*)b; return (x>y)-(x<y); }
+#define ROUNDS 7
+static void report(const char*n,double*r,int c,long it){ qsort(r,c,sizeof(double),cmpd);
+  printf("%-40s %10.1f %10.1f   (n=%ld)\n",n,r[0],r[c/2],it); fflush(stdout); }
+#define BENCH(name,iters,...) do{ double _r[ROUNDS]; long _N=(iters); \
+  for(int _q=0;_q<ROUNDS;_q++){ double _t0=now_ns(); for(long i=0;i<_N;i++){ __VA_ARGS__; } \
+    _r[_q]=(now_ns()-_t0)/(double)_N; } report(name,_r,ROUNDS,_N);}while(0)
+
+#define NFILES 4000
+static char root[512];
+
+static void mk_tree(const char *base){
+  mkdir(base,0755);
+  char p[600]; snprintf(p,sizeof p,"%s/flat",base); mkdir(p,0755);
+  for(int i=0;i<NFILES;i++){ char f[700]; snprintf(f,sizeof f,"%s/flat/f%05d",base,i);
+    int fd=open(f,O_CREAT|O_WRONLY|O_TRUNC,0644); if(fd>=0){ (void)!write(fd,"x",1); close(fd);} }
+  /* ruta profunda: base/d/d/d/... (12 niveles) con un fichero al fondo */
+  char deep[600]; snprintf(deep,sizeof deep,"%s/deep",base); mkdir(deep,0755);
+  for(int i=0;i<12;i++){ strcat(deep,"/d"); mkdir(deep,0755); }
+  char df[700]; snprintf(df,sizeof df,"%s/x",deep);
+  int fd=open(df,O_CREAT|O_WRONLY,0644); if(fd>=0){(void)!write(fd,"x",1);close(fd);}
+}
+static void rm_tree(const char *base){ char c[700]; snprintf(c,sizeof c,"rm -rf '%s'",base); (void)!system(c); }
+
+static void metadata(const char *base,const char *etiqueta){
+  char flat[600]; snprintf(flat,sizeof flat,"%s/flat",base);
+  int dfd=open(flat,O_RDONLY|O_DIRECTORY);
+  printf("\n### metadatos sobre %s (%d ficheros en un directorio)\n",etiqueta,NFILES);
+  printf("%-40s %10s %10s\n","BENCH (ns/fichero)","min","mediana");
+  BENCH("stat() por ruta completa",           NFILES, {struct stat s;char f[700];snprintf(f,sizeof f,"%s/f%05d",flat,(int)(i%NFILES));sink=stat(f,&s);});
+  BENCH("fstatat(dirfd, nombre)",             NFILES, {struct stat s;char f[32];snprintf(f,sizeof f,"f%05d",(int)(i%NFILES));sink=fstatat(dfd,f,&s,0);});
+  BENCH("statx(dirfd,nombre,DONT_SYNC)",      NFILES, {struct statx s;char f[32];snprintf(f,sizeof f,"f%05d",(int)(i%NFILES));sink=syscall(SYS_statx,dfd,f,AT_STATX_DONT_SYNC,STATX_BASIC_STATS,&s);});
+  BENCH("openat(dirfd,nombre)+close",         NFILES, {char f[32];snprintf(f,sizeof f,"f%05d",(int)(i%NFILES));int fd=openat(dfd,f,O_RDONLY);if(fd>=0)close(fd);});
+  { struct open_how how; memset(&how,0,sizeof how); how.flags=O_RDONLY; how.resolve=RESOLVE_BENEATH|RESOLVE_NO_SYMLINKS;
+    int probe=syscall(SYS_openat2,dfd,"f00000",&how,sizeof how);
+    if(probe>=0){ close(probe);
+      BENCH("openat2 RESOLVE_BENEATH+NO_SYMLINKS",NFILES,{char f[32];snprintf(f,sizeof f,"f%05d",(int)(i%NFILES));int fd=syscall(SYS_openat2,dfd,f,&how,sizeof how);if(fd>=0)close(fd);});
+    } else printf("openat2: no disponible (%s)\n",strerror(errno)); }
+  /* getdents64: barrido completo, coste por entrada */
+  { double r[ROUNDS]; long cnt=0;
+    for(int q=0;q<ROUNDS;q++){ char buf[65536]; long c=0; double t0=now_ns();
+      int fd=open(flat,O_RDONLY|O_DIRECTORY); long n;
+      while((n=syscall(SYS_getdents64,fd,buf,sizeof buf))>0){ long off=0; while(off<n){ struct dirent64 *e=(void*)(buf+off); c++; off+=e->d_reclen; } }
+      close(fd); r[q]=(now_ns()-t0)/(double)c; cnt=c; }
+    report("getdents64 (barrido, sin stat)",r,ROUNDS,cnt); }
+  close(dfd);
+  /* profundidad de ruta */
+  { char shallow[700],deep[700]; snprintf(shallow,sizeof shallow,"%s/flat/f00000",base);
+    snprintf(deep,sizeof deep,"%s/deep",base); for(int i=0;i<12;i++) strcat(deep,"/d"); strcat(deep,"/x");
+    BENCH("open ruta corta (2 componentes bajo base)",20000,{int fd=open(shallow,O_RDONLY);if(fd>=0)close(fd);});
+    BENCH("open ruta 13 componentes mas profunda",   20000,{int fd=open(deep,O_RDONLY);if(fd>=0)close(fd);}); }
+}
+
+static void copia(const char *base){
+  const long SZ = 64L*1024*1024;
+  char src[700],dst[700]; snprintf(src,sizeof src,"%s/src.bin",base); snprintf(dst,sizeof dst,"%s/dst.bin",base);
+  int fd=open(src,O_CREAT|O_WRONLY|O_TRUNC,0644);
+  char *buf=malloc(1<<20); memset(buf,0xA5,1<<20);
+  for(long w=0;w<SZ;w+=1<<20) (void)!write(fd,buf,1<<20);
+  close(fd); free(buf);
+  printf("\n### copiar %ld MB (fichero en cache de pagina)\n",SZ>>20);
+  printf("%-40s %10s %10s\n","BENCH (MB/s)","max","mediana");
+  double r[ROUNDS];
+  for(int q=0;q<ROUNDS;q++){ int s=open(src,O_RDONLY),d=open(dst,O_CREAT|O_WRONLY|O_TRUNC,0644);
+    char b[65536]; ssize_t n; double t0=now_ns(); while((n=read(s,b,sizeof b))>0) (void)!write(d,b,n);
+    r[q]=SZ/((now_ns()-t0)/1e9)/1e6; close(s);close(d);} 
+  qsort(r,ROUNDS,sizeof(double),cmpd); printf("%-40s %10.1f %10.1f\n","read()+write() 64K",r[ROUNDS-1],r[ROUNDS/2]);
+  for(int q=0;q<ROUNDS;q++){ int s=open(src,O_RDONLY),d=open(dst,O_CREAT|O_WRONLY|O_TRUNC,0644);
+    off_t off=0; double t0=now_ns(); while(off<SZ){ ssize_t n=sendfile(d,s,&off,SZ-off); if(n<=0)break; }
+    r[q]=SZ/((now_ns()-t0)/1e9)/1e6; close(s);close(d);} 
+  qsort(r,ROUNDS,sizeof(double),cmpd); printf("%-40s %10.1f %10.1f\n","sendfile()",r[ROUNDS-1],r[ROUNDS/2]);
+  for(int q=0;q<ROUNDS;q++){ int s=open(src,O_RDONLY),d=open(dst,O_CREAT|O_WRONLY|O_TRUNC,0644);
+    loff_t io=0,oo=0; double t0=now_ns(); long left=SZ;
+    while(left>0){ ssize_t n=syscall(SYS_copy_file_range,s,&io,d,&oo,left,0); if(n<=0)break; left-=n; }
+    r[q]=SZ/((now_ns()-t0)/1e9)/1e6; close(s);close(d);} 
+  qsort(r,ROUNDS,sizeof(double),cmpd); printf("%-40s %10.1f %10.1f\n","copy_file_range()",r[ROUNDS-1],r[ROUNDS/2]);
+  /* materializacion de arbol: hardlink vs copia, 2000 ficheros pequeños */
+  char lk[700]; snprintf(lk,sizeof lk,"%s/mat",base); mkdir(lk,0755);
+  printf("\n### materializar 2000 ficheros de 1 byte (el problema del store)\n");
+  BENCH("link() [hardlink]",2000,{char a[700],b[700];snprintf(a,sizeof a,"%s/flat/f%05d",base,(int)i);snprintf(b,sizeof b,"%s/mat/l%05d",base,(int)i);unlink(b);sink=link(a,b);});
+  BENCH("open+read+write+close [copia]",2000,{char a[700],b[700];char c[64];snprintf(a,sizeof a,"%s/flat/f%05d",base,(int)i);snprintf(b,sizeof b,"%s/mat/c%05d",base,(int)i);int s=open(a,O_RDONLY),d=open(b,O_CREAT|O_WRONLY|O_TRUNC,0644);ssize_t n=read(s,c,sizeof c);(void)!write(d,c,n);close(s);close(d);});
+  unlink(src); unlink(dst);
+}
+
+static void uring(const char *base){
+  const long SZ=64L*1024*1024; const int BS=4096; const int DEPTH=32;
+  char src[700]; snprintf(src,sizeof src,"%s/u.bin",base);
+  int fd=open(src,O_CREAT|O_RDWR|O_TRUNC,0644);
+  char *b=malloc(1<<20); memset(b,7,1<<20); for(long w=0;w<SZ;w+=1<<20)(void)!write(fd,b,1<<20); free(b);
+  fsync(fd);
+  printf("\n### 4K aleatorios sobre 64MB en cache: syscall vs io_uring\n");
+  printf("%-40s %10s %10s\n","BENCH (ns/lectura)","min","mediana");
+  char *buf=aligned_alloc(4096,BS*DEPTH);
+  long nblk=SZ/BS;
+  BENCH("pread() 4K",50000,{sink=pread(fd,buf,BS,(long)((i*7919)%nblk)*BS);});
+  struct io_uring ring;
+  if(io_uring_queue_init(256,&ring,0)==0){
+    double r[ROUNDS];
+    for(int q=0;q<ROUNDS;q++){ long done=0,total=50000; double t0=now_ns();
+      while(done<total){ int batch=DEPTH; if(total-done<batch)batch=total-done;
+        for(int k=0;k<batch;k++){ struct io_uring_sqe*s=io_uring_get_sqe(&ring);
+          io_uring_prep_read(s,fd,buf+k*BS,BS,(long)(((done+k)*7919)%nblk)*BS); }
+        io_uring_submit_and_wait(&ring,batch);
+        struct io_uring_cqe*c; unsigned h; int seen=0;
+        io_uring_for_each_cqe(&ring,h,c){ seen++; } io_uring_cq_advance(&ring,seen);
+        done+=batch; }
+      r[q]=(now_ns()-t0)/total; }
+    report("io_uring read 4K (profundidad 32)",r,ROUNDS,50000);
+    /* piso de submission: NOP en lote */
+    for(int q=0;q<ROUNDS;q++){ long done=0,total=200000; double t0=now_ns();
+      while(done<total){ int batch=64; if(total-done<batch)batch=total-done;
+        for(int k=0;k<batch;k++){ struct io_uring_sqe*s=io_uring_get_sqe(&ring); io_uring_prep_nop(s); }
+        io_uring_submit_and_wait(&ring,batch);
+        struct io_uring_cqe*c; unsigned h; int seen=0;
+        io_uring_for_each_cqe(&ring,h,c){ seen++; } io_uring_cq_advance(&ring,seen);
+        done+=batch; }
+      r[q]=(now_ns()-t0)/total; }
+    report("io_uring NOP (piso de operacion)",r,ROUNDS,200000);
+    io_uring_queue_exit(&ring);
+  } else printf("io_uring: no disponible (%s)\n",strerror(errno));
+  free(buf); close(fd); unlink(src);
+}
+
+static void memoria(void){
+  const long SZ=128L*1024*1024;
+  printf("\n### fallos de pagina: tocar 128MB anonimos (ns por pagina de 4K)\n");
+  printf("%-40s %10s %10s\n","BENCH","min","mediana");
+  double r[ROUNDS]; long pages=SZ/4096;
+  for(int q=0;q<ROUNDS;q++){ char*m=mmap(0,SZ,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0);
+    madvise(m,SZ,MADV_HUGEPAGE); double t0=now_ns();
+    for(long o=0;o<SZ;o+=4096) m[o]=1; r[q]=(now_ns()-t0)/pages; munmap(m,SZ);} 
+  report("MADV_HUGEPAGE (THP)",r,ROUNDS,pages);
+  for(int q=0;q<ROUNDS;q++){ char*m=mmap(0,SZ,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0);
+    madvise(m,SZ,MADV_NOHUGEPAGE); double t0=now_ns();
+    for(long o=0;o<SZ;o+=4096) m[o]=1; r[q]=(now_ns()-t0)/pages; munmap(m,SZ);} 
+  report("MADV_NOHUGEPAGE (4K de a uno)",r,ROUNDS,pages);
+  for(int q=0;q<ROUNDS;q++){ double t0=now_ns();
+    char*m=mmap(0,SZ,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS|MAP_POPULATE,-1,0);
+    for(long o=0;o<SZ;o+=4096) m[o]=1; r[q]=(now_ns()-t0)/pages; munmap(m,SZ);} 
+  report("MAP_POPULATE (prefault)",r,ROUNDS,pages);
+  BENCH("mmap+munmap 4K (churn)",100000,{void*m=mmap(0,4096,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0);munmap(m,4096);});
+}
+
+static void namespaces(void){
+  printf("\n### coste del sandbox: crear namespaces (us por proceso)\n");
+  printf("%-40s %10s %10s\n","BENCH","min","mediana");
+  double r[ROUNDS];
+  for(int q=0;q<ROUNDS;q++){ int N=100; double t0=now_ns();
+    for(int i=0;i<N;i++){ pid_t p=fork(); if(p==0){ char*a[]={"/bin/true",0}; execve(a[0],a,environ); _exit(127);} int st; waitpid(p,&st,0);}    
+    r[q]=(now_ns()-t0)/N/1000; } report("fork+exec /bin/true (base)",r,ROUNDS,100);
+  int fallo=0;
+  for(int q=0;q<ROUNDS;q++){ int N=100; double t0=now_ns();
+    for(int i=0;i<N;i++){ pid_t p=fork();
+      if(p==0){ if(unshare(CLONE_NEWUSER|CLONE_NEWNS|CLONE_NEWPID|CLONE_NEWIPC|CLONE_NEWUTS|CLONE_NEWNET)<0) _exit(9);
+                char*a[]={"/bin/true",0}; execve(a[0],a,environ); _exit(127);}
+      int st; waitpid(p,&st,0); if(WEXITSTATUS(st)==9) fallo=1; }
+    r[q]=(now_ns()-t0)/N/1000; }
+  if(fallo) printf("unshare completo: no permitido para este uid\n");
+  else report("fork+unshare(6 ns)+exec",r,ROUNDS,100);
+  for(int q=0;q<ROUNDS;q++){ int N=100; double t0=now_ns();
+    for(int i=0;i<N;i++){ pid_t p=fork();
+      if(p==0){ unshare(CLONE_NEWUSER|CLONE_NEWNS); char*a[]={"/bin/true",0}; execve(a[0],a,environ); _exit(127);}
+      int st; waitpid(p,&st,0); }
+    r[q]=(now_ns()-t0)/N/1000; } report("fork+unshare(USER|MNT)+exec",r,ROUNDS,100);
+}
+
+int main(int argc,char**argv){
+  const char *base = argc>1?argv[1]:"/tmp/bench-linux";
+  snprintf(root,sizeof root,"%s",base);
+  rm_tree(root); mk_tree(root);
+  metadata(root, base);
+  copia(root);
+  uring(root);
+  if(argc<=2){ memoria(); namespaces(); }
+  rm_tree(root);
+  return 0;
+}
diff --git a/docs/evidencia/tasas-kernel-2026-08-29/bench_pin.c b/docs/evidencia/tasas-kernel-2026-08-29/bench_pin.c
new file mode 100644
index 00000000..847be262
--- /dev/null
+++ b/docs/evidencia/tasas-kernel-2026-08-29/bench_pin.c
@@ -0,0 +1,53 @@
+/* bench_pin.c — mismas medidas, PINNEADAS a un cpu, para sacar la cola del scheduler
+ * de en medio y dejar ver el efecto puro. */
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <sched.h>
+#include <pthread.h>
+#include <spawn.h>
+#include <sys/wait.h>
+#include <sys/mman.h>
+extern char **environ;
+static inline double now_ns(void){ struct timespec t; clock_gettime(CLOCK_MONOTONIC,&t); return t.tv_sec*1e9+t.tv_nsec; }
+static int cmpd(const void*a,const void*b){ double x=*(const double*)a,y=*(const double*)b; return (x>y)-(x<y); }
+#define R 9
+static void rep(const char*n,double*r){ qsort(r,R,sizeof(double),cmpd); printf("%-46s %9.1f %9.1f\n",n,r[0],r[R/2]); fflush(stdout); }
+static void pin(int c){ cpu_set_t s; CPU_ZERO(&s); CPU_SET(c,&s); sched_setaffinity(0,sizeof s,&s); }
+static char *area; static long asz;
+static void grow(long mb){ if(area)munmap(area,asz); asz=mb*1024L*1024; if(!mb){area=0;return;}
+  area=mmap(0,asz,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0);
+  madvise(area,asz,MADV_NOHUGEPAGE); for(long o=0;o<asz;o+=4096) area[o]=1; }
+static void* nada(void*p){ (void)p; return 0; }
+int main(int argc,char**argv){
+  pin(2);
+  const char *sta=argc>1?argv[1]:"/bin/true", *dyn=argc>2?argv[2]:"/bin/true", *mus=argc>3?argv[3]:sta;
+  printf("%-46s %9s %9s\n","BENCH (us/op), PINNEADO a 1 cpu","min","mediana");
+  printf("--- fork vs espacio de direcciones del padre ---\n");
+  long mbs[]={0,64,256};
+  for(unsigned k=0;k<3;k++){ grow(mbs[k]); double a[R],b[R]; char n[90];
+    for(int q=0;q<R;q++){ double t0=now_ns(); for(int i=0;i<120;i++){pid_t p=fork(); if(!p)_exit(0); int st;waitpid(p,&st,0);} a[q]=(now_ns()-t0)/120/1000; }
+    for(int q=0;q<R;q++){ double t0=now_ns(); for(int i=0;i<120;i++){pid_t p;char*ar[]={(char*)sta,0}; if(!posix_spawn(&p,sta,0,0,ar,environ)){int st;waitpid(p,&st,0);}} b[q]=(now_ns()-t0)/120/1000; }
+    snprintf(n,sizeof n,"  padre %3ld MB: fork+_exit+wait",mbs[k]); rep(n,a);
+    snprintf(n,sizeof n,"  padre %3ld MB: posix_spawn+wait (estatico)",mbs[k]); rep(n,b); }
+  grow(0);
+  printf("--- tasa del enlazador dinamico (misma libc: glibc) ---\n");
+  { double r[R];
+    for(int q=0;q<R;q++){ double t0=now_ns(); for(int i=0;i<150;i++){pid_t p;char*a[]={(char*)sta,0};if(!posix_spawn(&p,sta,0,0,a,environ)){int st;waitpid(p,&st,0);}} r[q]=(now_ns()-t0)/150/1000; } rep("  exec glibc ESTATICO",r);
+    for(int q=0;q<R;q++){ double t0=now_ns(); for(int i=0;i<150;i++){pid_t p;char*a[]={(char*)dyn,0};if(!posix_spawn(&p,dyn,0,0,a,environ)){int st;waitpid(p,&st,0);}} r[q]=(now_ns()-t0)/150/1000; } rep("  exec glibc DINAMICO",r);
+    for(int q=0;q<R;q++){ double t0=now_ns(); for(int i=0;i<150;i++){pid_t p;char*a[]={(char*)mus,0};if(!posix_spawn(&p,mus,0,0,a,environ)){int st;waitpid(p,&st,0);}} r[q]=(now_ns()-t0)/150/1000; } rep("  exec musl ESTATICO",r); }
+  printf("--- hilo vs proceso ---\n");
+  { double r[R]; for(int q=0;q<R;q++){ double t0=now_ns(); for(int i=0;i<2000;i++){pthread_t t;pthread_create(&t,0,nada,0);pthread_join(t,0);} r[q]=(now_ns()-t0)/2000/1000; } rep("  pthread_create+join",r); }
+  printf("--- fallos de pagina, 64MB (defrag=madvise) ---\n");
+  { const long SZ=64L<<20; double r[R]; long pg=SZ/4096;
+    for(int q=0;q<R;q++){ char*m=mmap(0,SZ,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0); madvise(m,SZ,MADV_HUGEPAGE);
+      double t0=now_ns(); for(long o=0;o<SZ;o+=4096)m[o]=1; r[q]=(now_ns()-t0)/pg*1000; munmap(m,SZ);} rep("  MADV_HUGEPAGE   (ns/pagina 4K)",r);
+    for(int q=0;q<R;q++){ char*m=mmap(0,SZ,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0); madvise(m,SZ,MADV_NOHUGEPAGE);
+      double t0=now_ns(); for(long o=0;o<SZ;o+=4096)m[o]=1; r[q]=(now_ns()-t0)/pg*1000; munmap(m,SZ);} rep("  MADV_NOHUGEPAGE (ns/pagina 4K)",r);
+    for(int q=0;q<R;q++){ double t0=now_ns(); char*m=mmap(0,SZ,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS|MAP_POPULATE,-1,0);
+      for(long o=0;o<SZ;o+=4096)m[o]=1; r[q]=(now_ns()-t0)/pg*1000; munmap(m,SZ);} rep("  MAP_POPULATE    (ns/pagina 4K)",r); }
+  return 0;
+}
diff --git a/docs/evidencia/tasas-kernel-2026-08-29/bench_proc.c b/docs/evidencia/tasas-kernel-2026-08-29/bench_proc.c
new file mode 100644
index 00000000..bf6098de
--- /dev/null
+++ b/docs/evidencia/tasas-kernel-2026-08-29/bench_proc.c
@@ -0,0 +1,147 @@
+/* bench_proc.c — piso de syscall, control de procesos, /proc, IPC, scheduler.
+ * Método: R rondas; se reporta MIN (mejor caso, robusto al ruido) y MEDIANA. */
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <errno.h>
+#include <signal.h>
+#include <sched.h>
+#include <pthread.h>
+#include <spawn.h>
+#include <dirent.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <sys/resource.h>
+#include <sys/eventfd.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+extern char **environ;
+static volatile long sink;
+
+static inline double now_ns(void){ struct timespec t; clock_gettime(CLOCK_MONOTONIC,&t); return t.tv_sec*1e9+t.tv_nsec; }
+static int cmpd(const void*a,const void*b){ double x=*(const double*)a,y=*(const double*)b; return (x>y)-(x<y); }
+
+#define ROUNDS 9
+static void report(const char*name,double*r,int n,long it){
+  qsort(r,n,sizeof(double),cmpd);
+  printf("%-38s %10.1f %10.1f   (n=%ld)\n",name,r[0],r[n/2],it); fflush(stdout);
+}
+#define BENCH(name,iters,...) do{ double _r[ROUNDS]; long _N=(iters); \
+  for(int _q=0;_q<ROUNDS;_q++){ double _t0=now_ns(); for(long i=0;i<_N;i++){ __VA_ARGS__; } \
+    _r[_q]=(now_ns()-_t0)/(double)_N; } report(name,_r,ROUNDS,_N);}while(0)
+
+/* ---------- helpers ---------- */
+static void pin(int cpu){ cpu_set_t s; CPU_ZERO(&s); CPU_SET(cpu,&s); sched_setaffinity(0,sizeof s,&s); }
+
+static long read_stat_utime(int fd){
+  char buf[1024]; ssize_t n=pread(fd,buf,sizeof buf-1,0); if(n<=0) return -1; buf[n]=0;
+  char *p=strrchr(buf,')'); if(!p) return -1; p+=2;
+  int f=1; long v=0; while(*p&&f<12){ if(*p==' ')f++; p++; }
+  v=strtol(p,NULL,10); return v;
+}
+static long parse_vmrss(void){
+  int fd=open("/proc/self/status",O_RDONLY); if(fd<0) return -1;
+  char buf[4096]; ssize_t n=read(fd,buf,sizeof buf-1); close(fd); if(n<=0)return -1; buf[n]=0;
+  char*p=strstr(buf,"VmRSS:"); return p?strtol(p+6,NULL,10):-1;
+}
+
+static volatile sig_atomic_t got;
+static void h(int s){ (void)s; got=1; }
+
+/* RTT entre dos hilos pinneados al MISMO cpu */
+struct rtt { int a[2], b[2]; int kind; long iters; };
+static void rd1(int fd,int kind){ if(kind==2){uint64_t v;(void)!read(fd,&v,8);} else {char c;(void)!read(fd,&c,1);} }
+static void wr1(int fd,int kind){ if(kind==2){uint64_t v=1;(void)!write(fd,&v,8);} else {char c=1;(void)!write(fd,&c,1);} }
+static void*echo(void*p){ struct rtt*r=p; pin(3);
+  for(long i=0;i<r->iters;i++){ rd1(r->a[0],r->kind); wr1(r->b[1],r->kind); } return 0; }
+
+static double rtt_bench(int kind,long iters){
+  struct rtt r; r.kind=kind; r.iters=iters;
+  if(kind==0){ if(pipe(r.a)||pipe(r.b)) return -1; }
+  else if(kind==1){ if(socketpair(AF_UNIX,SOCK_STREAM,0,r.a)||socketpair(AF_UNIX,SOCK_STREAM,0,r.b)) return -1; }
+  else { r.a[0]=r.a[1]=eventfd(0,0); r.b[0]=r.b[1]=eventfd(0,0); }
+  pthread_t t; pthread_create(&t,0,echo,&r); pin(3);
+  double best=1e18;
+  for(int q=0;q<3;q++){ long n=iters/3; double t0=now_ns();
+    for(long i=0;i<n;i++){ wr1(r.a[1],kind); rd1(r.b[0],kind); }
+    double v=(now_ns()-t0)/n; if(v<best)best=v; }
+  pthread_join(t,0);
+  return best;
+}
+
+int main(void){
+  pin(3);
+  printf("%-38s %10s %10s\n","BENCH (ns/op)","min","mediana");
+  printf("--- A. piso de syscall ---\n");
+  BENCH("getppid (syscall nulo)",              300000, sink=syscall(SYS_getppid));
+  BENCH("clock_gettime MONOTONIC (vDSO)",      300000, {struct timespec t;clock_gettime(CLOCK_MONOTONIC,&t);sink=t.tv_nsec;});
+  BENCH("clock_gettime MONOTONIC (syscall)",   200000, {struct timespec t;syscall(SYS_clock_gettime,CLOCK_MONOTONIC,&t);sink=t.tv_nsec;});
+  BENCH("getpid (vDSO no; glibc syscall)",     300000, sink=getpid());
+  BENCH("sched_yield",                         200000, sched_yield());
+
+  printf("--- B. contabilidad de proceso propio ---\n");
+  BENCH("getrusage(RUSAGE_SELF)",              200000, {struct rusage u;getrusage(RUSAGE_SELF,&u);sink=u.ru_utime.tv_usec;});
+  BENCH("clock_gettime(PROCESS_CPUTIME)",      200000, {struct timespec t;clock_gettime(CLOCK_PROCESS_CPUTIME_ID,&t);sink=t.tv_nsec;});
+  { int fd=open("/proc/self/stat",O_RDONLY);
+    BENCH("pread+parse /proc/self/stat (fd abierto)", 100000, sink=read_stat_utime(fd));
+    close(fd); }
+  BENCH("open+read+parse /proc/self/stat",      50000, {int fd=open("/proc/self/stat",O_RDONLY);sink=read_stat_utime(fd);close(fd);});
+  BENCH("open+read+parse /proc/self/status(RSS)",30000, sink=parse_vmrss());
+
+  printf("--- C. liveness de otro proceso ---\n");
+  { pid_t me=getpid(); char p[64]; snprintf(p,sizeof p,"/proc/%d",me);
+    int pfd=syscall(SYS_pidfd_open,me,0);
+    BENCH("access(/proc/<pid>) [lo que se usa hoy]",200000, sink=access(p,F_OK));
+    BENCH("kill(pid,0)",                            300000, sink=kill(me,0));
+    if(pfd>=0){ BENCH("pidfd_send_signal(fd,0) [sin carrera]",300000, sink=syscall(SYS_pidfd_send_signal,pfd,0,NULL,0)); }
+    else printf("pidfd_send_signal: no disponible (%s)\n",strerror(errno));
+    BENCH("pidfd_open+close",                      100000, {int f=syscall(SYS_pidfd_open,me,0); if(f>=0)close(f);});
+    if(pfd>=0) close(pfd); }
+
+  printf("--- D. barrido de /proc (enumeracion) ---\n");
+  { int nproc=0;
+    double r1[ROUNDS],r2[ROUNDS];
+    for(int q=0;q<ROUNDS;q++){
+      double t0=now_ns(); int c=0;
+      DIR*d=opendir("/proc"); struct dirent*e;
+      while((e=readdir(d))){ if(e->d_name[0]<'0'||e->d_name[0]>'9')continue; c++; }
+      closedir(d); r1[q]=now_ns()-t0; nproc=c;
+      t0=now_ns(); d=opendir("/proc");
+      while((e=readdir(d))){ if(e->d_name[0]<'0'||e->d_name[0]>'9')continue;
+        char p[512]; snprintf(p,sizeof p,"/proc/%s/stat",e->d_name);
+        int fd=open(p,O_RDONLY); if(fd<0)continue; sink=read_stat_utime(fd); close(fd); }
+      closedir(d); r2[q]=now_ns()-t0; }
+    qsort(r1,ROUNDS,sizeof(double),cmpd); qsort(r2,ROUNDS,sizeof(double),cmpd);
+    printf("procesos vivos = %d\n",nproc);
+    printf("%-38s %10.1f %10.1f   (us/barrido)\n","solo readdir(/proc)",r1[0]/1000,r1[ROUNDS/2]/1000);
+    printf("%-38s %10.1f %10.1f   (us/barrido)\n","readdir + stat de cada pid",r2[0]/1000,r2[ROUNDS/2]/1000);
+    printf("%-38s %10.1f %10.1f   (ns/proceso)\n","  => coste por proceso",r2[0]/nproc,r2[ROUNDS/2]/nproc); }
+
+  printf("--- E. creacion de procesos ---\n");
+  BENCH("fork + _exit + waitpid",                  400, {pid_t p=fork(); if(p==0)_exit(0); int st;waitpid(p,&st,0);});
+  BENCH("vfork + execve(/bin/true) + wait",         300, {pid_t p=vfork(); if(p==0){char*a[]={"/bin/true",0};execve(a[0],a,environ);_exit(127);} int st;waitpid(p,&st,0);});
+  { BENCH("posix_spawn(/bin/true) + wait",          300, {pid_t p;char*a[]={"/bin/true",0};if(posix_spawn(&p,a[0],0,0,a,environ)==0){int st;waitpid(p,&st,0);} }); }
+
+  printf("--- F. IPC: round-trip entre 2 hilos (mismo cpu) ---\n");
+  { pthread_mutex_t m=PTHREAD_MUTEX_INITIALIZER;
+    BENCH("pthread_mutex lock+unlock (sin disputa)",1000000,{pthread_mutex_lock(&m);pthread_mutex_unlock(&m);}); }
+  printf("%-38s %10.1f            (RTT)\n","pipe read/write",      rtt_bench(0,60000));
+  printf("%-38s %10.1f            (RTT)\n","socketpair AF_UNIX",   rtt_bench(1,60000));
+  printf("%-38s %10.1f            (RTT)\n","eventfd",              rtt_bench(2,60000));
+  { signal(SIGUSR1,h);
+    BENCH("kill(self,SIGUSR1) + handler",         200000, {got=0;kill(getpid(),SIGUSR1);sink=got;}); }
+
+  printf("--- G. temporizadores ---\n");
+  { double r[ROUNDS];
+    for(int q=0;q<ROUNDS;q++){ struct timespec req={0,1000000},rem; double t0=now_ns();
+      for(int i=0;i<20;i++) nanosleep(&req,&rem);
+      r[q]=(now_ns()-t0)/20 - 1000000.0; }
+    qsort(r,ROUNDS,sizeof(double),cmpd);
+    printf("%-38s %10.1f %10.1f   (sobrecoste ns sobre 1ms)\n","nanosleep(1ms) overshoot",r[0],r[ROUNDS/2]); }
+  return 0;
+}
diff --git a/docs/evidencia/tasas-kernel-2026-08-29/extra.txt b/docs/evidencia/tasas-kernel-2026-08-29/extra.txt
new file mode 100644
index 00000000..0207ad18
--- /dev/null
+++ b/docs/evidencia/tasas-kernel-2026-08-29/extra.txt
@@ -0,0 +1,23 @@
+BENCH (us/op)                                       min    mediana
+--- a) fork segun el espacio de direcciones del padre ---
+  padre   0 MB tocados: fork+_exit                992.4     1430.4
+  padre   0 MB tocados: fork+exec                 806.8     1985.0
+  padre   0 MB tocados: posix_spawn(CLONE_VFORK)       77.9       81.0
+  padre  16 MB tocados: fork+_exit               1804.9     2090.5
+  padre  16 MB tocados: fork+exec                1381.1     2181.3
+  padre  16 MB tocados: posix_spawn(CLONE_VFORK)      383.8      887.2
+  padre  64 MB tocados: fork+_exit               2324.1     5504.8
+  padre  64 MB tocados: fork+exec                1392.4     4248.0
+  padre  64 MB tocados: posix_spawn(CLONE_VFORK)       87.3       91.1
+  padre 256 MB tocados: fork+_exit               4021.7     5060.5
+  padre 256 MB tocados: fork+exec                4324.5     4523.2
+  padre 256 MB tocados: posix_spawn(CLONE_VFORK)       80.7       89.2
+--- c) tasa del enlazado dinamico en exec ---
+  vfork+exec binario ESTATICO                      64.1       65.7
+  vfork+exec binario DINAMICO                     308.5      334.2
+--- d) cierre de descriptores antes del exec ---
+  dup+close x512, uno a uno (us/fd)                 0.2        0.2
+  close_range(lo,hi) (us/fd)                        0.0        0.0
+--- b) contabilidad: cgroup v2 vs sumar /proc ---
+  cgroup: /sys/fs/cgroup/openrc.shuma-daemon
+  leer cpu.stat+memory.current+pids.current         4.6        4.7
diff --git a/docs/evidencia/tasas-kernel-2026-08-29/io-ext4.txt b/docs/evidencia/tasas-kernel-2026-08-29/io-ext4.txt
new file mode 100644
index 00000000..01b65ff3
--- /dev/null
+++ b/docs/evidencia/tasas-kernel-2026-08-29/io-ext4.txt
@@ -0,0 +1,40 @@
+
+### metadatos sobre /mnt/vvv/hammer/work/.bench-linux (4000 ficheros en un directorio)
+BENCH (ns/fichero)                              min    mediana
+stat() por ruta completa                      647.9      814.4   (n=4000)
+fstatat(dirfd, nombre)                        439.0      444.9   (n=4000)
+statx(dirfd,nombre,DONT_SYNC)                 467.2      476.0   (n=4000)
+openat(dirfd,nombre)+close                    898.8      994.1   (n=4000)
+openat2 RESOLVE_BENEATH+NO_SYMLINKS          1025.2     1449.8   (n=4000)
+getdents64 (barrido, sin stat)                168.2      172.8   (n=4002)
+open ruta corta (2 componentes bajo base)      759.1      908.2   (n=20000)
+open ruta 13 componentes mas profunda         964.6     1002.5   (n=20000)
+
+### copiar 64 MB (fichero en cache de pagina)
+BENCH (MB/s)                                    max    mediana
+read()+write() 64K                           2837.0     2722.6
+sendfile()                                   3345.1     2958.8
+copy_file_range()                            3520.7     2859.6
+
+### materializar 2000 ficheros de 1 byte (el problema del store)
+link() [hardlink]                            9223.4    10895.0   (n=2000)
+open+read+write+close [copia]              142151.6   154711.4   (n=2000)
+
+### 4K aleatorios sobre 64MB en cache: syscall vs io_uring
+BENCH (ns/lectura)                              min    mediana
+pread() 4K                                    539.9      674.2   (n=50000)
+io_uring read 4K (profundidad 32)             593.6      654.2   (n=50000)
+io_uring NOP (piso de operacion)               47.7       50.5   (n=200000)
+
+### fallos de pagina: tocar 128MB anonimos (ns por pagina de 4K)
+BENCH                                           min    mediana
+MADV_HUGEPAGE (THP)                          2730.4     3205.5   (n=32768)
+MADV_NOHUGEPAGE (4K de a uno)                1055.7     1073.5   (n=32768)
+MAP_POPULATE (prefault)                       796.4      846.9   (n=32768)
+mmap+munmap 4K (churn)                       1663.9     1784.1   (n=100000)
+
+### coste del sandbox: crear namespaces (us por proceso)
+BENCH                                           min    mediana
+fork+exec /bin/true (base)                   1648.6     2092.8   (n=100)
+fork+unshare(6 ns)+exec                      2742.0     2922.3   (n=100)
+fork+unshare(USER|MNT)+exec                  2050.4     2512.9   (n=100)
diff --git a/docs/evidencia/tasas-kernel-2026-08-29/io-tmpfs.txt b/docs/evidencia/tasas-kernel-2026-08-29/io-tmpfs.txt
new file mode 100644
index 00000000..3a6742fd
--- /dev/null
+++ b/docs/evidencia/tasas-kernel-2026-08-29/io-tmpfs.txt
@@ -0,0 +1,27 @@
+
+### metadatos sobre /tmp/bench-linux-tmpfs (4000 ficheros en un directorio)
+BENCH (ns/fichero)                              min    mediana
+stat() por ruta completa                      713.6     1196.5   (n=4000)
+fstatat(dirfd, nombre)                        489.1      761.1   (n=4000)
+statx(dirfd,nombre,DONT_SYNC)                 593.2     1059.3   (n=4000)
+openat(dirfd,nombre)+close                    960.8     1272.4   (n=4000)
+openat2 RESOLVE_BENEATH+NO_SYMLINKS           762.8      996.5   (n=4000)
+getdents64 (barrido, sin stat)                 59.4       63.8   (n=4002)
+open ruta corta (2 componentes bajo base)      657.8      672.4   (n=20000)
+open ruta 13 componentes mas profunda         860.3      902.3   (n=20000)
+
+### copiar 64 MB (fichero en cache de pagina)
+BENCH (MB/s)                                    max    mediana
+read()+write() 64K                           3084.8     2970.9
+sendfile()                                   3325.8     3115.7
+copy_file_range()                            3221.2     3077.9
+
+### materializar 2000 ficheros de 1 byte (el problema del store)
+link() [hardlink]                            2851.8     3274.8   (n=2000)
+open+read+write+close [copia]                4964.9     5339.4   (n=2000)
+
+### 4K aleatorios sobre 64MB en cache: syscall vs io_uring
+BENCH (ns/lectura)                              min    mediana
+pread() 4K                                    601.9      611.9   (n=50000)
+io_uring read 4K (profundidad 32)            1672.3     2132.6   (n=50000)
+io_uring NOP (piso de operacion)               50.3       54.4   (n=200000)
diff --git a/docs/evidencia/tasas-kernel-2026-08-29/pin.txt b/docs/evidencia/tasas-kernel-2026-08-29/pin.txt
new file mode 100644
index 00000000..2a3c06bd
--- /dev/null
+++ b/docs/evidencia/tasas-kernel-2026-08-29/pin.txt
@@ -0,0 +1,18 @@
+BENCH (us/op), PINNEADO a 1 cpu                      min   mediana
+--- fork vs espacio de direcciones del padre ---
+  padre   0 MB: fork+_exit+wait                    109.1     201.9
+  padre   0 MB: posix_spawn+wait (estatico)        230.8    1029.8
+  padre  64 MB: fork+_exit+wait                    937.8    1016.7
+  padre  64 MB: posix_spawn+wait (estatico)        234.0     240.4
+  padre 256 MB: fork+_exit+wait                   3949.4    4383.5
+  padre 256 MB: posix_spawn+wait (estatico)        230.8     253.6
+--- tasa del enlazador dinamico (misma libc: glibc) ---
+  exec glibc ESTATICO                              232.7     245.6
+  exec glibc DINAMICO                              324.3     335.6
+  exec musl ESTATICO                                77.4      86.4
+--- hilo vs proceso ---
+  pthread_create+join                               11.9      12.6
+--- fallos de pagina, 64MB (defrag=madvise) ---
+  MADV_HUGEPAGE   (ns/pagina 4K)               3046278.5 5007046.6
+  MADV_NOHUGEPAGE (ns/pagina 4K)               1013225.8 1088726.2
+  MAP_POPULATE    (ns/pagina 4K)                752505.7  802625.3
diff --git a/docs/evidencia/tasas-kernel-2026-08-29/proc.txt b/docs/evidencia/tasas-kernel-2026-08-29/proc.txt
new file mode 100644
index 00000000..ea203614
--- /dev/null
+++ b/docs/evidencia/tasas-kernel-2026-08-29/proc.txt
@@ -0,0 +1,35 @@
+BENCH (ns/op)                                 min    mediana
+--- A. piso de syscall ---
+getppid (syscall nulo)                       78.7       97.2   (n=300000)
+clock_gettime MONOTONIC (vDSO)               31.2       31.7   (n=300000)
+clock_gettime MONOTONIC (syscall)           143.8      147.0   (n=200000)
+getpid (vDSO no; glibc syscall)              71.1       74.3   (n=300000)
+sched_yield                                 174.2      180.9   (n=200000)
+--- B. contabilidad de proceso propio ---
+getrusage(RUSAGE_SELF)                      238.1      244.6   (n=200000)
+clock_gettime(PROCESS_CPUTIME)              214.0      245.8   (n=200000)
+pread+parse /proc/self/stat (fd abierto)     1271.4     1334.6   (n=100000)
+open+read+parse /proc/self/stat            2691.7     2801.0   (n=50000)
+open+read+parse /proc/self/status(RSS)     4642.6     4857.3   (n=30000)
+--- C. liveness de otro proceso ---
+access(/proc/<pid>) [lo que se usa hoy]      344.9      352.4   (n=200000)
+kill(pid,0)                                 119.0      126.0   (n=300000)
+pidfd_send_signal(fd,0) [sin carrera]       119.0      129.1   (n=300000)
+pidfd_open+close                            413.7      423.4   (n=100000)
+--- D. barrido de /proc (enumeracion) ---
+procesos vivos = 291
+solo readdir(/proc)                         106.0      121.2   (us/barrido)
+readdir + stat de cada pid                  990.7     1061.7   (us/barrido)
+  => coste por proceso                     3404.3     3648.5   (ns/proceso)
+--- E. creacion de procesos ---
+fork + _exit + waitpid                    83874.0    87347.9   (n=400)
+vfork + execve(/bin/true) + wait         324621.2   353117.9   (n=300)
+posix_spawn(/bin/true) + wait            323507.4   335470.5   (n=300)
+--- F. IPC: round-trip entre 2 hilos (mismo cpu) ---
+pthread_mutex lock+unlock (sin disputa)        8.6        8.7   (n=1000000)
+pipe read/write                            2955.8            (RTT)
+socketpair AF_UNIX                         3638.5            (RTT)
+eventfd                                    2507.1            (RTT)
+kill(self,SIGUSR1) + handler               1165.1     1188.5   (n=200000)
+--- G. temporizadores ---
+nanosleep(1ms) overshoot                  65179.4    70381.1   (sobrecoste ns sobre 1ms)