Cómo funciona cpuidle en Linux y por qué afecta tanto al consumo

Última actualización: 11/05/2026
Autor: Isaac
  • cpuidle separa política y mecanismo mediante governors y drivers para gestionar los estados de reposo de la CPU.
  • La elección de estado se basa en residencia objetivo, latencia de salida, histórico de inactividad y próximos temporizadores.
  • PM QoS y el scheduler tick condicionan qué estados profundos se pueden usar sin romper los requisitos de latencia.
  • En ARM y otros SoC modernos, cpuidle se integra con firmware vía PSCI, siendo clave para el consumo y la autonomía reales.

cpuidle linux gestion del tiempo de inactividad

Si usas Linux en portátil, sobremesa o placas ARM y te preocupa la batería, el calor o por qué tu CPU no se duerme “como debería”, entender cómo funciona el subsistema cpuidle es clave. Detrás de algo aparentemente tan simple como “la CPU está inactiva” hay una maquinaria bastante sofisticada que decide qué estado de reposo usar, cuánto tiempo se puede dormir y cuánto tarda en despertarse.

Además, si vienes de proyectos como Asahi Linux en Mac con M1/M2, es normal estar confundido: se habla de drivers de cpuidle, de estados de reposo poco maduros o directamente ausentes, y de que eso es justo lo que impide usar el sistema como daily driver. En este artículo vamos a desgranar, con calma, qué es realmente cpuidle, cómo funciona por dentro, qué papel juegan governors y drivers, cómo se configura, qué opciones del kernel le afectan y cómo se integra en plataformas ARM modernas como las que usan PSCI o TF-A.

Qué es el subsistema cpuidle y por qué existe

Hace décadas, el “reposo” del kernel era un bucle vacío: cuando no había nada que ejecutar, se corría el idle loop, que básicamente hacía un bucle infinito esperando a la siguiente interrupción. Solo por no ejecutar código complejo ya se ahorraba algo de energía: no se tocaba tanto la caché, la FPU, etc.

Con la evolución del hardware, los procesadores empezaron a ofrecer múltiples estados de inactividad (C-states, idle states), cada uno con distintos niveles de ahorro y distintas penalizaciones: entrar en un estado profundo puede ahorrar mucha energía, pero cuesta más tiempo y energía entrar y salir de él. Si te metes en un estado demasiado profundo y te despiertan enseguida, has perdido.

Aquí entra en juego cpuidle, el subsistema del kernel dedicado a gestionar el tiempo de CPU inactiva. Su objetivo es decidir, en cada oportunidad en que una CPU se queda sin tareas (solo le queda la tarea idle), cuál es el mejor estado de reposo que puede usar para ahorrar energía sin romper la latencia de respuesta.

Conceptualmente, cpuidle separa dos piezas: por un lado el mecanismo (drivers), que sabe cómo hablar con el hardware y enumerar los estados de idle; y por otro, la política (governors), que decide qué estado concreto se va a usar en cada momento, basándose en historial de inactividad, próximos temporizadores y restricciones de latencia.

Todo esto ocurre en el idle loop: cuando el scheduler ve que una CPU no tiene más tareas runnable, ejecuta la tarea especial “idle”, cuyo código llama primero al governor para elegir estado y después al driver para entrar en él.

cpuidle linux gobernadores y drivers

CPUs lógicas, tarea idle y qué significa estar inactivo

El subsistema de cpuidle trabaja siempre en términos de CPUs lógicas, es decir, las entidades que ve el scheduler: pueden ser núcleos físicos, hilos hardware (hyper-threads) o combinaciones, según la arquitectura e implementación.

Desde el punto de vista del kernel, una CPU lógica es “idle” cuando no hay tareas ejecutables asociadas a ella salvo la tarea idle. El scheduler maneja procesos e hilos como “tasks”, y éstas pueden estar en distintos estados; cuando una tarea se vuelve runnable, se asigna a alguna CPU. Si una CPU se queda solo con la tarea idle runnable, el kernel la considera inactiva.

