Linux Drivers 2010

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 69

Desarrollo de drivers en Linux

Introducción
• ¿Qué es un Driver de acceso a dispositivo?
¾ Pieza de software, normalmente perteneciente al sistema,
para el acceso a dispositivos físicos.

Driver de
dispositivo

Driver de Programas de
dispositivo aplicación

Driver de
dispositivo
Introducción

• ¿Qué contiene un driver?


¾Funciones de entrada salida
Direccionamiento Selección de un dispositivo

Sincronización Inicio de transferencia

Transferencia Método de transferencia


Drivers en Linux
• Cada controlador hardware dispone de
¾ registros de control,
¾ registros de estado y
¾ registros de datos
• Utilizados para inicializar el dispositivo, hacer diagnósticos,
intercambiar datos, etc.
• No se pone el código para manejar un dispositivo dentro de cada
aplicación que lo necesite, dicho código forma parte del núcleo
de sistema operativo.
Drivers en Linux
• ¿Qué hace un driver?
¾Abstracción en el manejo de los dispositivos
físicos.
¾Los dispositivos son tratados como archivos (se
abren, cierran, leen y escriben usando las mismas
llamadas al sistema que para manipular
archivos).
¾Cada dispositivo es representado por un archivo
de dispositivo (device file), por ejemplo, el primer
disco duro del sistema es representado por /dev/
hda.
Drivers en Linux

• Cada archivo de dispositivo tiene asociado un


número que lo identifica: el major number.
¾ /dev/ fd0 el major_number 2, /dev/ lp0 el 6.
• Dispositivos del mismo tipo tienen asociado el
mismo major_number (los diskettes fd0 y fd1 el
2), porque son atendidos por el mismo driver.
• Muestran minor_mumbers diferentes ( 0, 1 ),
indican al driver cuál de los dispositivos que
solicita servicio (cuando un proceso accede a ese
archivo de dispositivo).
Kernel de linux

Programas de usuario
Interfaz de llamadas al sistema (API, del sistema operativo)

Gestión de Gestión de Sistema de Control de Gestión de red


procesos memoria archivos dispositivos

Código Gestor de Sistemas de Dispositivos Subsistema de


dependiente memoria ficheros de carácter red
de la
arquitectura Dispositivos Drivers de
de bloque tarjetas
¿por qué un driver?
¿ y por que no acceder directamente desde un programa
de usuario?
• VENTAJAS
¾ Empleo de librerías externas.
¾ Mejora en las técnicas de depuración.
¾ Mayor control sobre los posibles errores de ejecución.
¾ Asignación dinámica de la memoria (reutilizable).
¾ Concurrencia en el acceso a los dispositivos.
• DESVENTAJAS
¾ Interrupciones no permitidas.
¾ Necesidad de bloquear páginas de memoria.
¾ Acceso directo a memoria y puertos de I/O
restringido (superusuario).
¾ Tiempos de respuesta lentos.
Características de los Drivers

• Comunicación directa con programas de usuario.


• Accesibles a través de archivos especiales identificados
unívocamente (minor, major).
• Se realiza la transferencia de datos entre bloques de
memoria y dispositivos a través de estos archivos.
• Registrados como integrantes del sistema.
• Pertenecientes al espacio del kernel.
• Extienden la funcionalidad del sistema.
• Pueden emplear todos los recursos del sistema.
• Limitados por la rigidez del kernel.
• Desarrollo a nivel “super- usuario”.
• Permiten configuraciones específicas para cada kernel.
• Permanecen en espera hasta ser requeridos.
Drivers en Linux

• Drivers en Linux
¾Cuatro tipos de drivers
De carácter
De bloque
De red
 pseudo-dispositivos
– Usb
– SCSI
– Fire-Wire
¾Normalmente implementados como módulos
Drivers en Linux

• Concurrencia
¾Múltiples versiones del código del driver
ejecutándose simultáneamente
Varios procesos acceden simultáneamente al recurso
Varias CPU que ejecutan simultáneamente el mismo driver
• El código debe ser reentrante
¾Correr en más de un contexto al mismo tiempo
¾Diseño de las estructuras de datos con cuidado
para evitar la corrupción
Drivers en Linux

