Cuando lees /proc/cpuinfo o escribes en /proc/sys/vm/swappiness, no estás tocando ningún archivo en disco. No hay inodos apuntando a bloques en un SSD, no hay caché de página involucrada de la manera habitual. Lo que tienes delante es un filesystem virtual —procfs y sysfs— montado en memoria, cuyo contenido el kernel genera en el momento exacto en que lo solicitas. El VFS (Virtual Filesystem Switch) intercepta la llamada read(), la redirige a un handler interno del kernel, y ese handler serializa el estado actual de sus estructuras de datos en el buffer que recibe tu proceso. Es lectura directa del estado del kernel, con una capa de abstracción POSIX encima para que puedas usar cat, echo, o cualquier herramienta estándar.
La división entre /proc y /sys no es arbitraria. /proc surgió en Unix como interfaz a los procesos (de ahí el nombre), y Linux la extendió progresivamente hasta convertirla en un volcadero de información del sistema —algo que los propios desarrolladores del kernel admiten que quedó desordenado. /sys (sysfs) se introdujo en el kernel 2.6 con un diseño más deliberado: expone el device model del kernel, una jerarquía que refleja cómo el kernel organiza buses, drivers y dispositivos. Si necesitas información sobre hardware, /sys es el sitio correcto. Si necesitas estado de procesos o parámetros de kernel ajustables, /proc.
Cuándo necesitas esto directamente: cuando las herramientas de alto nivel (top, ss, lsblk) no te dan el detalle suficiente, cuando estás escribiendo un monitor o un agente de telemetría, cuando depuras un proceso que se comporta mal, o cuando necesitas ajustar parámetros del kernel en un sistema bajo carga sin reiniciar. Lo que rompes si lo haces mal: sysctl -w escribe inmediatamente en el kernel en producción, sin confirmación ni rollback automático. Un valor incorrecto en net.ipv4.tcp_rmem o vm.overcommit_memory puede degradar el rendimiento de toda la máquina o provocar OOM kills en cascada.
# ── Explorar /proc: información global del sistema ────────────────────
# Número de cores (físicos y lógicos) y su microarquitectura
grep -E "^(processor|model name|cpu cores|siblings)" /proc/cpuinfo | head -20
# Estado de memoria: MemAvailable es lo que realmente importa para saber
# si el sistema está bajo presión (MemFree no cuenta caches recuperables)
grep -E "^(MemTotal|MemAvailable|SwapTotal|SwapFree|Dirty|Writeback):" /proc/meminfo
# Load average: tres números (1, 5, 15 min), running/total threads, último PID
cat /proc/loadavg
# ── Inspección de un proceso específico ──────────────────────────────
# Tomamos el PID de sshd como ejemplo concreto
SSHD_PID=$(pidof sshd | awk '{print $NF}')
# Línea de comando completa, con argumentos (el kernel separa args con \0)
# tr convierte los null bytes en espacios para hacerlo legible
tr '\0' ' ' < /proc/${SSHD_PID}/cmdline; echo
# File descriptors abiertos: el número es el fd, el destino del symlink
# revela si es un archivo, socket, pipe, o dispositivo
ls -la /proc/${SSHD_PID}/fd
# Un socket TCP abierto aparece como "socket:[12345]" — ese número es el
# inode del socket, que puedes cruzar con ss -tlnp para identificarlo
ls -la /proc/${SSHD_PID}/fd | grep socket
# Estado resumido del proceso: VmRSS (RAM física usada), Threads,
# voluntary_ctxt_switches (señal de I/O bound vs CPU bound)
grep -E "^(Name|State|Pid|VmRSS|VmSwap|Threads|voluntary)" \
/proc/${SSHD_PID}/status
# Mapa de memoria: qué regiones virtuales tiene mapeadas, permisos,
# y de dónde vienen (biblioteca, heap anónimo, stack…)
# Filtrar las líneas con nombre de archivo para ver dependencias reales
awk 'NF==6' /proc/${SSHD_PID}/maps | sort -u -k6
# ── /sys: hardware y dispositivos ────────────────────────────────────
# Interfaces de red: estado operacional y tipo
for iface in /sys/class/net/*/; do
name=$(basename "$iface")
state=$(cat "${iface}operstate" 2>/dev/null)
type=$(cat "${iface}type" 2>/dev/null) # 1=Ethernet, 772=loopback
printf "%-12s state=%-6s type=%s\n" "$name" "$state" "$type"
done
# Discos: tamaño en sectores de 512 bytes
for disk in /sys/block/sd* /sys/block/vd* /sys/block/nvme* 2>/dev/null; do
[ -d "$disk" ] || continue
name=$(basename "$disk")
sectors=$(cat "${disk}/size" 2>/dev/null)
printf "%s: %d GiB\n" "$name" $(( sectors * 512 / 1024 / 1024 / 1024 ))
done
# Scheduler de I/O activo para el primer disco encontrado
FIRST_DISK=$(ls /sys/block/ | grep -m1 -E "^(sd|vd|nvme)")
cat /sys/block/${FIRST_DISK}/queue/scheduler
# ── sysctl: leer y modificar parámetros del kernel ───────────────────
# Ver todos los parámetros actuales (útil para auditar un sistema nuevo)
sysctl -a 2>/dev/null | grep -E "^(vm\.|net\.ipv4\.(ip_forward|tcp_syncookies))"
# Leer un parámetro concreto
sysctl vm.swappiness
sysctl fs.file-max
# Cambio en caliente — tiene efecto inmediato, sin reinicio
# En un servidor de base de datos con suficiente RAM, swappiness bajo
# reduce la probabilidad de que el kernel mueva páginas activas a swap
sysctl -w vm.swappiness=10
# Habilitar routing IPv4 (necesario en gateways y contenedores con NAT)
sysctl -w net.ipv4.ip_forward=1
# Verificar que el kernel lo aceptó (la escritura puede fallar silenciosa
# si el parámetro es de solo lectura en este kernel/configuración)
sysctl net.ipv4.ip_forward
# ── Persistencia: que los cambios sobrevivan al reboot ────────────────
# Nunca edites /etc/sysctl.conf directamente para cambios de aplicación
# o tuning específico — usa un archivo propio en sysctl.d para no
# mezclar con la configuración base de Debian
cat > /etc/sysctl.d/99-tuning.conf << 'EOF'
# Reducir agresividad del swap — ajustar según RAM disponible
vm.swappiness = 10
# Habilitar routing entre interfaces (necesario para LXC/Docker NAT)
net.ipv4.ip_forward = 1
# Aumentar el límite global de file descriptors (por defecto ~800k en Bookworm)
# Para servidores con muchas conexiones simultáneas
fs.file-max = 2097152
# Protección contra SYN flood — activo por defecto en Debian, pero
# conviene hacerlo explícito en /etc/sysctl.d para auditoría
net.ipv4.tcp_syncookies = 1
EOF
# Aplicar todos los archivos de sysctl.d sin reiniciar
sysctl --system
# Confirmar que el valor activo en el kernel coincide con el archivo
sysctl vm.swappiness fs.file-max net.ipv4.ip_forward
Lo que está pasando detrás de cada decisión
El awk 'NF==6' sobre /proc/${SSHD_PID}/maps merece explicación. El archivo maps tiene líneas con 5 o 6 campos: las de 5 son regiones anónimas (heap, stack, zonas mmap sin archivo backing), las de 6 tienen el path del archivo o biblioteca. Al filtrar NF==6 ves exactamente qué binarios y .so tiene cargados el proceso, sin ruido de memoria anónima —útil para detectar inyecciones de librerías o confirmar qué versión de libssl usa realmente un proceso.
El cruce entre ls /proc/${SSHD_PID}/fd y ss -tlnp funciona porque ambos usan el mismo espacio de inodos de socket. Cuando ves socket:[4194305] en /proc/fd/, ese número aparece también en la columna de ss —así puedes mapear un fd concreto a un puerto local sin necesidad de lsof.
En el bucle de /sys/class/net/, leer type en lugar de confiar solo en el nombre de la interfaz es más robusto: una interfaz puede llamarse eth0 y ser un túnel, o llamarse docker0 y ser un bridge. El valor 1 en type corresponde a ARPHRD_ETHER del kernel; 772 es ARPHRD_LOOPBACK. Si estás construyendo un script de inventario, este valor es más fiable que parsear nombres.
El uso de /etc/sysctl.d/99-tuning.conf en lugar de /etc/sysctl.conf responde a cómo sysctl --system procesa los archivos: lee en orden lexicográfico todos los archivos de /usr/lib/sysctl.d/, /run/sysctl.d/, y /etc/sysctl.d/, y finalmente /etc/sysctl.conf. El prefijo 99- garantiza que tus valores sobreescriben los de Debian (que típicamente están en 10- o similares) sin tocar archivos del sistema. Si luego haces apt upgrade y Debian actualiza sus defaults, no hay conflicto de merge.
El aviso sobre sysctl -w fallando silenciosamente no es hipotético: parámetros como kernel.kptr_restrict son de solo lectura en kernels con CONFIG_SECURITY_LOCKDOWN, y el comando devuelve exit code 0 igualmente en algunas versiones. La verificación con sysctl <param> después de escribir es el único modo de confirmarlo.
N° 92