wandres.dev
EL DEVICE MODEL · kobject, bus/class/driver

kobject, kset y sysfs: la infraestructura base

El kobject es el ladrillo universal del kernel: aporta nombre, conteo de referencias por kref, lugar en una jerarquía y una entrada en sysfs. El kobj_type define su liberación y sus operaciones de sysfs; el kset agrupa objetos afines y ancla los uevents. De esa jerarquía de kobjects nace, directorio a directorio, el árbol de /sys.

⏱ 16 min

Debajo de cada struct device, de cada driver y de cada entrada de /sys hay la misma pieza minúscula: el kobject. Es el ladrillo común que el device model incrusta en todo. Aporta cuatro cosas a la vez —un nombre, un contador de referencias, un lugar en una jerarquía y un directorio en sysfs— y lo hace en apenas unas decenas de bytes. Entender el kobject es entender el sustrato sobre el que se apoya todo lo demás del modelo.

🎯 Al terminar esta lección sabrás
  • Entender struct kobject como el ladrillo común y su kref de conteo de referencias.
  • Ver cómo el kobj_type define release y sysfs_ops, y por qué release libera el objeto contenedor.
  • Comprender struct kset como colección de objetos afines y punto de anclaje de los uevents.
  • Ver cómo la jerarquía de kobject genera, directorio a directorio, el árbol de /sys.

kobject: el ladrillo y su contador

Un kobject casi nunca se usa suelto: se incrusta dentro de una estructura mayor, y desde el kobject se recupera el contenedor con container_of. Sus campos revelan sus cuatro responsabilidades.

/* include/linux/kobject.h (resumido) */
struct kobject {
	const char		*name;     /* nombre = directorio en sysfs */
	struct list_head	entry;     /* enlace en el kset */
	struct kobject		*parent;   /* directorio padre en la jerarquia */
	struct kset		*kset;     /* coleccion a la que pertenece */
	const struct kobj_type	*ktype;    /* release + sysfs_ops */
	struct kernfs_node	*sd;       /* su nodo en el arbol de sysfs */
	struct kref		kref;      /* CONTEO DE REFERENCIAS */
	unsigned int state_initialized:1;
	unsigned int state_in_sysfs:1;
	/* ...banderas de estado... */
};

El corazón es el kref: un contador atómico. kobject_get lo incrementa, kobject_put lo decrementa y, cuando llega a cero, invoca la función release del ktype. Ese es el único punto legítimo donde se libera la memoria del objeto. Ningún código debe hacer kfree de una estructura que incrusta un kobject vivo: eso lo decide el conteo de referencias.

kobj_type: release y la ventana a sysfs

El kobj_type es el comportamiento compartido por todos los kobject del mismo tipo. Define dos cosas críticas: cómo se libera el objeto y cómo responde a las lecturas y escrituras de sus atributos en sysfs.

struct termo {
	struct kobject kobj;   /* el kobject incrustado */
	int milicelsius;
};

static inline struct termo *to_termo(struct kobject *kobj)
{
	return container_of(kobj, struct termo, kobj);
}

/* SE LLAMA cuando el ultimo kref se suelta: aqui se libera el contenedor */
static void termo_release(struct kobject *kobj)
{
	kfree(to_termo(kobj));
}

/* enruta las lecturas de cualquier atributo del kobject */
static ssize_t termo_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
	return sysfs_emit(buf, "%d\n", to_termo(kobj)->milicelsius);
}

static const struct sysfs_ops termo_sysfs_ops = {
	.show = termo_show,
};

static struct attribute termo_attr = { .name = "milicelsius", .mode = 0444 };
static struct attribute *termo_attrs[] = { &termo_attr, NULL };
ATTRIBUTE_GROUPS(termo);

static const struct kobj_type termo_ktype = {
	.release	= termo_release,       /* libera al llegar a cero */
	.sysfs_ops	= &termo_sysfs_ops,    /* como se leen los atributos */
	.default_groups	= termo_groups,        /* ficheros a crear en el directorio */
};

La conexión clave es que release libera el contenedor, no el kobject aislado. Como el kobject está incrustado, liberar la struct termo con kfree destruye ambos a la vez. Por eso el conteo de referencias del kobject gobierna, en la práctica, la vida de toda la estructura que lo alberga.

kset: colecciones y punto de anclaje

Un kset es una colección de kobject afines. Lo peculiar es que un kset es a su vez un kobject: tiene su propio directorio en sysfs y puede ser padre de sus miembros. Además ancla las operaciones de uevent que se aplican a todos los objetos de la colección.