• Módulos
¾Se cargan y descargan de forma dinámica
Para activar un dispositivo basta con cargar el módulo con el
driver (función insmod)
Para descargar el dirver basta con llamar a la función rmmod
¾El Kernel es capaz de descargar de la memoria un
modulo que no este usándose para liberar
recursos activándolo de forma automática cuando
lo precise.
Drivers en Linux
• Módulos
¾ Emplean únicamente funciones del sistema.
¾ Gran número de funciones y variables globales.
Todos los módulos se ejecutan en el mismo espacio del kernel.
Necesidad de ocultar los datos mediante la palabra static.
¾ Llamados desde el espacio del usuario.
Permite que un proceso ejecutado en modo usuario acceda a
recursos de modo supervisor
Tranferencia desde el espacio de usuario al del kernel mediante
llamadas al sistema e interrupciones.
¾ Ejecutados en el espacio del kernel (Modo
supervisor).
¾ Código ejecutado en forma continua. Es necesario
descargar el módulo de forma explicita (rmmod)
Estructura básica de un módulo

#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");

static int hello_init(void)


{
printk(KERN_ALERT "Hello, world\n");
return 0;
}

static void hello_exit(void)


{
printk(KERN_ALERT "Goodbye, cruel world\n");
}
module_init(hello_init);
module_exit(hello_exit);
Estructura básica de un módulo
• Inicialización de módulo
¾Registro de capacidades del modulo
 static int __init funcion_ini(void){}
 module_init(funcion_ini);
• Finalización
¾Desregistrar recursos registrados.
static void __exit funcion_fin(void){}
module_exit(funcion_fin);
Configuración del módulo

Modos de configuración del módulo


