next up previous contents index
Next: Escribiendo código reentrante Up: Módulos para dispositivos de Previous: Averiguando el numero menor   Índice General   Índice de Materias


Lectura y escritura de dispositivos.

Las funciones de escritura y lectura del dispositivo deberían ser fáciles de implementar ahora que ya se han visto ejemplos de open y release.

No obstante, hemos de recordar que nuestro módulo no trabaja sobre ningún hardware de cinta magnética, solamente simula que lo hace. Es decir los datos que queramos leer o escribir mediante nuestra implementación de read y write no van a estar almacenados en ninguna cinta ``real''.

Para simular la permanencia de los datos en el dispositivo entre diferentes llamadas de escritura-lectura, vamos a usar un pequeño truco. Declararemos una nueva variable global, que llamaremos cintas (ya que tendremos un número de cintas igual a NUM_MAX_CINTAS). En ella vamos a almacenar los datos que se vaya escribiendo con write, y será de ella de donde vamos a leer datos con read. Para nuestro sencillo módulo, cintas no será más que un puntero a caracter.

El problema de usar este truco es que si desmontamos el módulo, la variable global desaparece de la memoria, por lo que perderemos los datos ``almacenados'' en nuestras ``cintas''. Es un mal menor que vamos a pagar a cambio de la simplicidad de nuestro código.

Una vez tenemos definido nuestro medio de ``almacenamiento'', podemos empezar a implementar read y write.

/*  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 
/* capacidad de una cinta en caracteres */
#define TAM_CINTA 150

int mpcinta_open(struct inode *pinode, struct file *pfile);
int mpcinta_release(struct inode *pinode, struct file *pfile);
loff_t mpcinta_lseek (struct file *pfile, loff_t f_pos, int i);
ssize_t mpcinta_read (struct file *pfile, char *buf, size_t tam_buf, loff_t
                      *f_pos);
ssize_t mpcinta_write (struct file *pfile, const char *buf, size_t tam_buf, loff_t
                       *f_pos);
/*  mpcinta.c */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/malloc.h>
#include <asm/uaccess.h>
#include "mpcinta.h"

unsigned int mpcinta_major=MPCINTA_MAYOR;
struct file_operations mpcinta_fops = {
        mpcinta_lseek,       /* lseek */
        mpcinta_read,       /* read */
        mpcinta_write,       /* 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 */
};              

char *cintas; /* puntero a nuestras primera cinta */

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;

        /* reservamos espacio para todas las cintas que tengamos */
        cintas = kmalloc(NUM_MAX_CINTAS * TAM_CINTA, GFP_KERNEL);
        if (!cintas) {
                result = -ENOMEM;
                goto fail_malloc;
        }
        /* inicializamos las cintas a 0 */
        memset(cintas, 0, NUM_MAX_CINTAS * TAM_CINTA);
        

        /* mensaje y salimos */
        printk( KERN_INFO "mpcinta> (init_module) cargado satisfactoriamente\n");
        return 0;

 fail_malloc: unregister_chrdev(mpcinta_major, "mpcinta");
        printk( KERN_INFO "mpcinta> (init_module) Error, no encontré memoria\n");
        return result;
}

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;
}

loff_t mpcinta_lseek (struct file *pfile, loff_t f_pos, int i) {
  printk(KERN_INFO "mpcinta> (lseek) f_pos=%Ld\n",f_pos);
  if (f_pos>=TAM_CINTA)
    return(-1); /* no vamos a permitir situarnos más lejos del tamaño de 
                                                             la cinta */
  pfile->f_pos=f_pos;
  return(0);
}

