El problema que resuelve el device model
Antes de la serie 2.6 el kernel no tenía una representación común de dispositivos, drivers y buses: cada subsistema inventaba la suya y nadie conocía la topología completa. La gestión de energía, el hotplug y la liberación segura de objetos exigían un árbol unificado. El device model es ese árbol, y este nivel lo desmonta pieza a pieza.
Durante los años noventa cada subsistema del kernel llevaba su propia contabilidad de hardware: PCI tenía sus tablas, USB las suyas, SCSI otras. Nadie podía responder a una pregunta tan simple como “quién es el padre de este dispositivo”. Esa pregunta parece académica hasta que intentas suspender un portátil: no puedes apagar el controlador USB antes que el ratón que cuelga de él. El device model, incorporado en la serie 2.6, nació para dar al kernel un único árbol de objetos donde dispositivos, drivers y buses conviven con relaciones explícitas.
- Entender por qué antes de 2.6 no existía una vista unificada de dispositivos, drivers y buses.
- Ver cómo la gestión de energía exige un árbol con orden padre-hijo estricto.
- Comprender el problema del conteo de referencias: quién libera un objeto y cuándo.
- Situar
struct device,struct device_driverystruct bus_typecomo la respuesta unificada.
El caos anterior: cada subsistema, su universo
Antes del device model cada bus era una isla. El código PCI mantenía sus propias listas de pci_dev, el código USB las suyas de usb_device, y ninguno compartía tipo ni convención con el otro. Enumerar todo el hardware de la máquina significaba recorrer subsistemas incompatibles con recorridos distintos, y no existía ninguna estructura común desde la que preguntar algo genérico.
/* Antes: cada subsistema con su estructura y sus listas propias.
Ningun tipo comun, ninguna relacion padre-hijo explicita. */
struct pci_dev *pdev; /* el mundo PCI, con su tabla de IDs */
struct usb_device *udev; /* el mundo USB, incompatible con el anterior */
struct scsi_device *sdev; /* el mundo SCSI, otra isla mas */
/* pregunta imposible de responder de forma generica:
dado un puntero cualquiera, quien es su dispositivo padre? */
El problema no era estético. Sin un tipo común no había forma de escribir código transversal —gestión de energía, hotplug, exportación a userspace— que funcionara para cualquier dispositivo. Cada nueva necesidad se reimplementaba bus por bus, y las relaciones de dependencia física entre dispositivos vivían solo en la cabeza del programador.
El otro gran empuje fue el hotplug. Un USB que se enchufa diez horas después de arrancar tiene que notificar a userspace para que se cargue su driver y se cree su nodo, y esa notificación necesitaba un formato común: no tenía sentido un mecanismo de aviso distinto por cada bus. Energía y hotplug empujaban en la misma dirección —un árbol único de objetos con nombre, padres y un canal de eventos— y de esa convergencia nació el modelo.
La energía lo cambió todo: hace falta un árbol
El catalizador fue la gestión de energía. Suspender una máquina no es apagar dispositivos en cualquier orden: un dispositivo debe suspenderse antes que aquel del que depende. El ratón USB se suspende antes que el controlador USB, que se suspende antes que el puente PCI que lo alberga. Reanudar exige el orden inverso. Esto solo es expresable si el kernel conoce, para cada dispositivo, quién es su padre.
/* include/linux/device.h (resumido) */
struct device {
struct kobject kobj; /* base comun: nombre, refcount, sysfs */
struct device *parent; /* EL ESLABON QUE FALTABA */
const struct bus_type *bus; /* como esta conectado */
struct device_driver *driver; /* quien lo gobierna, o NULL */
dev_t devt; /* major:minor para /dev */
void (*release)(struct device *dev);
/* ...decenas de campos mas... */
};
Con el campo parent el kernel construye un árbol y lo linealiza en una lista de energía. El apagado la recorre en orden inverso al de registro: como el hijo siempre se registra después del padre, ir hacia atrás garantiza suspender las hojas antes que la raíz.
/* drivers/base/power/main.c — idea esencial del apagado ordenado */
void dpm_suspend(pm_message_t state)
{
struct device *dev;
/* dpm_list guarda los dispositivos en ORDEN DE REGISTRO; como el
hijo se registra despues del padre, recorrerla al reves suspende
siempre las hojas antes que la raiz de la que dependen */
list_for_each_entry_reverse(dev, &dpm_list, power.entry)
device_suspend(dev);
}
flowchart TD ROOT[Raiz virtual] --> PCI[Puente PCI] PCI --> USBC[Controlador USB] USBC --> HUB[Hub USB] HUB --> MOUSE[Raton] HUB --> KBD[Teclado] MOUSE -. se suspende antes que .-> HUB HUB -. se suspende antes que .-> USBC
Quién libera el objeto: el conteo de referencias
El segundo problema es la vida del objeto. Un struct device no lo referencia solo su driver: lo apuntan sysfs, el subsistema de energía, un proceso que tiene abierto un fichero en /sys, otro dispositivo hijo. Si el driver hace kfree mientras alguien lo mira, el kernel corrompe memoria. La única solución correcta es contar referencias y liberar solo cuando la cuenta llega a cero.
/* nadie sabe cuantos punteros vivos hay a un device: por eso se cuentan */
struct device *get_device(struct device *dev); /* +1 a kobj.kref */
void put_device(struct device *dev); /* -1; en 0 llama release */
Por eso struct device no se libera con kfree sino a través de su función release, invocada por el conteo de referencias cuando cae el último usuario. Ese mecanismo no es exclusivo del dispositivo: vive en el kobject que todos ellos incrustan, la pieza que verás en la lección 28.2.
Topología
Sin un tipo común ni un campo parent, nadie conoce las relaciones padre-hijo del hardware. El device model las hace explícitas en un único árbol.
Energía
Suspender y reanudar exige orden hoja-raíz. Solo es expresable si el kernel conoce la topología completa, no islas por subsistema.
Vida del objeto
Muchos usuarios apuntan a un mismo dispositivo. Liberarlo con kfree mientras se usa corrompe memoria; hace falta conteo de referencias.
La respuesta: un modelo unificado
La solución fue una tríada de tipos comunes —struct bus_type, struct device_driver y struct device— asentada sobre un ladrillo universal, el kobject, y con una ventana a userspace, sysfs. Cada dispositivo, cada driver y cada bus pasan a ser objetos de primera clase con nombre, lugar en el árbol, conteo de referencias y representación en /sys. La topología deja de vivir en la cabeza del programador y pasa a ser una estructura de datos que el kernel puede recorrer.
Esa estructura se vuelve palpable en /sys, donde el mismo dispositivo aparece a la vez desde tres ejes: su lugar físico en el árbol, su bus y su clase.
# la topologia fisica: sigue los padres hacia la raiz
ls -l /sys/devices/
# el eje de conexion y el eje de funcion, para el mismo hardware
ls /sys/bus/ # pci, usb, i2c, platform...
ls /sys/class/ # net, block, tty, leds...
Esas tres vistas no son tres copias del hardware, sino tres proyecciones del único árbol de objetos que ahora vive en el kernel. Antes no existía ninguna; había islas incomunicadas.
El árbol de objetos del kernel se proyecta automáticamente en /sys: /sys/devices es la topología física, /sys/bus agrupa por bus y /sys/class por función. Es tentador pensar que sysfs es el device model, pero es solo su reflejo: el modelo son las estructuras en memoria y sus relaciones; sysfs es la vista que el kernel genera de ellas para userspace.
Detente en la magnitud del cambio conceptual, porque es mayor que cualquier API concreta de este nivel. Antes de 2.6 el kernel tenía dispositivos pero no los pensaba como un dominio unificado: cada bus era un dialecto, y las relaciones entre piezas de hardware eran conocimiento tácito disperso por miles de líneas. El device model hizo con el hardware lo que una buena ontología hace con un campo del saber: definió las categorías fundamentales —dispositivo, driver, bus, clase—, fijó las relaciones que pueden existir entre ellas —un device pertenece a un bus, un driver gobierna un device, un device es hijo de otro— y las materializó en tipos que el kernel manipula con código genérico. La gestión de energía fue solo el detonante; lo que quedó fue infinitamente más grande. Porque una vez que existe un árbol de objetos con conteo de referencias y proyección a userspace, ese mismo sustrato sirve para el hotplug, para el arranque ordenado, para la asignación de recursos, para el firmware, para la depuración. El kobject, el uevent y sysfs —las tres piezas que estudiarás en las lecciones siguientes— nacieron para resolver un problema de energía y terminaron siendo la infraestructura sobre la que se construye todo lo demás. Esa es la firma de una buena abstracción: resuelve el problema para el que se diseñó y luego resuelve diez que nadie había planteado. Cuando internalices que el device model no es “el código que gestiona dispositivos” sino “la ontología del hardware que el resto del kernel presupone”, dejarás de verlo como un subsistema más y empezarás a verlo como la gramática común que hace que PCI, USB, I2C y platform hablen el mismo idioma.
- Explora
/sys/devicesy sigue los enlaces../desde un dispositivo cualquiera hasta la raíz; dibuja la cadena de padres que encuentres. - Elige un dispositivo y lee su
power/en sysfs; razona en qué punto de la lista de energía se suspendería respecto a su padre. - Explica por qué hacer
kfreesobre unstruct devicees un error y qué garantizanget_deviceyput_device. - Para un dispositivo concreto, identifica su bus, su driver enlazado y su clase, y argumenta qué pregunta de las tres —topología, energía, vida— resuelve cada uno de esos tres datos.