En el marco de silenciar el cooler de una Raspberry Pi, vengo capacitándome tal como cuento en el curso anterior de linux "avanzado". Este, Linux Device Drivers , del mismo autor es de... drivers.
Los drivers en forma de módulos son la manera de no tener que estar recompilando y rebooteando al sistema operativo ante agregados y cambios.
El curso me ha servido mucho para comprender la arquitectura, estaba
teniendo una idea equivocada del alcance de las responsabilidades del
driver, cuando lo haga concretamente entraré en más detalles.
La parte de gestión está un poco repetida respecto al curso anterior, pero se vé con mayor profundidad, por ejemplo la parte de configuración para modprobe, el blacklisting, que en /sys/module/$MODULE/parameters están los parametros y serán legibles o incluso escribibles según los permisos de module_param().
Respecto a escribir módulos muy básicos el curso está ok, pero hay muchos temas muy interesantes que sólo menciona de modo muy superficial, por ejemplo:
- debugfs
- tracing
- crash
Amerita cada uno de estos temas un buen tiempo, hoy no va a ser...
Me hubiera gustado que en el ejercicio de char devices, que es un dispositivo donde guardar un string, como parte del enunciado hubiera pedido que el tamaño del buffer se determinara en el momento de la carga en lugar del momento de compilación, pero eso hubiese significado usar memoria dinámica.
Tambien que se pudiera lidiar con >>, esto es, poder agregar información, el comportamiento actual es:
$ echo "hola" > /dev/charDevice
$ cat /dev/charDevice
hola
$ echo "chau" >> /dev/charDevice
$ cat /dev/charDevice
chau
Y lo que deseo es:
$ echo "hola" > /dev/charDevice
$ cat /dev/charDevice
hola
$ echo "chau" >> /dev/charDevice
$ cat /dev/charDevice
hola
chau
Hagamos mi deseo realidad
La parte del append tiene dos aspectos, detectarlo y lidiar correctamente con el buffer.
Tras una caótica exploración de las estructuras en uso (léase, no recuerdo que hice) parecida a pensar que en el momento del write debería haber algún indicio de si estamos en el contexto de un > o un >>, hallé que file provee unos flags donde dice el modo de apertura. el campo se llama f_flags. y está en la struct file en fs.h y
con un if (filep->f_flags &O_APPEND) bien posicionado, ya podemos elegir el comportamiento.
Este es el código base:
ssize_t charDevice_write( struct file *filep,
const char *buf,
size_t nbytes, loff_t *ppos){
int failed, transferred;
if (debug)
printk("WRITE : %d flags: %x\n",
(int)nbytes, filep->f_flags);
if (!nbytes)
return 0;
if (nbytes >= BUFFER_SIZE)
nbytes = BUFFER_SIZE-1;
if (filep->f_flags & O_APPEND)
printk("WRITE : Appending %ld bytes\n",nbytes);
else
printk("WRITE : Saving %ld bytes\n",nbytes);
failed = copy_from_user(char_buffer, buf, nbytes);
transferred = nbytes - failed;
if (!transferred)
return -EFAULT;
char_buffer[transferred] = '\0';
buffer_length = transferred;
return transferred;
}
Este es el código intermedio ya funcionando:
ssize_t charDevice_write( struct file *filep,
const char *buf,
size_t nbytes, loff_t *ppos){
int failed, transferred;
if (debug)
printk("WRITE : %d flags: %x\n",
(int)nbytes, filep->f_flags);
if (!nbytes)
return 0;
if (filep->f_flags & O_APPEND) {
printk("WRITE : Appending %ld bytes\n",nbytes);
if (nbytes >= BUFFER_SIZE - buffer_length)
nbytes = BUFFER_SIZE - buffer_length -1;
failed = copy_from_user(
char_buffer + buffer_length, buf, nbytes);
transferred = nbytes - failed;
if (!transferred)
return -EFAULT;
buffer_length += transferred;
char_buffer[ buffer_length] = '\0';
} else {
printk("WRITE : Saving %ld bytes\n",nbytes);
if (nbytes >= BUFFER_SIZE )
nbytes = BUFFER_SIZE-1;
failed = copy_from_user(char_buffer, buf, nbytes);
transferred = nbytes - failed;
if (!transferred)
return -EFAULT;
buffer_length = transferred;
char_buffer[transferred] = '\0';
}
return transferred;
}
Este es el código final refactorizado, notando que todo se trata de buffer_length:
ssize_t charDevice_write( struct file *filep,
const char *buf,
size_t nbytes, loff_t *ppos){
int failed, transferred;
if (debug)
printk("WRITE : %d flags: %x\n",
(int)nbytes, filep->f_flags);
if (!nbytes)
return 0;
if (filep->f_flags & O_APPEND) {
printk("WRITE : Appending %ld bytes\n",nbytes);
} else {
printk("WRITE : Saving %ld bytes\n",nbytes);
buffer_length = 0;
}
if (nbytes >= BUFFER_SIZE - buffer_length)
nbytes = BUFFER_SIZE - buffer_length -1;
failed = copy_from_user(
char_buffer + buffer_length, buf, nbytes);
transferred = nbytes - failed;
if (!transferred)
return -EFAULT;
buffer_length += transferred;
char_buffer[ buffer_length] = '\0';
return transferred;
}
Este es mi script de prueba de mi script de prueba:
sudo rmmod charDevice
sudo insmod charDevice.ko debug=1 major=197
echo "one" > charDevice2
cat charDevice2
echo "two" >> charDevice2
cat charDevice2
dmesg | tail
Y esta es la traza de la ejecución:
./go.sh
[sudo] password for carlos:
one
one
two
[971144.117183] WRITE : 4 flags: 8001
[971144.117187] WRITE : Saving 4 bytes
[971144.119726] READ : 131072 bytes ppos : 0
[971144.119958] READ : 131072 bytes ppos : 4
[971144.119959] READ : 1
[971144.120169] WRITE : 4 flags: 8401
[971144.120171] WRITE : Appending 4 bytes
[971144.120987] READ : 131072 bytes ppos : 0
[971144.125827] READ : 131072 bytes ppos : 8
[971144.125831] READ : 1
El código completo en github.
Conclusiones
El curso está bien, empieza mejor de lo que termina, en el tercer tema que es network devices como que pierde el foco, pues explica algo muy específico, quizás ya estaba cansado y la limitación fué mía.