• Mediante parámetros especificados por el usuario en
forma explícita en tiempo de carga.
¾ module_param(nombre, tipo, valorpordefacto);
tipo: int,char,...
modprobe nombredelmodulo nombreparametro=valor
• Mediante parámetros obtenidos por autodetección en
tiempo de inicialización.
• Mediante parámetros obtenidos por autodetección con
posibilidad de modificación manual.
Registro del fichero de dispositivo
• Se utilizan para el intercambio de datos entre aplicaciones del espacio
de usuario y el módulo.
• Se suelen situar en el directorio /dev
• Vienen caracterizados por el tipo (bloque o caracter, y los números
major y minor
• MAJOR
 Utilizado por el kernel.
 Permite asociar a cada dispositivo su driver apropiado.
 Posibilita el registro del driver.
 Asignado en la inicialización del driver.
 Empleado como índice en un arreglo estático de drivers.
• MINOR
 Utilizado por el driver.
 Usual en drivers que controlan varios dispositivos.
 Permite diferenciar entre dispositivos con igual MAJOR.
 Determina el comportamiento del dispositivo.
• HAY QUE REGISTRAR MAJOR Y MINOR AL INICIAR EL MODULO
Drivers de carácter: Funciones para Registro de
los numeros major y minor
The Linux Kernel API:
http://www.fsl.cs.sunysb.edu/kernel-api/index.html

register_chrdev_region — register a range of device numbers


alloc_chrdev_region — register a range of char device numbers
register_chrdev — Register a major number for character devices.
unregister_chrdev_region — return a range of device numbers
cdev_add — add a char device to the system
cdev_del — remove a cdev from the system
cdev_alloc — allocate a cdev structure
cdev_init — initialize a cdev structure
• Se le pasan los numeros de major y minor (0 si
automatico). En el ejemplo:

if (( result = alloc_chrdev_region( &gShortDevNum, 0, 1,


“short” )) < 0 )
{
printk( KERN_WARNING "sample: Unable to allocate
major, err: %d\n", result );
return result;
}
printk( "sample driver allocated major:%d minor:%d\n",
MAJOR( gShortDevNum ), MINOR( gShortDevNum ));
Funcionamiento de los drivers de caracter

• Se accede como un archivo normal.


• El driver asociado al dispositivo es el que implementa ese
proceder.
• El driver implementa las llamadas al sistema open, close,
read y write.
• La consola y el puerto paralelo son dos ejemplos de este
tipo de dispositivos, que pueden ser accedidos a través los
archivos especiales /dev/tty y /dev/lp.
Funcionamiento de los drivers de caracter

Programa de usuario
....
fid=fopen(“/dev/short0”,”rw”);
.....
Llamada al sistema
escrito=write(fid,”hola”,4);

Llamada al sistema

Modulo del Kernel


Funcion Open Funcion Release Funcion Read Funcion Write
..... ..... ..... .....

Inicializacion Salida
..... .....
Drivers de carácter, Operaciones asociadas al
driver
Posibles funciones (Un driver no tiene por qué implementarlas todas)
1) Posición actual del archivo
int (* lseek) (struct file *, loff_t, int);
2) Leer datos desde el dispositivo
int (* read) (struct file *, char *, size_t, loff_t;
3) Escribir datos en el dispositivo
int (* write) (struct file *, const char *, size_t, loff_t);
4) Apertura del dispositivo
int (* open) (struct inode *, struct file *);
5) Cierre del dispositivo
int (* release) (struct inode *, struct file *);
6) Determinar si el dispositivo es de r, w, rw
int (* select) (struct file *, int, select_table *);
7) Especificar comandos del driver
int (* ioctl) (struct inode *, struct file *, unsigned int, unsigned
long);
8) Peticiones de mapeo de memoria
int (* mmap) (struct file *, struct file *,struct vm_area_struct *);
9) Sincroniza el dispositivo
int (* fsync) (struct inode *, struct dentry *, int);
Drivers de carácter, estructura
file_operations
Identifica las funciones implementadas
Punteros a funcion
struct file_operations nombre_modulo_fops =
{nombre_modulo_lseek,
nombre_modulo_read,
nombre_modulo_write,
NULL, /* readdir */
NULL, /* select */
nombre_modulo_ioctl,
NULL, /* mmap */
nombre_modulo_open,
nombre_modulo_release,
NULL, /* fsync */
NULL, /* fasync */
NULL, /* check media change */
NULL, /* revalidate */
};
Drivers de carácter, estructura
file_operations

Inicialización resumida

struct file_operations nombre_modulo_fops = {


llseek: nombre_modulo_lseek,
read: nombre_modulo_read,
write: nombre_modulo_write,
ioctl: nombre_modulo_ioctl,
open: nombre_modulo_open,
release: nombre_modulo_release,
};
Drivers de carácter, estructura struct_file
Identifica internamente el fichero asociado al Driver

Información asociada contenida en la estructura


Modo archivo, indica si el fichero se puede leer o escribir
mode_t f_mode;
Puntero con la posición actual dentro del archivo:
loff_t f_pos;
Flags del archivo, O_RDONLY, O_NONBLOK y O_SYNC:
unsigned short f_flags;
Entrada de directorio asociada con el fichero:
struct dentry *f_dentry;
contiene a la estructura d_inode (f_dentry->d_inode).
Operaciones asociadas con el archivo:
struct file_operations *f_op;
Puntero a datos asociados al archivo:
void *private_data;
Drivers de carácter, Apertura del archivo
(open)

• Chequear posibles errores relacionados con


la especificación del dispositivo.
• Inicializar el dispositivo (en la primera
apertura).
• Identificar el número minor.
• Actualizar el puntero f_op.
• Asignar y completar todas las estructuras
de datos de:
¾filp->private_data.
• Incrementar el contador de uso .
Drivers de carácter, estructura inode

Representa internamente dentro del kernel al fichero

Dos campos de interes.


1. dev_t i_rdev Contiene el número actual del dispositivo
1. Para acceder a mayor y minor usamos
unsigned int iminor (struct inode *inode)
unsigned int imayor (struct inode *inode)
2. struct cdev *i_cdev estructura que representa el dispositivo
orientado a caracter
Drivers de carácter, registro de un driver
(2.6)
struct cdev *mi_dev = cdev_alloc;
int cdev_init(mi_dev, &nombre_modulo_fops);
int cdev_add(mi_dev, dev_t num, unsigned int count);
int cdev_del(mi_dev);
En el ejemplo…. (funcion de incializacion)

cdev_init( &gShortCDev, &short_fops );


gShortCDev.owner = THIS_MODULE;
if (( result = cdev_add( &gShortCDev, gShortDevNum, 1 )) !=
0 )
{
printk( KERN_WARNING "sample: cdev_add failed: %d\n",
result );
return result;
}
Donde…

struct cdev *mi_dev = cdev_alloc;


int cdev_init(mi_dev, &nombre_modulo_fops);
int cdev_add(mi_dev, dev_t num, unsigned int count);
int cdev_del(mi_dev);

En el ejemplo…. Asignacion de funciones…

//Estructura de operaciones---> instala "Callbacks" anterioresstruct file_operations


short_fops = {
.owner = THIS_MODULE,
.read = short_read,
.write = short_write,
.poll = short_poll,
.open = short_open,
.release = short_release,
};
Drivers de carácter, open en varios
modos
• Posibilidad de controlar varios dispositivos distintos
bajo un mismo driver o distintos modos de apertura.
¾ Se utiliza el minor number para identificar el dispositivo o
el modo de apertura.
• Cada modo de apertura tiene sus propias operaciones
definidas en una estructura file_operations
Drivers de carácter, implementación de read y
write
• int (*read) (struct file *, char *,
int,llof_t *);
• int (*write) (struct file *, const char *,
int, llof_t *f_pos);
• Argumentos:
¾ struct file * file
 Puntero a la estructura file para este dispositivo.
¾ char * buf
 Es un buffer de caracteres a ser leido o escrito. Está localizado en el
espacio de memoria del usuario y debe ser accedido usando las
macros get_fs*(), put_fs*(), and memcpy* fs(). El espacio del usuario
es inaccesible durante una interrupción.
¾ int count
 Es la cantidad de caracteres a ser leidos o escritos en el buffer. Puede
ser del tamaño del buffer.
¾ loff_t * f_pos
 Es un puntero a un long offset (está codificado con al menos con 64
bits) e indica la posición del fichero que el usuario esta accediendo
Drivers de carácter, read y write
Estas operaciones deben retornar un valor que
está relacionado con la solicitud realizada
VP = Valor Pedido
• VP == VR . VR = Valor Resultado
¾ Transferencia realizada satisfactoriamente.
• VP > VR .
¾ Se lograron transferir sólo una parte de los datos.
• VR == 0 .
¾ Fin de archivo.
• VR < 0 .
¾ Error cuyo valor es el código de error.

• Si no hay función read() o write() Registrada en la


estructura file_operations , y el dispositivo es de:
¾ Caracteres retornará –EINVAL;
¾ Bloques , serán manejadas por el VFS.
Drivers de carácter, Transferencia entre kernel
y usuario

• El espacio del kernel y del usuario son


distintos
¾ Necesidad de funciones para la transferencia entre ellos

• Del USUARIO al DISPOSITIVO (write)


void copy_from_user (void __user * to ,
const void * from ,
unsigned long count);
• Del DISPOSITIVO al USUARIO (read)
void copy_to_user (void * to ,
const void __user * from ,
unsigned long count);
Operaciones de entrada/salida
• Mecanismos de I/O bajo control CPU
¾ Programada, Instrucciones especiales
Precisa instrucciones especiales por parte de la CPU
Consume tiempo de CPU
Fácil de programar
Control
¾ Memoria mapeada
Redireccionamiento de accesos a memoria
Simple
Problema: diferentes velocidades de acceso
Programación Periféricos

Microprocesador
Periféricos Memoria
Intel x86

Espacio Espacio

Direcciones I/O Direcciones de


Memoria

Funciones Acceso a puertos


Operaciones E/S
• Instrucciones de acceso a puertos
Puertos de 8 bits
inb(unsigned port)
outb(unsigned char byte, unsigned port)
Puertos de 16 Bits
inw(unsigned port)
outw(unsigined short word,unsigned port)
Puertos de 32 Bits
inl(unsigned port)
outl(unsigined longword,unsigned port)
Reserva recursos E/S

• 000- 01F: Registros DMA1 (8237) • 1F0- 1F8: Controlador disco duro
• 020- 03F: Registro control INTR 1 • 200- 20F: Adaptador juegos
(8259) • 210- 277: No usado
• 040- 05F: Registro temporizador (8254) • 278- 27F: Puerto paralelo 3
• 060- 06F: Registro control TECLADO • 280- 2F7: No usado
(8042)
• 2F8- 2FF: Puerto serie 2
• 070- 07F: Reloj tiempo real
• 080- 08F: Registro página ADM
• 300- 31F: Placa prototipo USR
• 0A0- 0BF: Registro control INTR 2 • 360- 36F: Reservado
(8259) • 378- 37F: Puerto paralelo 2
• 0C0- 0DF: Registros DMA2 (8237)
• 0F0- 0FF: Registro Coprocesador
matemático

Reserva de espacio de E/S amtes de acceder


