• [linux kernel/driver] file operations struct(PARCHE by NvK)

 #437329  por NvK
 01 Mar 2014, 13:45
Hoy vine con un pequeño parche de la estructura file_operations, la cual demuestra el correcto
uso de la escritura desde el espacio del kernel al de usuario.

La estructura file_operations contiene los punteros a funciones de dispositivos,
en éste ejemplo las funciones necesarias para un dispositvo son definidas como:
struct file_operations FOps=
{
	.read= funcion_de_lectura, // al espacio de usuario
	.write= funcion_de_escritura, // desde el espacio de usuario
	.open= funcion_de_apertura,
	.release= funcion_al_liberar // al quitar un dispositivo.
}
Recordemos que la filosofía de linux es "todo es un archivo", claro que existe el dichoso espacio
de usuario y kernel, pero se puede mantener una relación más "fiable" con distintos tipos de operaciones...
Por lo tanto ésta estructura enrealidad representa el concepto de dispositivos(como lo son los de caracter).
Y no "operaciones de archivo" como puede parecer.

Dispositivos de characteres:
Utiles para la interacción entre el espacio de usuario y el kernel, éstos están situados en /dev y contienen
un número mayor y menor.
Numero mayor: Utilizado por el kernel para relacionar el dispositivo.
Numero menor: Útilizado por el propio dispositivo para diferenciar sobre que dispositivo se está operando.
Éstos son identificados por el caracter "c" mientras que los dispositivos de bloque son representados por "b".
Código del driver:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/kmod.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <asm/system.h>
#include <asm/uaccess.h>

/* Necesarias para la estructura de I/O en dispositivos */
static signed int device_open(struct inode *inode, struct file *filp);
static signed int device_release(struct inode *inode, struct file *filp);
ssize_t device_read(struct file *filp, char *buffer, size_t buff_len, loff_t *offset);
ssize_t device_write(struct file *filp, char *buf, size_t count, loff_t *f_pos);

static const unsigned int DEVICE_MAYOR_NUM __section(.init.rodata)= 60;
/* sin localizar en la seccion .rodata, por que hay peligro de polucion (solo en este caso) */
static const char *REGISTER_DEVICE = "NvK_Dev";
static struct class *cl;

static char *device_buff; // sin inicializar, no hay peligro de polucion
static int register_dev;
static dev_t dev; // device number
static struct cdev c_dev; // character device struct
volatile int bytes_buffer;

static struct file_operations fops =
{
  .read 	= device_read,
  .write 	= device_write,
  .open 	= device_open,
  .release 	= device_release
};

static signed int device_open(struct inode *inode, struct file *filp)
{
  if( 0x0000==(try_module_get(THIS_MODULE)) ){ // Incrementamos el registro de uso
    printk(KERN_INFO "[-] Error al actualizar el driver.\n");
    module_put(THIS_MODULE); // restauro el valor si se producen errores
  } return 0x0;
};

static char *Message= "file_operations PARCHEADA!\n";
ssize_t device_read(struct file *filp, char *buffer, size_t buff_len, loff_t *offset)
{
	__asm__("xor %%eax, %%eax"::"a"(bytes_buffer)); 
  
  copy_to_user((char *)buffer, Message, strlen(Message));
  bytes_buffer= strlen(buffer);
  
  /**
  	acomodamos la posición del offset dependiendo de la longitud de la palabra
  	de otro modo se imprimiran los caracteres sin cesar
  **/
  if(*offset<bytes_buffer){
    *offset+= bytes_buffer; 
    return *offset;
  }
  else{
  	return 0;
  }
}

ssize_t device_write(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
  //char *buff_data;
  //printk(KERN_INFO "escribiendo dispositivo");
  
  //buff_data= buf+count-0x1;
  //copy_from_user(device_buff, buff_data, 0x1);
  __asm__("nop;"); // Sin efecto
  return 0x1;
}

static signed int device_release(struct inode *inode, struct file *filp)
{
  // Decrementamos la cuenta de uso  y restauramos su valor original
  module_put(THIS_MODULE);
  return 0x0;
}

static int __section(.init.text) __cold notrace INIT_KERNEL(void)
{
  printk(KERN_INFO "Cargando driver... \n");
  
  alloc_chrdev_region(&dev, 0, 1, REGISTER_DEVICE);
	cl= class_create(THIS_MODULE, REGISTER_DEVICE);
	if(cl!=NULL)
	{
		device_create(cl, NULL, dev, NULL, REGISTER_DEVICE);
		cdev_init(&c_dev, &fops);
		cdev_add(&c_dev, dev, 1);
		printk(KERN_ALERT "[+] Driver cargado correctamente! \n");
	}
	else
	{
		unregister_chrdev_region(dev, 0x1);
	}
  
	printk(KERN_INFO "[+] Modulo cargado correctamente!\n");
  
  
  return 0x0;
}

static void __section(.exit.data) EXIT_KERNEL(void)
{
  printk(KERN_INFO "[!] Driver desinstalado correctamente. \n");
  
  /* Liberamos el numero mayor del dispositivo */
  unregister_chrdev(DEVICE_MAYOR_NUM, REGISTER_DEVICE);
  //if (device_buff) kfree(device_buff);
  
  cdev_del(&c_dev);
  device_destroy(cl, dev);
  class_destroy(cl);
  unregister_chrdev_region(dev, 0x1);
}

module_init(INIT_KERNEL);
module_exit(EXIT_KERNEL);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("NvK");
Imagen

Algo que tener en cuenta:
Si bien permiten la escritura de información desde y para el usuario, son limitados en tamaño.
Por ejemplo un dispositivo de caracteres normalmente puede almacenar el tamaño de una pagina de memoria,
sirviendo nada más que para producir algún tipo de información.
Un claro ejemplo son los IOctl que sirven para "mantener" una serie de comandos(utilizando mecanismos de sockets),
a menudo son utilizados por dispositivos de bloque(binarios) para servir algún tipo de información al usuario.

Imagen

Imagen

Aca te dejo un artículo que podría servirte: [ Debe registrarse para ver este enlace ]

NOTA: Alternativamente podriamos usar mknod(claro que el código cambiaria notablemente), pero es un
metodo algo obsoleto, ya que se puede producir una polucion en el registro de numeros(Mayor) si éste
ya existe.