Como ya sabemos un determinado driver puede atender a varios dispositivos, todos los que tengan el mismo número mayor que el driver. Si pudiésemos conocer el número menor del dispositivo que provocó la llamada podremos hacer código condicional al dispositivo dentro de nuestro módulo.
El número menor que vemos con "ls -l /dev/mi_cinta"
está almacenado en el
inodo
de dicho fichero. Cuando se invoca una operación sobre ese fichero,
al modulo correspondiente se le pasa un puntero al inodo, siendo así posible la
identificación del fichero que provocó la llamada.
El campo i_dev del inodo contiene la información sobre el número menor y el número mayor de dicho inodo. EL tipo de datos de i_dev es kdev_t . Para poder extraer de forma sencilla estos datos, en linux/kdev_t.h4.14 se definen un par de macros que nos pueden ser de utilidad:
Nótese como gracias a esto podemos crear diferentes ``dispositivos lógicos'' (ficheros en /dev/) que sean en realidad diferentes formas de acceder a un mismo ``dispositivo físico''.
A continuación se presenta una posible implementación para nuestro módulo, este código ya implementa las funciones open y release:
/* mpcinta.c */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include "mpcinta.h" unsigned int mpcinta_major=MPCINTA_MAYOR; struct file_operations mpcinta_fops = { NULL, /* lseek */ NULL, /* read */ NULL, /* write */ NULL, /* readdir */ NULL, /* poll */ NULL, /* ioctl */ NULL, /* mmap */ mpcinta_open, /* open */ NULL, /* flush */ mpcinta_release, /* release */ NULL, /* fsync */ NULL, /* fasync */ NULL, /* check_media_change */ NULL, /* revalidate */ NULL, /* lock */ }; int init_module(void) { int result; EXPORT_NO_SYMBOLS; /* reserva dinamica del número mayor del módulo */ result=register_chrdev(mpcinta_major, "mpcinta", &mpcinta_fops); if (result < 0) { printk(KERN_WARNING "mpcinta> (init_module) no se pudo obtener el major %d\n",mpcinta_major); return result; } if (mpcinta_major == 0) mpcinta_major = result; /* mensaje y salimos */ printk( KERN_INFO "mpcinta> (init_module) cargado satisfactoriamente\n"); return 0; } void cleanup_module(void) { int result; result = unregister_chrdev(mpcinta_major, "mpcinta"); printk( KERN_INFO "mpcinta> (cleanup_module) descargado sin problemas\n"); } int mpcinta_open(struct inode *pinode, struct file *pfile) { int menor= MINOR(pinode->i_rdev); printk(KERN_INFO "mpcinta> (open) menor= %d\n",menor); if (menor>=NUM_MAX_CINTAS) { printk(KERN_INFO "mpcinta> (open) el device con minor number %i no existe\n",menor); return(-ENODEV); } MOD_INC_USE_COUNT; return 0; } int mpcinta_release(struct inode *pinode, struct file *pfile) { int menor= MINOR(pinode->i_rdev); printk(KERN_INFO "mpcinta> (release) menor= %d\n",menor); MOD_DEC_USE_COUNT; return 0; }
MOD_INC_USE_COUNT es una macro definida en linux/module.h, que ayuda al programador de módulos a manejar de forma sencilla la cuenta de uso del módulo. Su uso en open es fundamental si se quiere evitar que nos desmonten el módulo mientras está siendo usado. Por supuesto, release tendrá que decrementar dicha cuenta ( MOD_DEC_USE_COUNT ) si queremos que una vez que el módulo no esté siendo utilizado se pueda eliminar de la memoria satisfactoriamente.
Al haber incluido las dos funciones que manejan la apertura y el cierre del dispositivo (mpcinta_open y mpcinta_release), tenemos que declarar sus cabeceras, por eso hemos creado también el fichero mpcinta.h en el que hemos incluido también algunas definiciones de utilidad para el módulo.
/* mpcinta.h */ /* el comportamiento por defecto es la asignación dinámica */ #define MPCINTA_MAYOR 0 /* el número máximo de dispositivos de cinta que vamos a tener */ #define NUM_MAX_CINTAS 2 int mpcinta_open(struct inode *pinode, struct file *pfile); int mpcinta_release(struct inode *pinode, struct file *pfile);
Ya estamos en condiciones de probar nuestro módulo, para ello vamos a construir un sencillo programa en c que nos va a servir para invocar las llamadas al sistema que hacen que se ejecuten mpcinta_open() y mpcinta_release():
/* prueba.c */ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main() { int file0, file1, result; /* probaremos los dos dispositivos */ file0=open("/dev/mi_cinta0",O_RDONLY); if (file0==-1) return(-1); file1=open("/dev/mi_cinta1",O_RDONLY); if (file1==-1) return(-1); sleep(10); /* dormimos un rato para ver si el módulo... */ result=close(file0); /* ...aparece como usado */ if (file==-1) return(-1); result=close(file1); if (file==-1) return(-1); return(result); }
Con este sencillo programa podremos ver si nuestro módulo funciona bien, este es la manera de comprobarlo:
make
)
gcc -o prueba prueba.c
), seria conveniente incluir esto en el Makefile para
ahorrarnos tiempo de ahora en adelante.
xterm -bg black -fg red -e tail -f /var/log/messages
)
./carga_modulo
)
./prueba
)
cat /proc/devices
. Incluso podemos tratar de desmontar el módulo en este periodo,
al estar este en uso, no nos será posible (rmmod mpcinta
).
rmmod mpcinta
).
El resultado en /var/messages tendría que ser algo parecido a esto:
Feb 28 18:20:30 shire kernel: mpcinta> (init_module) cargado satisfactoriamente Feb 28 18:20:33 shire kernel: mpcinta> (open) menor= 0 Feb 28 18:20:33 shire kernel: mpcinta> (open) menor= 1 Feb 28 18:20:33 shire kernel: mpcinta> (release) menor= 0 Feb 28 18:20:33 shire kernel: mpcinta> (release) menor= 1 Feb 28 18:20:36 shire kernel: mpcinta> (cleanup_module) descargado sin problemas
Todavía podemos ir más allá en nuestras pruebas, podemos intentar hacer:
dd if=/dev/mi_cinta0 of=/tmp/salida ibs=1 count=10
Veremos como dd
falla al ejecutarse sobre nuestro dispositivo, sin
embargo el dispositivo ha sido abierto y cerrado satisfactoriamente, como se
puede ver en /var/log/messages, ¿Cual ha sido el problema?4.15.