La tarea idle ejecuta el llamado idle loop. Ese bucle, en cada iteración, llama a un governor de cpuidle para decidir un estado de reposo y luego invoca al driver de cpuidle para pedir al hardware que entre en dicho estado. Si no hay estados de reposo disponibles, no hay tiempo suficiente antes del siguiente evento o las restricciones de latencia son demasiado duras, la CPU se queda ejecutando un bucle relativamente inútil o usando la instrucción más básica de espera (tipo hlt o similar).

En procesadores multi-core o con SMT, las decisiones de idle afectan a jerarquías de unidades: una petición de reposo a nivel de hilo puede hacer que su core entre también en un estado más profundo si todos los hilos están idle, y a su vez que un paquete o “cluster” de cores se duerma si todos sus miembros lo permiten. Cpuidle necesita modelar esto a través de estados que representan combinaciones de niveles jerárquicos, con latencias y residencias que reflecten el estado más profundo posible.

Cuando se pide un estado representado por un objeto de tipo struct cpuidle_state, el driver puede autorizar al hardware a ir tan profundo como permita el diseño. Por eso, la latencia de salida (exit latency) y la residencia objetivo (target residency) deben estar alineadas con el peor caso real de la combinación de estados dentro de la jerarquía (core, cluster, package, etc.).

Gobernadores de cpuidle: cómo deciden qué estado usar

Los governors de cpuidle son módulos de política que se ejecutan cada vez que una CPU entra en el idle loop. Su cometido es, con la información disponible, seleccionar el estado de inactividad que más energía ahorre sin violar las restricciones de latencia.

Cada governor se define como una estructura struct cpuidle_governor, con callbacks enable, disable, select y reflect, un campo de prioridad (rating) y un nombre. Una vez registrado con cpuidle_register_governor(), puede ser elegido por el kernel de forma automática (según rating, configuración por defecto o parámetro cpuidle.governor=) o manualmente desde user space vía sysfs.

Cuando un governor se activa para una CPU mediante su callback enable(), recibe un struct cpuidle_device que representa esa CPU y un struct cpuidle_driver con la lista de estados disponibles (struct cpuidle_state). Si enable() falla, el kernel usa un código de idle por defecto específico de la arquitectura en vez de cpuidle para esa CPU.

  Usar WSL para tareas no evidentes: guía completa para desarrolladores en Windows

El corazón del governor es el callback select(), que recibe el dispositivo cpuidle, el driver y un puntero a un booleano stop_tick. Este callback devuelve el índice del estado elegido dentro del array de estados, o un código de error negativo. Además, puede decidir si se debe parar el scheduler tick en esa CPU (limpiando o no el booleano).

Cuando la CPU se despierta, el governor recibe la llamada a reflect() con información sobre qué estado se eligió y cuánto duró realmente el periodo de inactividad. Esto le permite refinar sus predicciones a partir del histórico. También está obligado a respetar las restricciones de latencia de PM QoS: mediante cpuidle_governor_latency_req() obtiene el límite de latencia efectivo y nunca debe seleccionar un estado cuya exit_latency supere ese valor.

Principales governors: ladder, haltpoll, menu y teo

En Linux existen varios governors de cpuidle, cada uno con su estrategia y su público objetivo. Los cuatro principales son ladder, haltpoll, menu y teo, y cuál se elige por defecto depende en gran parte de si el kernel es tickless (capaz de parar el scheduler tick) o no.

ladder está pensado para sistemas con tick periódico activo. Usa un enfoque sencillo basado casi exclusivamente en historial de duraciones de idle: sube y baja por una “escalera” de estados, promoviendo a estados más profundos cuando observa inactividad suficientemente larga y retrocediendo cuando los despertares llegan demasiado pronto.

haltpoll es un governor especializado para máquinas virtuales. En lugar de entrar en estados de idle hardware profundos, se basa mucho en el polling (bucle de espera) para reducir la latencia aparente en entornos donde los estados físicos de reposo quizá no aportan mucho o no están bien modelados por el hipervisor.

