Listas enlazadas intrusivas: cómo las emplea Linux para gestionar procesos

Fuentes: Intrusive linked lists

Las listas enlazadas intrusivas son una variante de las listas enlazadas en la que los punteros de enlace se embeben dentro de la propia estructura del objeto que se desea enlazar, en lugar de residir en un nodo externo que contiene un puntero a los datos. En una lista convencional cada nodo almacena un puntero al dato y otro al siguiente nodo; en la versión intrusiva solo persiste el puntero al siguiente, ya que el nodo forma parte del objeto listado.

Para recuperar el objeto contenedor a partir de un nodo se resta el desplazamiento en bytes del miembro lista desde la dirección del nodo. En C compilado con GCC se logra restando ese desplazamiento —preferiblemente con la macro offsetof— sobre un puntero void, una operación no portátil pero admitida por GCC y aprovechada por el kernel de Linux.

Las listas intrusivas ofrecen dos ventajas principales: requieren menos asignaciones de memoria (una sola por objeto frente a las dos de una lista no intrusiva) y sufren menoscache thrashing al iterar, porque solo es necesario desreferenciar el siguiente nodo. Antes de abordar su uso en Linux, el texto repasa las listas doblemente enlazadas y circulares: las primeras simplifican inserciones y borrados al mantener punteros next y prev, y las segundas cierran el ciclo haciendo que el último nodo apunte al primero, lo que permite recorrer la lista desde cualquier punto.

Linux emplea listas enlazadas de forma masiva —más de 10.000 ocurrencias de struct list_head en la versión 5.2— para tareas como el seguimiento de slabs de memoria libre o la iteración sobre procesos en ejecución. Un análisis de Rusty Russell indica que solo el 6 % de las operaciones sobre listas son recorridos completos, de los cuales el 28 % ocurre sobre listas vacías o de un único nodo, lo que confirma que el kernel prioriza inserciones y borrados.

La implementación canónica se define en include/linux/list.h mediante struct list_head con punteros next y prev. La lista se inicializa de forma estática con LIST_HEAD_INIT o de forma dinámica con INIT_LIST_HEAD, que hace que el nodo apunte a sí mismo. La inserción se realiza con list_add, que delega en __list_add para reasignar los punteros entre el nodo cabeza y su siguiente.