Char devices: registrar un dispositivo
El primer driver de verdad: crear un dispositivo de carácter en /dev con el que los programas hablan. Números major/minor, struct cdev y file_operations.
Un char device es la forma clásica de que un driver exponga funcionalidad: aparece como un archivo en /dev, y los programas lo abren, leen y escriben como cualquier archivo. Crear uno es tu primer driver de verdad, y la puerta a entender el device model del kernel.
- Qué es un char device y su lugar en /dev.
- Números major y minor.
- Registrar un dispositivo con
cdev. - La struct
file_operations.
El char device y /dev
Un dispositivo de carácter transfiere datos como un flujo de bytes (un teclado, un puerto serie, /dev/null). Aparece en /dev con un número major (qué driver lo maneja) y minor (qué instancia concreta):
ls -l /dev/null
# crw-rw-rw- 1 root root 1, 3 ... → 'c' = char, major 1, minor 3
Cuando un programa abre /dev/tudispositivo, el kernel usa el major para encontrar tu driver y le pasa las operaciones.
Registrar el dispositivo
El flujo moderno: pedir un número de dispositivo, inicializar un cdev con tus operaciones, y añadirlo:
#include <linux/cdev.h>
#include <linux/fs.h>
static dev_t numero; /* major+minor empaquetados */
static struct cdev mi_cdev;
static int __init driver_init(void)
{
int ret = alloc_chrdev_region(&numero, 0, 1, "midispositivo");
if (ret)
return ret;
cdev_init(&mi_cdev, &mis_fops); /* conecta las operaciones */
ret = cdev_add(&mi_cdev, numero, 1);
if (ret)
goto liberar_region; /* patrón goto, nivel 11 */
return 0;
liberar_region:
unregister_chrdev_region(numero, 1);
return ret;
}
En exit deshaces esto en orden inverso: cdev_del y unregister_chrdev_region (la simetría del nivel 7.1).
file_operations: la tabla de funciones
El corazón de un char device es una struct file_operations: una tabla de punteros a función (el patrón del nivel 10 de C23) que dice qué hacer ante cada operación de archivo:
static struct file_operations mis_fops = {
.owner = THIS_MODULE,
.open = mi_open,
.release = mi_release, /* close */
.read = mi_read,
.write = mi_write,
.unlocked_ioctl = mi_ioctl,
};
Detente en esto, porque es el corazón del diseño de Unix. Cuando un programa hace read() sobre tu /dev/midispositivo, el kernel no sabe ni le importa qué es tu dispositivo: mira su file_operations, encuentra tu puntero .read, y lo llama. Tu driver, un teclado, un socket, un archivo normal — todos exponen la misma interfaz (file_operations), y el VFS (nivel 14) los trata idénticamente. Esto es exactamente el polimorfismo de la orientación a objetos, implementado en C con una tabla de punteros a función (nivel 10 de C23): la struct es la “vtable”, tu driver es la “clase”, y read()/write() son “métodos virtuales”. Por eso “todo es un archivo” funciona: cualquier cosa que rellene una file_operations se comporta como un archivo. Cuando rellenas esa struct, estás enchufando tu código en la maquinaria universal de E/S del kernel. Es una de las abstracciones más poderosas jamás diseñadas, y ahora la usas tú.
- Escribe un módulo que registre un char device con
alloc_chrdev_regionycdev_add. - Define una
file_operationscon.owner = THIS_MODULE(las funciones, en el nivel 13.2). - Añade la limpieza simétrica en
exit. - Tras cargarlo, crea el nodo con
mknod(o usa una clase de device para/devautomático) y compruébalo.