ssize_t mpcinta_read (struct file *pfile, char *buf, size_t tam_buf, loff_t
                    *f_pos) {
  unsigned long not_copied;

  int menor= MINOR(pfile->f_dentry->d_inode->i_rdev);
  char *pcintas=cintas; /* hacemos reentrante la función */
  
  if (*f_pos>=TAM_CINTA) {
    printk(KERN_INFO "mpcinta> (read) ERROR, la cinta no es tan grande\n");
    return(-1); /* no vamos a permitir situarnos más lejos del tamaño de 
                                                             la cinta */
  }
  if ((*f_pos)+tam_buf>=TAM_CINTA) { /* si nos pasamos, recortamos el tamaño de
                                        los datos */
    tam_buf=tam_buf-((((*f_pos)+tam_buf)-TAM_CINTA)-1);
    printk(KERN_INFO "mpcinta> (read) WARNING, la cinta no es tan grande,
 mostramos lo que se pueda\n");
  }
  /* traducimos del espacio de direcciones del kernel al de usuario */
  not_copied=__copy_to_user(buf,pcintas+(menor*TAM_CINTA)+(*f_pos),tam_buf);
  if (not_copied>0) {
    printk(KERN_INFO "mpcinta> (read) WARNING, no se escribieron los datos\n");
    return(-EFAULT);
  }
  printk(KERN_INFO "mpcinta> (read) (menor=%d, tam=%d, f_pos=%Ld)\n",menor,
                                                          tam_buf,(*f_pos));
  (*f_pos)+=tam_buf;
  return(tam_buf); 
}

ssize_t mpcinta_write (struct file *pfile, const char *buf, size_t tam_buf, loff_t
                     *f_pos) {
  unsigned long not_copied;
  
  int menor= MINOR(pfile->f_dentry->d_inode->i_rdev);
  char *pcintas=cintas; /* hacemos reentrante la función */
 
  if (*f_pos>=TAM_CINTA) {
    printk(KERN_INFO "mpcinta> (write) ERROR, la cinta no es tan grande\n");
    return(-1); /* no vamos a permitir situarnos más lejos del tamaño de 
                                                              la cinta */
  }
  if ((*f_pos)+tam_buf>=TAM_CINTA) { /* si nos pasamos, recortamos el 
                                                tamaño de los datos */
    tam_buf=tam_buf-((((*f_pos)+tam_buf)-TAM_CINTA)-1);
    printk(KERN_INFO "mpcinta> (write) WARNING, los datos no caben, 
                                           escribimos lo que quepa\n");
  }
  /* traducimos del espacio de direcciones del usuario al del kernel */
  not_copied=__copy_from_user(pcintas+(menor*TAM_CINTA)+(*f_pos),buf,tam_buf);
  if (not_copied>0) {
    printk(KERN_INFO "mpcinta> (write) WARNING, no se escribieron los datos\n");
    return(-EFAULT);
  }
  printk(KERN_INFO "mpcinta> (write) (menor=%d, tam=%d, f_pos=%Ld)\n",menor,
                                                            tam_buf,(*f_pos));
  (*f_pos)+=tam_buf;
  return(tam_buf);
}

Para probar nuestro módulo, podemos utilizar el comando dd, cat..., copiando información a nuestros dispositivos, incluso intercambiando información entre ellos. Un seguimiento de las actividades del módulo (observando /var/log/messages) nos será de gran ayuda para comprender su funcionamiento.

No obstante, este código no está en absoluto libre de errores, hasta ahora no hemos tenido en cuenta que el código de nuestro módulo puede ser utilizado concurrentemente por varios procesos.

Supongamos que el proceso A intenta escribir el un dispositivo controlado por nuestro módulo, invoca la llamada al sistema write para escribir unos cuantos datos. Mientras tanto el proceso B pretende hacer lo mismo. A mitad de que el proceso A está escribiendo datos en cintas, el planificador de tiempos del kernel le hecha de la cpu, le toca el turno al proceso B, que sobreescribe los datos de A, cuando todavía no a terminado de escribir, se le devuelve el control al proceso A, que continua escribiendo donde lo dejó. Al final, los datos almacenados en nuestro dispositivo no son ni lo que A quería ni lo que B quería.

Este comportamiento tan desagradable es un problema común en los programas concurrentes, y debe ser evitado. Las funciones que modifiquen variables globales de nuestro programa han de ser reentrantes .



Subsecciones
next up previous contents index
Next: Escribiendo código reentrante Up: Módulos para dispositivos de Previous: Averiguando el numero menor   Índice General   Índice de Materias
Alberto Cortés 2001-03-26