Dormir considerado dañino

Intrabduction

La sincronización es un tema muy controvertido en informática e ingeniería de software. Estamos atrapados en un círculo vicioso en el que todo el mundo se le dice sincronización es difícil y por lo tanto, trata de mantenerse alejado del tema, y ​​cuando uno tiene que lidiar con el roscado, prefieren que se mantenga alejado de la lectura acerca de la sincronización porque la lectura es aún más difícil .

Una cosa que he notado mientras que el desarrollo de software linux para varios dispositivos es que los conductores escritos por los proveedores de hardware chupan inevitablemente. Hoy voy a contarles acerca de una cosa que me asusta (seguro que hay todo un universo de cosas que me asustan, pero eso es más allá del alcance de este post).

Ahora, a veces, cuando se trata de hardware , uno tiene que esperar (sueño) algún tiempo después de escribir a los registros para que el dispositivo cambie su estado. Un ejemplo destacado son los controladores de los controladores LED. Sí, su nuevo pisapapeles Android fantasía probablemente tiene un LED para notificar cuando tus amigos firmen a FaceBoob color. Los LEDs pueden tener diversos factores desencadenantes, por ejemplo, se puede establecer un LED para que parpadee cuando se accede a la memoria, al igual que en las cajas de escritorio antiguos con discos duros. De alguna manera cada vez que se intenta establecer un disparador para un LED en el kernel de Android (en lugar de utilizar ioctls de propiedad del Android), el dispositivo sólo se congela muerto. ¿Qué sucede realmente?

Si echamos un vistazo a la definición de clase LED en el kernel de linux, veremos que las funciones que controlan el estado de los LED (que han de ser aplicadas por el proveedor de hardware) No se deben utilizar las primitivas de dormir como msleep o usleep . ¿Por qué se plantea esta exigencia? Hay dos razones. Uno de ellos es que los LED se pueden utilizar en un contexto que no permite dormir – por ejemplo, dentro del código planificador para indicar la carga de la CPU. Dormir mientras que la programación puede ser manejado por los desarrolladores del kernel, por supuesto, pero tal operación hará que la deriva del tiempo y aumentar la complejidad del planificador. Otra razón es que el código de control de LED puede ser llamado miles de veces por segundo, y cualquier retraso hará que la enorme carga de la CPU.

De todos modos, incluso el código crítico el rendimiento tiene que ser correcta, por lo tanto, la necesidad de la sincronización. La sincronización ligera primitiva usada por todo el núcleo es el spinlock. Como se puede adivinar por su nombre, que no bloquea el subproceso de llamada (como un semáforo o un mutex haría), pero ocupados-espera hasta que se puede entrar en la sección crítica. Polling (u ocupado en espera) siempre ha sido desalentados porque consume tiempo de CPU. Sin embargo, permite que el algoritmo para el progreso en vez de bloquear (por lo tanto, la mayoría bloqueo libre algoritmos utilizan barreras de memoria y espera ocupada, que es equivalente a usar un bloqueo de giro en el kernel). De cualquier manera, lo que usted debe necesitar sobre spinlocks es que en la mayoría de los casos se utilizan para guardar la escritura a un registro de IO que es una operación muy rápida, y mientras un spinlock se mantiene, las interrupciones están deshabilitadas, por lo tanto, si se llama a sleep (), el hilo no tendría forma de reanudar.

Y sí, la mayoría de los desarrolladores de controladores novato no sabe que algunos buses externos (como I2C) requieren para dormir debido a la naturaleza del hardware. Por lo tanto, cuando uno emite una transferencia I2C dentro del spinlock, lo que quieren es ver el congelamiento kernel.

Ahora que hemos descubierto lo que causa la congelación, vamos a pensar en lo que podemos hacer al respecto. Por un lado, si tenemos suficiente dinero, podemos simplemente asumir los clientes aceptarán cualquier porquería que vendemos ya que no tienen alternativa. Y eso no funciona para Samsung, Qualcomm, Sony y muchos otros proveedores. Por otro lado, es posible que desee para arreglarlo. Sabemos que no podemos dormir en una cerradura, pero necesitamos un bloqueo para mantener los datos consistentes. Linux tiene varias soluciones para el rescate y te voy a dar una visión general de los dos de ellos que son fácil y conveniente para un típico /> sistema embebido / escritorio
Workqueues

La primera solución utiliza una WorkQueue. A WorkQueue es una abstracción (en la parte superior de los hilos del núcleo) que permite programar trabajos (de trabajo) a ser ejecutado en algún momento después. Usted puede pensar en un WorkQueue como de un «futuro» o el concepto de «async» utilizado en espacio de usuario.

Voy a usar el ejemplo de un controlador LED que he escrito para un smartphone (HTC Kovsky también conocido como Sony Ericsson Xperia X1) para ilustrar la WorkQueue.

En primer lugar, tenemos que incluir el encabezado correspondiente. . No confíes en mí aquí, las cosas cambia todo el tiempo en linux, así que prefiero usar grep para encontrar la cabecera real

  1. # include