check_region(long,long) Comprueba dispobibilidad del rango
request_region() reserva el rango
release_region() libera el rango
Reserva recursos de memoria
• 00000000-0009fbff : RAM del sistema
• 0009fc00-0009ffff : reservado
• 000a0000-000bffff :Vídeo RAM
• 000c0000-000c7fff :Vídeo ROM
• 000f0000-000fffff : ROM del sistema
• 00100000-03feffff : RAM del sistema
• 00100000-0022c557 : código Kernel
• 0022c558-0024455f : datos del Kernel
• 20000000-2fffffff :Intel Corporation 440BX/ZX -82443BX/ZX Host bridge
• 68000000-68000fff :Texas Instruments PCI1225
• 68001000-68001fff :Texas Instruments PCI1225 (#2)
• e0000000-e3ffffff : PCI Bus #01
• e4000000-e7ffffff : PCI Bus #01
• e4000000-e4ffffff :ATI Technologies Inc 3D Rage LT Pro AGP-133
• e6000000-e6000fff :ATI Technologies Inc 3D Rage LT Pro AGP-133
• fffc0000-ffffffff : reservado

Funciones para acceder al las direcciones por parte de los drivers


check_mem_region(long,long) Comprueba dispobibilidad del rango
request_mem_region() reserva el rango
Release_mem_region() libera el rango
Fronteras

• Necesarias para indicar al compilador que tenga


cuidado con las optimizaciones y se asegure de la
completación de ciertas operaciones antes de seguir.
• #include<linux/kernel.h>
¾ barrier();
Copia todos los valores modificados a memoria para releerlos
despues.
• #include<asm/system.h>
¾ rmb();
¾ read_barrier_depends();
¾ wmb();
¾ mb();
Interrupciones

• Las interrupciones son un recurso limitado.


• Su uso se basa en manejadores de forma similar a las
señales.
• Es necesario instalar el manejador para usarlas. Si éste no
existe la interrupción es ignorada.
int request_irq(unsigned int irq,
irqreturn_t (*manejador) (int, void *, struct pt_regs *),
unsigned long flags, const char dev_name, void *dev_id);
manejador (unsigned int irq, void *dev_id);
- irq número de la interrupción solicitada
- manejador función encargada de gestión de la interrupción
- flags tipo de manejador
- name usado para indicar el propietario de la
interrupcion, usa el valor empleado al registrar del
dispositivo
- dev_id datos privados para entregar al manejador
Interrupciones

result = request_irq(shortp_irq, shortp_interrupt, 0,


"shortprint", NULL);

• Liberar la interrución
int free_irq(unsigned int irq,
, void *dev_id);
irq número de la interrupción solicitada
- manejador función encargada de gestión de la interrupción
- dev_id datos privados para entregar al manejador

result = free_irq(shortp_irq, NULL);


Interrupciones

• disable_irq(int irq);
• disable_irq_nosycn(int irq):
• enable_irq(int irq);

• local_irq_save(unsigned long flags)


• local_irq_disable();
• local_irq_restore(unsigned long flags)
• local_irq_enable();
Temporización. Dormir procesos
• Para dormir procesos es necesario crear una cola de
procesos en estado suspendido.
• Se usa la cabecera <linux/wait.h>
• Para dormir proceso primero es necesario crear una cola.
¾ Dos formas, dinámica y estática.
¾ DECLARE_WAIT_QUEUE_HEAD(mi_cola);
¾ wait_queue_head_t mi_cola;
¾ init_waitqueue_head(&mi_cola);
Temporización. Dormir procesos
• Una vez creada la cola se puede poner a un
proceso a dormir en esta en espera de una
condición.
¾ wait_event(mi_cola,condicion);
¾ wait_event_interruptible(mi_cola, condicion);
¾ wait_event_timeout(mi_cola, condicion, timeout);
¾ wait_event_interruptible_timeout(mi_cola,
condicion, timeout);
¾ Tiempo expresado en jiffies

• Despertar. Otro proceso, o manejador de


interrupción debe despertar al proceso.
¾ wake_up(&mi_cola);
¾ wake_up_interruptible(&mi_cola);
Otro método de espera

• void prepare_to_wait(wait_queue_head_t
*queue,wait_queue_t *wait,int State)
• Schedule();
• void finish_wait(wait_queue_head_t*
queue,wait_queue_t *wait)

while (short_head == short_tail) {


prepare_to_wait(&short_queue, &wait, TASK_INTERRUPTIBLE);
if (short_head == short_tail)
schedule();
finish_wait(&short_queue, &wait);
if (signal_pending (current)) /* a signal arrived */
return -ERESTARTSYS; /* tell the fs layer to handle it */
}
Temporización. Temporización
• Quantum de tiempo del kernel, jiffies, mide el número de veces
que ha entrado la interrupción de temporización. Normalmente da
la vuelta aproximadamente cada 50 días en plataformas de 32 bits.
Es posible usar extensión de 64 bits
• Conversión de jiffies a tiempo normal.
• jiffies*1000/HZ msec
• Rutinas de evaluación.
• time_before(),time_after_eq(), time_after(),
timer_timer_before_eq
• u65 get_jiffies_64();
Temporización. Temporización
• Tiempo en días, horas …
• Devuelve el tiempo en jiffies
¾ mktime(año,mes,dia,hora, minuto,segundo).
 unsigned int año,mes,dia,hora, minuto,segundo.
• Leer el tiempo
¾ do_gettimeofday(struct timeval *tv)
¾ struct timespec current_kernel_time();
Temporización. Temporización
• Retrasar la ejecución, dos formas
¾ while(timer_before(jiffies,j1))
cpu_relax(); // espera activa
¾ while(timer_before(jiffies,j1))
schedule(); // espera con cesión
Temporización. Temporización
• Dormir proceso
¾1
wait_event_interruptible_timeout
(cola,0,retraso);
¾2
set_current_state (TASK_INTERRUPTIBLE);
schedule_timeout(retraso);// ventaja de no
necesita crear la cola
Temporización. Temporización
• Retrasos pequeños (modo activo)
¾ ndaly(nasegundos),udelay (microsegundos),
mdaly(misegundos)
• Retrasos pequeños (modo pasivo)
¾ msleep(misegundos), sleep(segundos)
 Garantiza que el driver duerme, al menos, el intervalo dado
¾ msleep_interruptible(misegundos),
 El driver puede ser despertado, por ejemplo, por wake_up, en este
caso la rutina devuelve el tiempo que le quedaba por dormir
Temporización. Temporizadores del
kernel
• De forma similar a posix es posible crear y gestionar
temporizadores.
• La función que se lanza debe ejecutarse de forma atómica y no
puede acceder a espacio de usuario ni a punteros
• Se ejecutan en forma de interrupción por lo que el puntero no es
usable el proceso interrumpido puede no tener nada que ver con el
timer.
• No puede dormir la función.

struct timer_list timer;


init_timer(&timer);
timer.data = (unsigned long)parametro;
timer.function = jit_timer_fn;
timer.expires = j + tdelay; /* tiempo en jiffies */
add_timer(&timer);
Temporización. Tasklets
• Parecidos a los temporizadores del kernel y al igual que estos
lanzados por interrupción software.
¾ No hay control de que instante de tiempo se ejecuta. El
kernel lanza la tarea en un tiempo posterior pero siempre
antes de la siguiente interrupción de temporización

struct tasklet_struct tlet;


tasklet_init(&tlet, jit_tasklet_fn, (unsigned long)parametro);

tasklet_hi_schedule(&tlet);
/* tasklet_schedule(&tlet); */
/* tasklet_disable(&tlet) suspende la ejecución hasta se
habilita*/
/* tasklet_enable(&tlet) habilita la que esta suspendida */
Temporización. Workqueues

• Parecidos a las tasklets del kernel y difieren un trabajo para


ejecutarse en el futuro.
¾ Se ejecutan en un contexto especial (no dentro de una
interrupción). ¡¡ PUEDEN DORMIR !!
¾ Se puede indicar en que instante se desea ejecutar.
¾ Necesita para ejecutarse una cola de tipo workqueue_struct y
rellenar la estructura work_struct

struct workqueue_struct *queue;


/*queue = create_workqueue */
queue = create_singlethread_workqueue("shortprint");
DECLARE_WORK(trabajo,funcion, NULL);// crea y rellena work_struct
queue_work (queue, &trabajo);
queue_delayed_work (queue, &trabajo, retraso);
cancel_delayed_work (queue, &trabajo);
Temporización. Workqueues

• Se puede usar la cola compartida


¾ Si solo se desea enviar un trabajo de vez en cuando no
es necesario crear una cola se puede usar la cola
compartida

// crea y rellena work_struct


//DECLARE_WORK(trabajo,funcion, &datos);
static struct work_struct trabajo;

INIT_WORK(&trabajo, funcion, &datos);


schedule_delayed_work(& trabajo, delay);
schedule_work(& trabajo);
Sincronización

• Los diferentes drivers y copias del driver se pueden


ejecutar concurrentemente
• Necesidad de mecanismos de exclusión mutua.
¾ Semáforos
¾ Completions
¾ Spinlocks
¾ Operaciones atómicas.
Sincronización. Semáforos

• Su funcionamiento es similar a los semáforos


usados en los programas de usuarios
¾ Son informativos
¾ Bloquea la tarea durmiéndola.
• Para su uso es necesario emplear la cabecera
<asm/semaphore.h>
• El tipo semáforo usa la estructura semaphore
• Es necesario inicializar esta estructura antes de
usarla.
Sincronización. Semáforos

• Dos formas de inicializar el semáforo


¾ Dinamica
 struct semaphore sem;
 sema_init (&sem,val_init);
 Donde valor_init toma 0 o 1.
 init_MUTEX(&sem);
 init_MUTEX_LOCKED(&sem);
¾ Estática
 DECLARE_MUTEX(sem);
 DECLARE_MUTEX_LOCKED(sem);
Sincronización. Semáforos

• Operaciones sobre semáforo


¾ Bloqueo
¾ down(&sem);
¾ down_interruptible(&sem);
¾ down_trylock(&sem);
¾ Las dos primeras decrementan y, si es necesario,
suspenden la ejecución. La segunda permite que la
captura pueda ser interrumpida.
Sincronización. Semáforos

• Si down_interruptible retorna distinto de 0


indica que la captura fue interrumpida.
• Operaciones sobre semáforo
¾ Liberacion
¾ up(&sem);
Sincronización. Completions

• Permite la ejecución de una función cuando finaliza


una tarea.
• Permite acciones similares a las variables
condiciones.
• Necesita la cabecera <linux/completion.h>
• Dos formas de inicializar al igual que los semáforos
¾ Dinámica
 struct completion comp;
 init_completion (&comp);
¾ Estática
DECLARE_COMPLETION(comp);
Sincronización. Completions

• Espera la finalización de la tarea


¾ wait_completion(&comp);
¾ Suspende la tarea en espera de la activación de la
variable
¾ Para activar la variable:
 complete(&comp);
 complete_all(&comp);
Sincronización. Completions

• En el primer caso activa una de las tareas en


espera, en el segundo caso activa todas las tareas
que estén en espera.
• Después de completion_all es necesario reiniciar si
se desea volver a usar mediante
INIT_COMPLETION(comp);
Sincronización. Spinlock

• Similares en funcionamiento a los semáforos, pero


a diferencia de estos no “duermen” la tarea. Queda
en estado “live”.
¾ Peligro de “colgar” el kernel
Tarea bloquea spinlock y luego duerme. Otras tareas quedan
activas esperando y nunca vuelve a entrar tarea que bloqueo
• Necesarios en las funciones que no pueden
“dormir”, como interrupciones.
Sincronización. Spinlock

• Dos formas de inicializar el spinlock


¾ Dinamica
 spinlock_t mi_lock;
 spin_lock_init (&mi_lock);
¾ Estática
spinlock_t mi_lock = SPIN_LOCK_UNLOCKED
Sincronización. Spinlock

• Operaciones sobre spinlock


¾ Bloqueo
¾ spin_lock(&mi_lock);
 Bloquea el spin, si esta bloqueado la tarea permanece en espera de
hasta que esta disponible. Es ininterrumpible
¾ spin_lock_irqsave(&mi_lock,flag);
 unsigned long flag
 Bloquea el spin, con interrupciones deshabilitadas, salva vector
interrupciones
¾ spin_lock_irq(&mi_lock);
 Bloquea el spin, con interrupciones deshabilitadas
¾ spin_lock_bh(&mi_lock);
 Bloquea el spin, deshabilitando interrupciones software y habilitando
hardware.
Sincronización. Spinlock
• Operaciones sobre spinlock
¾ Bloqueo
¾ spin_trylock(&mi_lock);
¾ spin_trylock_bh(&mi_lock);
Bloquea el spin, deshabilitando interrupciones software y
habilitando hardware.
Sincronización. Operaciones atómicas
• Existe la posibilidad de usar variables atómicas.
• El kernel garantiza que la operación sobre la
variable se realiza de forma atómica.
• Necesario para sistemas SMP.
• Muy eficiente.
• Son equivalentes a enteros de 24 bits
• Definidos en <asm/atomic.h>
Sincronización. Operaciones atómicas
• Inicialización
¾ atomic_t v=ATOMIC_INIT(0);
¾ atomic_set(&v,i);
 Int i;
• Lectura
¾ atomic_read(&v);
• Suma y resta
¾ atomic_add(i,&v);
¾ atomic_sub(i,&v);
 int i;
• Incremento y decremento
¾ atomic_inc(&v);
¾ atomic_dec(&v);
¾ atomic_inc_and_test(&v); //devuelve true si el
resultado es 0
¾ atomic_dec_and_test(&v); //devuelve true si el
resultado es 0
Sincronización. Otros mecanismos de
sincronización

• Operaciones sobre bits


¾ Funcionan de forma similar a las operaciones atómicas
 set_bit(nr,void *add), clear_bit(nr,void *add),
change_bit(nr,void *add), test_and_set_bit(nr,void *add),
test_bit(nr,void *add), test_and_clear_bit(nr,void *add),
test_and_change_bit(nr,void *add),
¾ Seqlock
Diseñados para bloqueos rápidos sobre recursos pequeños
donde hay muchas lecturas pero las escrituras son raras.

También podría gustarte