menu y teo son los governors que se usan en sistemas tickless (CONFIG_NO_HZ_IDLE o CONFIG_NO_HZ_FULL). Ambos combinan el histórico de periodos idle con la información del próximo temporizador, e intentan ahorrar energía sin pasar más tiempo computando la decisión que lo que se ahorra con ella.

El governor menu trata de predecir explícitamente cuánto tiempo va a estar idle la CPU. Parte del tiempo hasta el siguiente timer como límite superior y le aplica un factor corrector basado en el histórico de inactividad reciente para aproximar una duración “típica”. Con ese valor previsto, consulta la tabla de estados para elegir el más profundo cuya residencia objetivo encaje.

El governor teo (Timer-Event Oriented) ataca el problema de otra forma: en lugar de intentar predecir el tiempo exacto de inactividad, cuantiza el histórico en “bins” o intervalos asociados a cada estado. Cada bin corresponde al rango de tiempos donde ese estado suele ser óptimo. TEO mantiene métricas de hits (despertares donde la duración real casa bien con la residencia objetivo) e intercepts (despertares por eventos no temporizados que desbaratan la predicción) y, con eso, infiere directamente qué estado concreto es el que más probabilidades tiene de ser el correcto.

Solo cuando le compensa, TEO mira el tiempo hasta el siguiente timer para no entrar en un estado cuya target residency sea mayor que la ventana real disponible. Además, su diseño intenta reducir el coste de decisión: en escenarios con idle muy cortos, es mejor elegir rápido un estado poco profundo que gastarse más energía pensando que la que se ahorra.

Drivers de cpuidle: puente entre el kernel y el hardware

Mientras los governors se ocupan de la política, los drivers de cpuidle implementan el mecanismo real de entrada y salida de estados. Cada driver representa la lista de estados soportados por el procesador (o por un conjunto de CPUs) a través de un struct cpuidle_driver que contiene el array de struct cpuidle_state.

Cada struct cpuidle_state define, entre otros campos, la residencia objetivo (target_residency en microsegundos), la latencia máxima de salida (exit_latency), flags como CPUIDLE_FLAG_POLLING y, muy importante, un callback enter() que es el que hace la parte delicada: ejecutar las instrucciones o llamadas necesarias para pedir al hardware que entre en ese estado.

Las entradas del array de estados deben ir ordenadas por target_residency ascendente, de forma que el índice 0 suele corresponder al estado más superficial (y más barato de usar) y los índices que siguen se asocian a estados cada vez más profundos. Los governors asumen esta ordenación para sus cálculos.

El callback enter() recibe el dispositivo cpuidle, el driver y el índice del estado a usar. Para casos de suspensión tipo suspend-to-idle, se emplea en cambio enter_s2idle(), que debe cumplir restricciones más duras: no puede reactivar interrupciones ni manipular dispositivos de temporización durante su ejecución, algo que enter() sí podría hacer según la plataforma.

Además de describir los estados, el driver debe registrar qué CPUs están bajo su control: cada CPU tiene su struct cpuidle_device, que se registra normalmente con cpuidle_register_device(). Si no hay estados “acoplados” (que requieren coordinación entre varias CPUs), el registro se hace con cpuidle_register_driver(); si los hay, se usa cpuidle_register(), que además se encarga de registrar los dispositivos.

En plataformas modernas se intenta reducir el número de drivers específicos: por ejemplo, en ARM se suele usar un driver genérico que delegue en interfaces estándar como PSCI (Power State Coordination Interface), y en RISC-V algo similar vía SBI (Supervisor Binary Interface). Aun así, en x86 siguen existiendo drivers como intel_idle (con la tabla de estados “quemada” en el driver) y acpi_idle (que obtiene los estados de las tablas ACPI).

Estados de inactividad: parámetros, sysfs y métricas

Cada estado de reposo que cpuidle expone a los governors se caracteriza por varios parámetros principales. Los dos más importantes son la residencia objetivo (target_residency) y la latencia de salida (exit_latency), ambos en microsegundos.

La target_residency marca, en la práctica, la profundidad energética del estado: es el tiempo mínimo que el hardware debe permanecer en ese estado (incluyendo el coste de entrada) para que compense frente a estados menos profundos. Si el sistema despierta antes de alcanzar esa residencia, probablemente se ha gastado más energía entrando en ese estado que la que se ha ahorrado.

  Alternativas a Paint para Linux y macOS que sí merecen la pena

La exit_latency fija el peor caso de tiempo que pasa entre que la CPU pide salir de ese estado y ejecuta realmente la siguiente instrucción útil. Incluye también el caso en que llega un evento de wakeup mientras el hardware está todavía entrando en el estado, porque en general hay que completar la transición interna antes de poder salir de forma ordenada.

Además, hay flags que describen propiedades adicionales del estado: por ejemplo, CPUIDLE_FLAG_POLLING indica que ese “estado” en realidad no es un reposo hardware, sino un bucle de polling que se usa como mecanismo especial para no tocar los estados reales cuando conviene (por ejemplo, en ciertos entornos virtualizados o de depuración).

El kernel expone información muy detallada de los estados de idle por CPU a través de sysfs, en /sys/devices/system/cpu/cpu<N>/cpuidle/. Ahí hay directorios state0, state1, etc., uno por cada entrada del array del driver, y en cada uno podemos encontrar atributos como name, desc, latency, residency, usage, time, power, above, below y rejected.

Estos atributos permiten ver cuántas veces se ha pedido cada estado (usage), cuánto tiempo total se ha pasado en él según el kernel (time), y en qué medida la elección fue buena o mala (above y below contabilizan casos donde la duración real de idle fue claramente demasiado corta o larga respecto a la residencia objetivo). rejected cuenta las ocasiones en que la solicitud fue rechazada, típicamente porque entró una interrupción justo en el momento de la transición.

Hay un atributo especialmente útil, disable, que permite activar o desactivar ese estado concreto para una CPU desde user space (escribiendo 1 o 0). Si se deshabilita para una CPU, el governor no lo tendrá en cuenta al seleccionar; si se quiere eliminar por completo un estado del sistema, hay que deshabilitarlo en todas las CPUs. El atributo default_status indica si el estado se habilita o no por defecto.

El scheduler tick y los sistemas tickless

El famoso scheduler tick es un temporizador periódico (100, 250 o 1000 Hz, según CONFIG_HZ) que el kernel usa, entre otras cosas, para repartir el tiempo de CPU entre tareas, actualizar contadores y disparar expiraciones de timers.

Desde el punto de vista de cpuidle, el tick periódico es un incordio: mientras esté activo en una CPU idle, esa CPU no puede dormir más que el período del tick, y además cada despertar por tick supone entrar y salir de un estado de reposo, desperdiciando energía si se elige uno demasiado profundo.

Por definición, en una CPU que solo está ejecutando el idle loop no es estrictamente necesario mantener el tick para el reparto de CPU: no hay más tareas runnable. Por eso, Linux puede configurarse como tickless en idle (CONFIG_NO_HZ_IDLE) o en modo “full” cuando solo hay una tarea aislada en la CPU (CONFIG_NO_HZ_FULL), desactivando el tick en esas condiciones.

La decisión de parar o no el tick la toma el governor mediante el parámetro stop_tick de su callback select(). Si espera una interrupción (de timer o no) en un plazo corto (dentro de lo que sería un período de tick), no tiene sentido desactivar el tick: se gastaría tiempo reprogramándolo y posiblemente se pasaría el periodo de inactividad en un estado demasiado superficial si luego resulta que nadie despierta a la CPU.

Por el contrario, si el governor cree que la CPU va a estar inactiva durante más tiempo que el tick y el estado elegido es profundo, es mejor parar el tick para no arruinar el ahorro. Algunas configuraciones del kernel (parámetro nohz=off o desactivar CONFIG_NO_HZ_IDLE) fuerzan a que el tick nunca se detenga, en cuyo caso el sistema no es tickless y el governor por defecto suele ser ladder en lugar de menu o teo.

PM QoS: cómo se controlan las latencias de reposo

El framework de Power Management Quality of Service (PM QoS) permite a drivers y procesos de user space expresar restricciones sobre el comportamiento energético del sistema, en particular sobre las latencias de entrada/salida de estados de reposo.

Para cpuidle existen dos grandes tipos de restricciones: un límite global de latencia de CPU (CPU latency limit) y restricciones de latencia de reanudación por CPU (pm_qos_resume_latency_us). Internamente, las peticiones se almacenan en listas de prioridad y el valor efectivo es, en este caso, el mínimo de todos los solicitados.

Desde user space, el límite global se puede modificar abriendo /dev/cpu_dma_latency y escribiendo en ese descriptor un entero de 32 bits con la latencia máxima tolerada en microsegundos. Cada descriptor abierto representa una petición independiente; cuando se cierra, esa petición desaparece y el sistema recalcula el valor efectivo con el resto.

Para restricciones por CPU, existe el archivo power/pm_qos_resume_latency_us en /sys/devices/system/cpu/cpu<N>/. Escribir un valor ahí cambia la petición asociada a esa CPU en concreto (compartida por todo user space, así que conviene arbitrar quién la toca). Drivers del kernel también pueden registrar sus propias peticiones via las APIs internas de PM QoS.

Los governors de cpuidle deben, en cada selección de estado, respetar el mínimo entre la latencia global efectiva y la de la CPU afectada: no pueden elegir estados cuya exit_latency supere ese límite. Esto es importantísimo en casos de software con requisitos de tiempo real blando, como audio o vídeo, donde una reanudación demasiado lenta desde un estado profundo podría causar underruns o glitches.

Existe además otro QoS, cpu_wakeup_latency, que afecta a la elección de estados de idle durante el modo suspend-to-idle (s2idle) del sistema. Su manejo, desde user space, es similar al de cpu_dma_latency y también se expresa en microsegundos.

Control de cpuidle vía parámetros del kernel

Linux permite ajustar el comportamiento de cpuidle y de los drivers de idle desde la línea de comandos del kernel. El parámetro más drástico es cpuidle.off=1, que desactiva el subsistema por completo: el idle loop sigue existiendo, pero ya no se invocan governors ni drivers de cpuidle y en su lugar se recurre al mecanismo “por defecto” de la arquitectura, normalmente mucho más simple y menos eficiente.

  Cómo crear Dual Boot en VirtualBox con Windows y Ubuntu

El parámetro cpuidle.governor=<nombre> permite forzar el governor a usar, por ejemplo cpuidle.governor=menu o cpuidle.governor=teo, en lugar del que se escogería automáticamente. Esto es útil para experimentar con consumo y latencia en un mismo hardware sin recompilar el kernel.

En arquitecturas x86 hay además parámetros específicos relacionados con cómo se entra en idle. Por ejemplo, idle=halt y idle=poll desactivan los drivers intel_idle y acpi_idle, obligando al sistema a usar la instrucción hlt o un bucle de polling puro para el reposo, respectivamente. Esto simplifica el comportamiento pero a costa de eficiencia: idle=poll, en particular, puede impedir el uso de P-states que requieren CPUs en idle, empeorando consumo y rendimiento monohilo.

El parámetro idle=nomwait prohíbe el uso de la instrucción MWAIT para entrar en estados de reposo, forzando a acpi_idle a usar hlt y desactivando intel_idle en procesadores Intel, de forma que solo ACPI gestione los estados. Además, los drivers intel_idle y processor (este último incluye acpi_idle) aceptan opciones como intel_idle.max_cstate=<n> y processor.max_cstate=<n> para recortar la lista de estados disponibles en el driver y descartar todos los que sean más profundos que el índice indicado.

En el caso de intel_idle.max_cstate=0, esto equivale a desactivar el driver específicamente de Intel y dejar paso al de ACPI, mientras que processor.max_cstate=0 se interpreta como processor.max_cstate=1. Son opciones útiles para diagnosticar problemas de estabilidad, latencias anómalas o consumos extraños, al precio de limitar la capacidad del sistema para ahorrar energía en idle.