/* include/linux/kobject.h (resumido) */
struct kset {
	struct list_head list;        /* todos sus kobjects */
	spinlock_t list_lock;
	struct kobject kobj;          /* un kset ES un kobject */
	const struct kset_uevent_ops *uevent_ops;  /* filtra y completa los uevents */
};

/* crea el kset y lo cuelga bajo /sys/kernel */
struct kset *ks = kset_create_and_add("termos", NULL, kernel_kobj);

Cuando un kobject pertenece a un kset, hereda de él el punto de emisión de eventos hacia userspace: al añadirlo, el uevent_ops del kset puede vetar el evento o enriquecerlo con variables. Ese es el enganche que en la lección 28.5 conecta el kernel con udev.

De kobject a /sys

La jerarquía de directorios de /sys no es un artefacto aparte: es la proyección directa de los punteros parent y de la pertenencia a un kset. Registrar un kobject con nombre y padre crea su directorio; sus atributos se convierten en ficheros dentro de él.

struct termo *t = kzalloc(sizeof(*t), GFP_KERNEL);

/* inicializa con el ktype y lo cuelga bajo /sys/kernel/termo0 */
kobject_init_and_add(&t->kobj, &termo_ktype, kernel_kobj, "termo0");
kobject_uevent(&t->kobj, KOBJ_ADD);   /* avisa a userspace del alta */

/* ...mas tarde, al destruir: no kfree directo, sino soltar la referencia */
kobject_put(&t->kobj);   /* en cero, dispara termo_release y libera la struct */
# el kobject se ve como un directorio, y su atributo como un fichero
ls -l /sys/kernel/termo0/
cat  /sys/kernel/termo0/milicelsius
flowchart TD
KOBJ[kernel_kobj raiz sysfs] --> T[termo0 directorio del kobject]
T --> A[milicelsius fichero de atributo]
KSET[kset termos] --> T
KSET -. aporta uevent_ops .-> T
ℹ️
Casi nunca usarás kobject a mano

Este ejemplo es didáctico. En la práctica rara vez creas un kobject desnudo: struct device ya incrusta uno, y registrar un dispositivo con device_add te da directorio en sysfs, conteo de referencias y uevents sin tocar el kobject directamente. El valor de conocerlo es entender qué ocurre por debajo, no reimplementarlo.

El kobject es el átomo del que están hechos todos los objetos del kernel

Da un paso atrás y contempla lo que acabas de ver, porque es una de las decisiones de diseño más fértiles de todo el kernel. Cuatro problemas que aparecen una y otra vez —cómo nombrar un objeto, cómo saber cuándo es seguro liberarlo, cómo situarlo en una jerarquía y cómo mostrarlo a userspace— fueron resueltos una sola vez, en una estructura de treinta y tantos bytes, y luego incrustados en todo lo demás. Un struct device es un kobject con contexto de hardware; un struct kset es un kobject que agrupa; una entrada de /sys es la sombra de un kobject. Esto no es reutilización de código sino algo más profundo: es la identificación del átomo correcto. Igual que en química toda la diversidad de la materia se explica por combinaciones de unos pocos tipos de átomo, aquí toda la diversidad de objetos del kernel —dispositivos, drivers, buses, colas, dominios de energía— se explica por el mismo núcleo de identidad, conteo y jerarquía. Y fíjate en la elegancia de acoplar el conteo de referencias con la jerarquía: como el kobject sabe quién es su padre y cuántos lo referencian, el kernel puede garantizar que un directorio de sysfs nunca desaparece mientras un proceso lo tiene abierto, sin que ningún driver escriba una sola línea de esa lógica. Cuando interiorices que kref no es un contador cualquiera sino el mecanismo que hace componibles a los objetos del kernel —porque permite que muchos dueños compartan uno sin acordar entre ellos cuándo liberarlo—, entenderás por qué el conteo de referencias es, junto con el bloqueo, uno de los dos pilares sobre los que se sostiene la programación concurrente de sistemas.

⚔️ Fabrica y disecciona un kobject
  1. Escribe un módulo que cree una struct con kobject incrustado bajo /sys/kernel, con un atributo de solo lectura, y compruébalo con cat.
  2. Añade una traza en la función release y demuestra, con kobject_get y kobject_put, que solo se ejecuta cuando el contador llega a cero.
  3. Explica por qué container_of es imprescindible y qué pasaría si liberaras el kobject con kfree(kobj) en lugar de liberar el contenedor.
  4. Crea un kset con kset_create_and_add y añade dos objetos; observa cómo aparecen agrupados bajo su directorio en /sys.
  5. Ejecuta udevadm monitor mientras cargas el módulo y localiza el evento KOBJ_ADD que emite kobject_uevent.