wandres.dev
CHAR DEVICES · file_operations, read/write

Implementar read, write y open

Dar vida al char device: escribir las funciones open, read, write y release que responden cuando un programa usa tu dispositivo, moviendo datos con seguridad.

⏱ 13 min

Registrar el dispositivo (nivel 13.1) crea el enchufe; ahora hay que darle vida escribiendo las operaciones. Aquí se unen todas las piezas: la tabla de funciones, la frontera con el usuario, el manejo de errores. Tu primer driver que de verdad hace algo.

🎯 Al terminar esta lección sabrás
  • Implementar open y release.
  • Implementar read con copy_to_user.
  • Implementar write con copy_from_user.
  • Un char device completo y funcional.

open y release

Se llaman cuando un programa abre y cierra tu dispositivo. Aquí preparas y liberas por-apertura:

static int mi_open(struct inode *inode, struct file *file)
{
	pr_info("dispositivo abierto por %s\n", current->comm);
	return 0;
}

static int mi_release(struct inode *inode, struct file *file)
{
	pr_info("dispositivo cerrado\n");
	return 0;
}

read: enviar datos al usuario

read copia datos del kernel al buffer del usuario. Fíjate en copy_to_user (nivel 12) y en cómo se respeta el desplazamiento y el tamaño:

static char mensaje[256] = "hola desde el kernel\n";

static ssize_t mi_read(struct file *file, char __user *buf,
                       size_t len, loff_t *offset)
{
	size_t disponible = strlen(mensaje);

	if (*offset >= disponible)
		return 0;                 /* fin: no queda nada por leer */
	if (len > disponible - *offset)
		len = disponible - *offset;

	if (copy_to_user(buf, mensaje + *offset, len))
		return -EFAULT;           /* dirección de usuario inválida */

	*offset += len;
	return len;                       /* nº de bytes leídos */
}

Devolver el número de bytes leídos (y 0 al final) es cómo cat sabe cuándo parar.

write: recibir datos del usuario

static ssize_t mi_write(struct file *file, const char __user *buf,
                        size_t len, loff_t *offset)
{
	if (len > sizeof(mensaje) - 1)
		len = sizeof(mensaje) - 1;    /* valida el tamaño (nivel 12) */

	if (copy_from_user(mensaje, buf, len))
		return -EFAULT;

	mensaje[len] = '\0';
	return len;                            /* nº de bytes escritos */
}

Probarlo desde userspace

Con el dispositivo cargado y el nodo /dev/midispositivo creado, lo usas como cualquier archivo:

cat /dev/midispositivo          # dispara tu mi_read → "hola desde el kernel"
echo "nuevo texto" > /dev/midispositivo   # dispara tu mi_write
cat /dev/midispositivo          # ahora lee "nuevo texto"
Acabas de conectar los dos mundos

Mira lo que has construido: un programa de usuario cualquiera (cat, echo, o uno tuyo en Python) habla con tu código de kernel usando las operaciones de archivo más básicas, y tu código —en anillo 0, con privilegio total— responde moviendo datos de forma segura a través de la frontera. Has unido todo el track hasta aquí: la tabla de file_operations (polimorfismo, nivel 13.1), copy_to/from_user (la frontera, nivel 12), los códigos de error (nivel 11), el ciclo de vida del módulo (nivel 7). Este patrón —un char device con sus operaciones— es la columna vertebral de una gran parte de los drivers del kernel. Lo que viene (concurrencia, memoria, interrupciones) hará tu driver robusto y capaz de hablar con hardware real, pero la estructura fundamental de “un programa usa mi dispositivo como un archivo y mi código responde” ya la dominas. Ese es un hito real.

⚔️ Un driver funcional de principio a fin
  1. Completa tu char device del nivel 13.1 con open, release, read y write.
  2. Usa copy_to_user en read y copy_from_user en write, validando tamaños.
  3. Crea /dev/midispositivo y pruébalo con cat y echo.
  4. Escribe un pequeño programa en C (userspace) que abra el dispositivo y lea de él.
  5. Provoca un -EFAULT pasando un buffer inválido y obsérvalo.