Integración con plataformas ARM, PSCI y standby

En plataformas ARM modernas (como muchos SoC de TI, NXP, Rockchip, Apple vía Asahi, etc.), cpuidle suele integrarse con firmware de bajo nivel a través de PSCI, y es habitual en dispositivos IoT con gestión inteligente de servicios IoT. El driver genérico de ARM para cpuidle es el que habla con PSCI usando llamadas SMC (Secure Monitor Call) y delega la transición real de estados a TF-A (Arm Trusted Firmware) u otro firmware equivalente.

Un caso típico es el de un SoC como AM62x, donde el estado de standby se implementa como un estado de CPUIdle basado en la instrucción WFI (Wait For Interrupt). Desde el punto de vista del usuario, el sistema entra y sale de standby continuamente, muchas veces por segundo, sin necesidad de interacción: se trata del estado de reposo “ligero” por defecto, con tiempos de entrada y salida del orden de microsegundos.

La ruta de ejecución cuando el sistema entra en idle en estas plataformas es aproximadamente: el idle loop detecta que no hay tareas, el governor elige el estado correspondiente (por ejemplo, uno llamado stby), el driver genérico de ARM llama a PSCI a través de la capa drivers/firmware/psci.c, y en el lado de TF-A se invoca el manejador cpu_standby() definido en la estructura plat_psci_ops. Ahí es donde realmente se ejecuta la WFI.

Cuando llega una interrupción, el procesador sale de WFI automáticamente, TF-A devuelve el control al kernel y la CPU continúa ejecutando desde donde estaba. Todo esto se orquesta de forma transparente, siempre que el device tree describa correctamente los idle states (nodo idle-states, referencias desde cada CPU, y propiedades como entry-latency-us, exit-latency-us y min-residency-us).

Este standby ligero por CPU no debe confundirse con modos de deep sleep a nivel de sistema completo, donde se apagan bloques de alimentación enteros, se reconfiguran periféricos y los tiempos de entrada/salida están en el rango de milisegundos o segundos. Los estados de cpuidle se orientan más a microgestionar el reposo de corto plazo, mientras que los modos de suspensión profunda requieren coordinación adicional de runtime PM, suspend/resume de drivers y, a menudo, soporte específico en el bootloader o firmware.

Una vez que el kernel tiene cpuidle activado y un driver funcional (por ejemplo, el genérico ARM + PSCI correctamente descrito en el DT), no hace falta que el usuario “active” nada: los governors se encargan automáticamente. Lo que sí se puede hacer es consultar estadísticas en /sys, cambiar el governor actual en /sys/devices/system/cpu/cpuidle/current_governor o ajustar las propiedades de los estados expuestos por el driver.

En plataformas relativamente nuevas o en procesos de port como Asahi Linux, buena parte de los problemas de batería, calor o falta de “sleep” funcional están ligados a que aún no existe (o no está madura) la integración completa de cpuidle con el hardware: faltan estados bien descritos en el DT, no hay firmware que implemente estados razonables, o el driver aún está en desarrollo. Hasta que eso se estabiliza, es habitual que solo se use un estado WFI muy superficial o, directamente, que se desactive cpuidle y se viva en un reposo bastante cutre.

Al final, entender cómo cpuidle modela las CPUs lógicas, cómo los governors toman decisiones con residencias objetivo, latencias y PM QoS, cómo los drivers conectan con ACPI, PSCI o firmware propietario y cómo el scheduler tick condiciona los periodos de inactividad, permite ver con otros ojos por qué tu portátil aguanta más (o menos) batería, por qué un kernel “tickless” suele consumir menos, y por qué en ciertos port a hardware exótico el sleep y el idle son, literalmente, la diferencia entre un sistema de pruebas y un daily driver usable.

Configurar políticas de energía avanzadas con Power Profiles y Modern Standby
Related article:
Configurar políticas de energía avanzadas con Power Profiles y Modern Standby