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.
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.
- Entender
struct kobjectcomo el ladrillo común y sukrefde conteo de referencias. - Ver cómo el
kobj_typedefinereleaseysysfs_ops, y por quéreleaselibera el objeto contenedor. - Comprender
struct ksetcomo colección de objetos afines y punto de anclaje de los uevents. - Ver cómo la jerarquía de
kobjectgenera, 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
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.
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.
- Escribe un módulo que cree una
structconkobjectincrustado bajo/sys/kernel, con un atributo de solo lectura, y compruébalo concat. - Añade una traza en la función
releasey demuestra, conkobject_getykobject_put, que solo se ejecuta cuando el contador llega a cero. - Explica por qué
container_ofes imprescindible y qué pasaría si liberaras elkobjectconkfree(kobj)en lugar de liberar el contenedor. - Crea un
ksetconkset_create_and_addy añade dos objetos; observa cómo aparecen agrupados bajo su directorio en/sys. - Ejecuta
udevadm monitormientras cargas el módulo y localiza el eventoKOBJ_ADDque emitekobject_uevent.