wandres.dev
CHAR DEVICES · file_operations, read/write

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.

⏱ 13 min

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.

🎯 Al terminar esta lección sabrás
  • 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,
};
file_operations: polimorfismo del kernel en acción

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ú.

⚔️ Crea tu dispositivo
  1. Escribe un módulo que registre un char device con alloc_chrdev_region y cdev_add.
  2. Define una file_operations con .owner = THIS_MODULE (las funciones, en el nivel 13.2).
  3. Añade la limpieza simétrica en exit.
  4. Tras cargarlo, crea el nodo con mknod (o usa una clase de device para /dev automático) y compruébalo.