1. Qué es el kernel
En el tema 10 ubicamos el kernel en el mapa de componentes. Ahora podemos definirlo con precisión:
El kernel es el programa que se ejecuta en el modo privilegiado de la CPU, permanece en memoria desde el arranque hasta el apagado y tiene el control exclusivo del hardware. Todos los demás programas acceden a los recursos de la máquina a través de él.
Tres rasgos lo distinguen de cualquier otro software:
- Es privilegiado. Puede ejecutar todas las instrucciones del procesador y acceder a toda la memoria y a todos los dispositivos. Los programas comunes no. Esa diferencia la impone la CPU, y la estudiamos en el tema 12.
- Es permanente. Se carga al arrancar y no termina nunca. Los programas van y vienen; el kernel está siempre.
- Es el intermediario obligado. No hay forma de leer un archivo, enviar un paquete o crear un proceso sin pasar por él.
La palabra «kernel» significa núcleo, o semilla: la parte central alrededor de la cual se construye el resto. Es, además, software como cualquier otro. En Linux está escrito casi íntegramente en C, se compila con gcc y contiene funciones, estructuras y bucles como los que escribes tú. Lo que lo hace especial no es su código, sino la posición desde la que se ejecuta.
2. Un programa que solo responde
La primera intuición sobre el kernel suele ser errónea. Se lo imagina como un programa que corre en paralelo a los demás, vigilándolos. No es así, y ya lo adelantamos en el tema 1: en un procesador se ejecuta una sola cosa a la vez, y mientras un programa de usuario tiene la CPU, el kernel no ejecuta ni una instrucción.
El kernel no es un proceso. No tiene una función main con un bucle que gire sin parar. Después de arrancar, es un conjunto de rutinas que esperan a ser invocadas, y solo hay tres acontecimientos que pueden invocarlas:
| Entrada | Quién la provoca | Ejemplo | Tema |
|---|---|---|---|
| Llamada al sistema | El programa en ejecución, a propósito. | write, open, getpid | 13 |
| Interrupción | Un dispositivo, en cualquier momento. | Llegó un paquete de red; venció el temporizador. | 17 |
| Excepción | El programa en ejecución, sin quererlo. | División por cero; acceso a memoria inválida. | 18 |
En los tres casos ocurre lo mismo: la CPU deja lo que hacía, pasa a modo privilegiado y salta a una rutina del kernel. El kernel atiende el asunto y devuelve la CPU, al mismo programa o a otro. Por eso conviene pensar en él menos como un programa y más como una biblioteca con privilegios combinada con un conjunto de manejadores de eventos.
Si el kernel solo se ejecuta cuando algo lo invoca, la multitarea parece imposible: un programa en un bucle infinito, que no hace llamadas al sistema ni provoca excepciones, nunca lo activaría. La pieza que falta es el temporizador, un dispositivo que interrumpe a la CPU a intervalos regulares precisamente para garantizar que el kernel recupere el control. Lo vemos en el tema 19.
3. Medir el tiempo dentro del kernel
Cuando un programa hace una llamada al sistema, el kernel trabaja en nombre de ese programa, y lleva la cuenta del tiempo que emplea. El comando time lo muestra separado en tres cifras:
real: el tiempo transcurrido en el reloj, de principio a fin.user: el tiempo de CPU ejecutando las instrucciones del propio programa, en modo usuario.sys: el tiempo de CPU que el kernel dedicó a atender sus pedidos, en modo kernel.
El siguiente programa tiene dos modos de trabajo. Con el argumento calcular hace mil millones de sumas, sin pedir nada a nadie. Con pedir hace cinco millones de llamadas al sistema, escribiendo un byte cada vez en /dev/null, el archivo especial que descarta todo lo que recibe.
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
if (argc == 2 && strcmp(argv[1], "calcular") == 0) {
volatile unsigned long suma = 0;
for (unsigned long i = 0; i < 1000000000UL; i++)
suma += i; /* solo instrucciones propias */
} else if (argc == 2 && strcmp(argv[1], "pedir") == 0) {
char byte = 'x';
int fd = open("/dev/null", O_WRONLY);
if (fd == -1) {
perror("open");
return 1;
}
for (long i = 0; i < 5000000; i++)
if (write(fd, &byte, 1) != 1) /* una llamada al sistema por vuelta */
break;
close(fd);
} else {
fprintf(stderr, "uso: %s calcular|pedir\n", argv[0]);
return 1;
}
return 0;
}
$ gcc -Wall -Wextra -g tiempos.c -o tiempos
$ time ./tiempos calcular
real 0m2.104s
user 0m2.101s
sys 0m0.002s
$ time ./tiempos pedir
real 0m1.612s
user 0m0.358s
sys 0m1.251s
Los valores exactos dependen del equipo, pero el patrón es siempre el mismo:
- En el primer caso el kernel casi no interviene. Los dos segundos son tiempo de usuario. El programa tuvo la CPU para sí, y el kernel estuvo detenido salvo por las breves interrupciones del temporizador.
- En el segundo, la mayor parte del tiempo se gastó dentro del kernel. El programa hace muy poco por su cuenta; casi todo es el kernel atendiendo llamadas.
Hay además una cuenta interesante: cinco millones de escrituras en 1,6 segundos son unos 320 nanosegundos por llamada, y eso que escribir en /dev/null no hace nada. Ese es, aproximadamente, el costo de cruzar la frontera hacia el kernel y volver: el equivalente a cientos de sumas. Explica por qué en el tema 7 recomendamos leer y escribir en bloques grandes.
4. Sus responsabilidades
El kernel se organiza en subsistemas, uno por cada tipo de recurso que administra. Para cada uno indicamos qué abstracción ofrece a los programas, con qué llamadas al sistema se usa y dónde puede observarse:
| Subsistema | Qué administra | Abstracción | Llamadas típicas | Dónde se observa |
|---|---|---|---|---|
| Procesos | La CPU | Proceso, hilo | fork, execve, exit, wait | /proc/<pid>, ps |
| Memoria | La RAM y el intercambio | Espacio de direcciones | mmap, brk | /proc/meminfo, free |
| Archivos | Los dispositivos de almacenamiento | Archivo, directorio | open, read, write, close | /proc/mounts, df |
| Red | Las interfaces de red | Socket | socket, connect, send | /proc/net, ss |
| Dispositivos | Todos los controladores de hardware | Archivo de dispositivo | ioctl, read, write | /sys, /dev |
A esas cinco áreas se suman tres tareas que las atraviesan a todas:
- Planificación: decidir, entre los procesos listos, cuál usa cada procesador y durante cuánto tiempo.
- Protección: comprobar en cada pedido que quien lo hace tiene permiso.
- Comunicación entre procesos: ofrecer canales controlados —tuberías, señales, memoria compartida— para que procesos aislados puedan cooperar.
Cada fila de la tabla es un curso de la ruta que sigue a este. En el bloque 7 damos una primera vuelta por todas.
5. El kernel como contable
Buena parte del trabajo del kernel consiste en llevar registros. Para administrar algo hay que saber qué existe, a quién pertenece y en qué estado está, de modo que el kernel mantiene en su memoria grandes tablas con esa información:
- Una entrada por cada proceso: su identificador, su estado, su dueño, sus registros guardados.
- Para cada proceso, el mapa de su memoria y la lista de sus archivos abiertos.
- Para cada página de memoria física, si está libre o quién la usa.
- Para cada archivo en uso, su tamaño, sus permisos y la ubicación de sus datos.
- Para cada dispositivo, su driver y sus recursos.
Estos registros no son nada exótico: son estructuras de C. La que describe a un proceso en Linux se llama task_struct. Tiene cientos de campos; este es un extracto simplificado:
struct task_struct {
unsigned int __state; /* en ejecución, en espera, detenido… */
pid_t pid; /* identificador del proceso */
struct task_struct *parent; /* el proceso que lo creó */
struct mm_struct *mm; /* su espacio de direcciones */
struct files_struct *files; /* sus archivos abiertos */
const struct cred *cred; /* usuario y permisos */
char comm[16]; /* nombre del programa */
/* … y varios cientos de campos más … */
};
Un proceso, desde el punto de vista del kernel, es una de estas estructuras. Crear un proceso es reservar una y completarla; finalizarlo es liberarla. Los programas no pueden leer la memoria del kernel, pero este publica parte del contenido de sus tablas en forma de archivos. El siguiente comando muestra varios campos del registro de la shell actual, cuyo PID es $$:
$ grep -E '^(Name|State|Pid|PPid|Uid|VmRSS):' /proc/$$/status
Name: bash
State: S (sleeping)
Pid: 3120
PPid: 3098
Uid: 1000 1000 1000 1000
VmRSS: 5632 kB
Cada línea corresponde a un campo de la estructura: Name a comm, State a __state, PPid al identificador de parent, Uid a cred y VmRSS, la memoria física que usa, a mm. Un detalle revelador: la shell figura como sleeping, dormida, porque en ese instante está esperando a que termine el grep que ella misma lanzó.
Estos archivos no existen en ningún disco. El kernel genera su contenido en el momento en que alguien los lee, a partir de sus tablas. Así obtienen sus datos ps, top y free, y así los obtendremos nosotros en el tema 31.
6. Dónde vive: en el disco
Antes de ejecutarse, el kernel es un archivo. En una instalación de Linux se encuentra en el directorio /boot, junto con algunos archivos auxiliares:
$ ls -lh /boot | grep $(uname -r)
-rw-r--r-- 1 root root 284K sep 12 09:14 config-6.8.0-45-generic
-rw-r--r-- 1 root root 68M sep 20 08:02 initrd.img-6.8.0-45-generic
-rw------- 1 root root 8,6M sep 12 09:14 System.map-6.8.0-45-generic
-rw------- 1 root root 15M sep 12 09:14 vmlinuz-6.8.0-45-generic
| Archivo | Contenido |
|---|---|
vmlinuz-… | El kernel. Es la imagen ejecutable, comprimida; de ahí la z final. El cargador de arranque la lleva a memoria. |
initrd.img-… | Un pequeño sistema de archivos temporal que el kernel usa durante el arranque. Lo vemos en el tema 21. |
config-… | Las miles de opciones con que se compiló este kernel: qué funciones incluye y cuáles no. |
System.map-… | La tabla de símbolos del kernel: el nombre y la dirección de cada una de sus funciones. |
Quince megabytes no alcanzan para contener los drivers de todo el hardware existente, y tampoco tendría sentido cargarlos en memoria si no se usan. Por eso la mayor parte del kernel de Linux se distribuye en módulos: archivos de código que el kernel incorpora en marcha, solo cuando hacen falta. Están en /lib/modules:
$ ls /lib/modules/$(uname -r)/kernel
arch block crypto drivers fs kernel lib mm net sound
$ find /lib/modules/$(uname -r) -name '*.ko*' | wc -l # módulos disponibles
6213
$ lsmod | head -5 # módulos cargados ahora
Module Size Used by
nvme 61440 2
nvme_core 212992 3 nvme
snd_hda_intel 61440 4
iwlwifi 598016 1 iwlmvm
Hay miles de módulos disponibles y solo unas decenas cargados: los que corresponden al hardware de este equipo. Entre ellos está nvme, el driver del disco que encontramos en el tema 8. Los nombres de los subdirectorios reflejan los subsistemas de la sección 4: fs para los sistemas de archivos, mm para la memoria, net para la red, drivers para los dispositivos.
Un módulo cargado no es un programa aparte: pasa a formar parte del kernel, con sus mismos privilegios. Volvemos sobre ellos en el tema 24, al comparar los diseños de kernel.
El directorio /boot está vacío y hay pocos módulos, o ninguno: el kernel lo proporciona Windows desde fuera de la distribución, y viene con casi todo incorporado. Los comandos de las secciones siguientes sí funcionan.
7. Dónde vive: en la memoria
Una vez cargado, el kernel ocupa una parte de la memoria física y no la abandona. Pero la pregunta interesante es en qué direcciones se lo encuentra, y la respuesta es una de las decisiones de diseño más ingeniosas del sistema.
Cada proceso tiene su propio espacio de direcciones, como vimos con ilusion.c en el tema 2. En Linux, ese espacio se divide en dos mitades:
- La mitad baja es el espacio de usuario: el código, los datos, el heap, las bibliotecas y la pila del programa. Es distinta en cada proceso.
- La mitad alta es el espacio de kernel: el código y las tablas del kernel. Es la misma en todos los procesos.
Las direcciones que hemos visto hasta ahora confirman el esquema: el código de nuestros programas aparecía en torno a 0x55… o 0x5d…, las bibliotecas en 0x79… y la pila en 0x7ffc…, justo por debajo del límite superior del espacio de usuario. Las funciones del kernel están en el otro extremo:
$ sudo grep -w __x64_sys_write /proc/kallsyms
ffffffff9a4b2c10 T __x64_sys_write
El archivo /proc/kallsyms lista los símbolos del kernel en ejecución. La función que atiende la llamada al sistema write está en una dirección que empieza por ffffffff, en lo más alto del espacio de direcciones. Sin sudo, el kernel muestra ceros en lugar de las direcciones; en un equipo ARM, la función se llama __arm64_sys_write.
¿Por qué colocar el kernel dentro del espacio de direcciones de cada proceso? Por eficiencia. Cuando un programa hace una llamada al sistema, la CPU no necesita cambiar de espacio de direcciones: el código del kernel ya está ahí, a un salto de distancia. Y el kernel puede leer y escribir directamente la memoria del proceso que lo llamó, por ejemplo para copiar los datos de un read.
Que esté presente no significa que sea accesible. Las páginas del espacio de kernel están marcadas como exclusivas del modo privilegiado, y si un programa de usuario intenta leer una de esas direcciones, la CPU lo impide y el resultado es el conocido «Segmentation fault».
8. Versiones y dimensiones
El comando que más hemos usado tiene ahora una lectura completa:
$ uname -r
6.8.0-45-generic
$ cat /proc/version
Linux version 6.8.0-45-generic (buildd@lcy02-amd64-075) (x86_64-linux-gnu-gcc-13
(Ubuntu 13.2.0-23ubuntu4) 13.2.0, GNU ld 2.42) #45-Ubuntu SMP PREEMPT_DYNAMIC
| Parte | Significado |
|---|---|
6.8 | La versión del kernel publicada por el proyecto Linux. Sale una nueva cada dos meses, aproximadamente. |
.0 | El nivel de correcciones sobre esa versión. |
-45 | La revisión de la distribución: Ubuntu aplica sus propios parches y los numera. |
generic | La variante: aquí, la de uso general. Hay otras para servidores en la nube o para tiempo real. |
La segunda salida añade un dato que enlaza con el tema 9: el kernel se compiló con gcc y se enlazó con ld, las mismas herramientas que usamos para nuestros programas.
Algunas cifras ayudan a dimensionarlo. El código fuente de Linux supera los 35 millones de líneas, lo que lo convierte en uno de los proyectos de software más grandes que existen. Lo desarrollan miles de personas de cientos de empresas, coordinadas todavía por Linus Torvalds, y se publica bajo una licencia libre en kernel.org. Como dijimos en el tema 8, la mayor parte de ese código son drivers, y el kernel de un equipo concreto solo compila y carga una fracción pequeña.
9. Los hilos del kernel
Dijimos que el kernel no es un proceso, y hay que matizarlo. Aunque casi siempre trabaja en respuesta a un acontecimiento, tiene tareas de mantenimiento que no puede resolver en ese momento y que deben hacerse «cuando haya tiempo». Para ellas crea hilos del kernel: flujos de ejecución que el planificador trata igual que a los procesos, pero que se ejecutan íntegramente en modo kernel y no tienen programa de usuario detrás.
Aparecen en la lista de procesos. Todos descienden del proceso con PID 2, kthreadd:
$ ps -e -o pid,ppid,comm | awk '$2 == 2'
16 2 ksoftirqd/0
18 2 migration/0
52 2 kworker/0:1
104 2 kswapd0
412 2 jbd2/nvme0n1p2-8
…
| Hilo | De qué se ocupa |
|---|---|
ksoftirqd/N | Completa el trabajo que las interrupciones dejaron pendiente en el procesador N. |
kworker/… | Ejecuta tareas diferidas de cualquier parte del kernel; por ejemplo, escribir en disco los datos modificados. |
kswapd0 | Libera memoria cuando escasea, descartando caché o intercambiando al disco. |
migration/N | Mueve procesos de un procesador a otro para equilibrar la carga. |
jbd2/… | Mantiene el registro de transacciones de un sistema de archivos ext4. |
Varios de ellos son los responsables de mecanismos del tema 7: los datos «sucios» de la caché de disco los escribe un kworker, y el intercambio lo decide kswapd. En la salida de ps aux se los reconoce porque su nombre aparece entre corchetes, como [kswapd0], y porque no consumen memoria de usuario.
10. Escuchar al kernel
El kernel no puede usar printf: no tiene terminal ni biblioteca de C. Para informar de lo que hace, escribe mensajes en una zona circular de su memoria, el búfer de mensajes. El comando dmesg lo muestra. Cada línea lleva delante los segundos transcurridos desde el arranque:
$ sudo dmesg | head -3
[ 0.000000] Linux version 6.8.0-45-generic (buildd@lcy02-amd64-075) …
[ 0.000000] Command line: BOOT_IMAGE=/boot/vmlinuz-6.8.0-45-generic root=UUID=… ro quiet
[ 0.000000] KERNEL supported cpus:
$ sudo dmesg | tail -3 # tras conectar una memoria USB
[ 8123.401222] usb 1-2: new high-speed USB device number 7 using xhci_hcd
[ 8123.553871] usb-storage 1-2:1.0: USB Mass Storage device detected
[ 8124.580113] sd 2:0:0:0: [sdb] Attached SCSI removable disk
Las primeras líneas son lo primero que dijo el kernel al arrancar. Las últimas muestran la enumeración del tema 8 en directo: el kernel detecta el dispositivo, le asigna el driver usb-storage y lo registra como el disco sdb. Cuando algo falla con el hardware, dmesg es el primer lugar donde mirar.
Además de los mensajes, el kernel expone su estado interno en dos directorios que ya hemos usado sin presentarlos formalmente:
/proc, con información sobre los procesos y sobre el sistema en general./sys, con el inventario de dispositivos y parámetros ajustables.
$ cat /proc/uptime # segundos desde el arranque (y segundos de CPU ociosa)
8431.77 61210.42
$ cat /proc/loadavg # carga media; procesos en ejecución/total; último PID
0.42 0.31 0.27 1/1187 9214
Les dedicamos el tema 31.
11. Programar dentro del kernel
El kernel es código C, pero se escribe en condiciones muy distintas a las de un programa común. Conocerlas ayuda a entender muchas decisiones de diseño:
| Programa de usuario | Código del kernel | |
|---|---|---|
| Bibliotecas | Dispone de la biblioteca de C. | No tiene ninguna. Usa sus propias funciones; por ejemplo, printk en lugar de printf. |
| Un acceso inválido a memoria | El kernel finaliza el programa. El resto sigue. | No hay nadie por encima que lo contenga. Puede detener el sistema. |
| Pila | Varios megabytes. | Unos pocos kilobytes, de tamaño fijo. |
| Concurrencia | Opcional. | Inevitable: el mismo código puede ejecutarse a la vez en varios procesadores y ser interrumpido en cualquier punto. |
| Depuración | gdb, strace, reiniciar el programa. | Mucho más difícil: un fallo puede obligar a reiniciar la máquina. |
La segunda fila es la decisiva. Cuando el kernel detecta en sí mismo un error del que no puede recuperarse, se detiene deliberadamente para no corromper datos. Es el kernel panic de Linux, equivalente a la «pantalla azul» de Windows. Si el error es menos grave y afecta solo a la operación en curso, el kernel emite un aviso llamado oops, finaliza el proceso implicado e intenta seguir.
De aquí se desprende un principio que ya vimos aplicado en el tema 10: todo lo que pueda hacerse fuera del kernel conviene hacerlo fuera. Cada línea de código que se añade al kernel es una línea que se ejecuta con todos los privilegios y cuyo fallo afecta a todo el sistema. Hasta dónde llevar ese principio es la discusión entre kernels monolíticos y microkernels, que abordamos en el tema 24.
12. Resumen
- El kernel es el programa privilegiado, permanente e intermediario obligado entre los programas y el hardware.
- No es un proceso que corre en paralelo: solo se ejecuta cuando lo invoca una llamada al sistema, una interrupción o una excepción.
timesepara el tiempo de un programa enuser, en su propio código, ysys, dentro del kernel a su servicio. Cada llamada al sistema cuesta cientos de nanosegundos.- Se organiza en subsistemas —procesos, memoria, archivos, red y dispositivos—, además de la planificación, la protección y la comunicación entre procesos.
- Mantiene tablas con el estado de todo lo que administra. Un proceso es, para el kernel, una estructura de C; parte de su contenido se publica en
/proc. - En disco es el archivo
/boot/vmlinuz, más miles de módulos en/lib/modulesque se cargan según el hardware. - En memoria ocupa la mitad alta del espacio de direcciones de todos los procesos, protegida del modo usuario.
- Tiene hilos propios para tareas de mantenimiento, e informa de su actividad mediante
dmesg. - Un error dentro del kernel puede detener el sistema, por lo que conviene mantenerlo lo más pequeño posible.
13. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. ¿Cuáles son las tres maneras de que el kernel pase a ejecutarse después del arranque?
Una llamada al sistema, cuando un programa le pide un servicio; una interrupción, cuando un dispositivo requiere atención; y una excepción, cuando la instrucción en curso no puede completarse, por ejemplo por un acceso inválido a memoria.
2. ¿Por qué se dice que el kernel no es un proceso?
Porque no tiene un flujo de ejecución propio que avance continuamente. Es un conjunto de rutinas que se ejecutan en respuesta a acontecimientos, casi siempre en nombre del proceso que estaba en la CPU. La excepción son los hilos del kernel, que realizan tareas de mantenimiento.
3. Un programa muestra user 0m0.1s y sys 0m4.0s. ¿Qué indica sobre lo que hace?
Que casi todo su tiempo de CPU lo consumió el kernel atendiendo sus pedidos: hace muchas llamadas al sistema, probablemente de entrada y salida, y muy poco cálculo propio. Reducir la cantidad de llamadas, por ejemplo agrupando los datos, lo aceleraría.
4. ¿Qué es task_struct y qué relación tiene con /proc/<pid>/status?
Es la estructura de C con la que el kernel de Linux representa a cada proceso: su identificador, su estado, su memoria, sus archivos, su dueño. El archivo status es una vista en texto de varios de esos campos, generada por el kernel en el momento de leerlo.
5. ¿Por qué el kernel de Linux usa módulos en lugar de incluir todos los drivers en vmlinuz?
Porque hay miles de drivers y cada equipo necesita solo unos pocos. Cargarlos todos desperdiciaría memoria y haría el kernel enorme. Con módulos, el kernel incorpora en marcha únicamente los que corresponden al hardware presente.
6. ¿Qué ventaja tiene que el kernel esté mapeado en el espacio de direcciones de cada proceso?
Una llamada al sistema no exige cambiar de espacio de direcciones: basta con cambiar de modo y saltar. Además, el kernel puede acceder directamente a la memoria del proceso que lo llamó. La protección se mantiene porque esas direcciones solo son accesibles en modo kernel.
7. En la salida de ps aparece [kswapd0]. ¿Qué es y por qué su nombre va entre corchetes?
Es un hilo del kernel, que se ocupa de liberar memoria cuando escasea. Va entre corchetes porque no tiene un programa de usuario ni una línea de comandos asociados: se ejecuta por completo dentro del kernel.
8. ¿Por qué un error de programación es más grave en un driver que en una aplicación?
La aplicación se ejecuta aislada en modo usuario: si falla, el kernel la finaliza y el resto del sistema continúa. El driver se ejecuta dentro del kernel, con acceso a todo; un error suyo puede corromper la memoria del kernel o provocar un kernel panic que detenga la máquina.
Para practicar
- Ejecuta
tiemposen sus dos modos y calcula el costo por llamada al sistema en tu equipo. Luego modifica el modopedirpara escribir 4096 bytes por llamada y hacer mil veces menos llamadas. ¿Cómo cambiasys? - Usa
timecon comandos reales y clasifícalos según predomineuserosys:ls -R /usr > /dev/null,sha256sum /boot/vmlinuz-*(consudo),dd if=/dev/zero of=/dev/null bs=1 count=1000000. - Lee
/proc/self/statuscompleto concat. ¿De qué proceso es? Identifica al menos cinco campos y explica qué recurso describe cada uno. - Con
lsmod, busca los módulos de tu tarjeta de red y de tu tarjeta de sonido. Consulta uno conmodinfoy localiza su archivo.ko. - Ejecuta
sudo dmesg -w, que queda esperando mensajes nuevos, y conecta o desconecta un dispositivo USB. Sal con Ctrl+C. - Cuenta los hilos del kernel de tu sistema con
ps -e -o ppid | grep -c -w 2. ¿Cuántos hay por cada procesador?