Tenemos que declarar un» trabajo » que queremos programar. Una obra es esencialmente una rutina que se llama, que acepta la instancia de una clase «struct work_struct». . Podemos utilizar la macro «DECLARE_WORK» para definir una obra

  1. estática DECLARE_WORK ( colorled_wq , htckovsky_update_color_leds );

Ahora, echemos un vistazo a la forma de aplicar la función de trabajo

  1. estática vacío htckovsky_update_button_light ( struct work_struct * trabajo ) {
  2. [ 3 caracteres amortiguar > ] = { MICROP_KEYPAD_BRIGHTNESS_KOVS , 0 , 0 };
  3. carbón brillo = kovsky_leds [ BOTONES ] brillo ;.
  4. si ( brillo ) {
  5. amortiguar [ 1 ] = 0x94 ;
  6. amortiguar estilo [ 2 ] = brillo >> 2 ;
  7. }
  8. microp_ng_write ( cliente , búfer , 3 );
  9. }

Como se puede notar, estamos usando la función microp_ng_write que en realidad está iniciando una transferencia I2C. También podríamos añadir una () llamada msleep o incluso añadir un mutex y que puede funcionar.

Ahora, ¿cómo nos llamamos el código? Simple, en su rutina de no dormir (que puede ser un manejador de interrupciones o una implementación de una función de la clase de LED), llame a la rutina «schedule_work».

  1. estática vacío htckovsky_set_brightness_color ( struct led_classdev * led_cdev ,
  2. enum led_brightness brillo )
  3. {
  4. schedule_work ( y colorled_wq );
  5. }

Ahora, lo que si quería pasar algunos datos a la rutina de trabajo sin necesidad de utilizar las variables globales? El «container_of» macro viene al rescate! Container_of es esencialmente una implementación de las clases-como POO en C basado en rutinas y composición en vez de la herencia. Funciona así: se supone que tiene una rutina que sólo acepta el puntero «struct work_struct», y desea pasar datos arbitrarios. Modificación del código de Linux y la creación de una copia de la aplicación WorkQueue cada vez sería tonto y aburrido (aunque eso es lo que los desarrolladores de Windows harían de todos modos). En su lugar, se crea una estructura para mantener nuestros datos, y agregar el puntero a la WorkQueue como miembro a la misma. La macro container_of calcula el desplazamiento de un WorkQueue en nuestra estructura, y sustrae desde el puntero WorkQueue. Woops, tenemos un puntero a nuestra estructura y podemos deserializar los datos de ella. Esto funciona así:

struct work_struct * our_work = kmalloc (sizeof (struct work_struct), GFP_KERNEL);
/ * Init la estructura de trabajo INIT_WORK /> * /
our_work , handler_routine);
struct my_work_data {
struct work_struct * cool_work;
} = {trabajar
cool_work = our_work ,
};

El código fuente completo del controlador está disponible en el linux correspondiente árbol en gitorious. No sincronizar el estado del LED, sin embargo. Uno debe ser cuidadoso al escribir código sin sincronización: a pesar de que para un LED que actualiza miles de veces por segundo, mostrando un estado mixto no es un problema (un ojo humano no notará glitches), si usted va esta manera, sólo mantener su «virtual», el estado conductor!
https://gitorious.org/linux-on-qualcomm-s-msm/alex-linux-xperia/source/844e6f8bed194d6947d966233f411e8997484091:drivers/leds/leds-microp-htckovsky.c

Kernel Threads
Otra alternativa es usar los hilos del núcleo. De hecho, los hilos del núcleo son casi como un proceso en espacio de usuario se ejecuta en modo kernel. Puede recibir señales y hacer muchas otras cosas, pero los hilos del núcleo de programación es más difícil y casi todo el tiempo usted puede conseguir lejos con usar workqueues. He aquí algunos ejemplos de código que muestra cómo generar un subproceso y hacer su trabajo sucio en el interior:

> int my_thread ( vacío * v) {
struct salida ,
siginfo_t información;

daemonize ( « mydriver_baddaemond «);
allow_signal (SIGKILL);
init_completion (& salida ),

mientras (1 ) {
si (signal_pending (actual)) {
si (dequeue_signal_lock (actual, y current-> bloqueados, e información) == SIGKILL) {
Ir limpieza;
}
}
demás {
/ / Hacer tus cosas muy mal aquí

}}

limpieza:
complete_and_exit (y salida , 0);
retorno 0;}


/ / debe estar en alguna parte en los datos del conductor
struct task_struct * mi_tarea = NULL ,

estática vacío start_thread ( vacío ) {
vacío * arg = NULL , / / Se puede usar para pasar datos al controlador
mi_tarea = kthread_create (my_thread, arg, » mydriver_baddaemond «);}


estática vacío stop_thread ( vacío ) {
send_sig (SIGKILL, mi_tarea, 0);
my_thread_task = NULL ,
}

Conclusiones
Ahora ya sabes cómo hacer que sus conductores más estable y menos hambrientos de poder. Casi cada vez que usted está pensando en una aplicación ad-hoc de un concepto común, asegúrese de linux tiene una solución limpia y que funcionaba ya.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *