Parece que es aquí donde tiene que estar SMP ; por lo tanto todo el mundo que esté en estos días hackeando el núcleo necesita conocer los fundamentos de la concurrencia y el bloqueos para SMP.
(Sáltate esto si sabes lo que es una Condición de Carrera (Race Condition).
En un programa normal, puedes incrementar un contador de la forma:
contador_muy_importante++;
Esto es lo que esperarías que pasase:
Tabla 1-1. Resultados Esperados
Instancia 1 | Instancia 2 |
---|---|
lee contador_muy_importante (5) | |
añade 1 (6) | |
escribe contador_muy_importante (6) | |
lee contador_muy_importante (6) | |
añade 1 (7) | |
escribe contador_muy_importante (7) |
Tabla 1-2. Resultados Posibles
Instancia 1 | Instancia 2 |
---|---|
lee contador_muy_importante (5) | |
lee contador_muy_importante (5) | |
añade 1 (6) | |
añade 1 (6) | |
escribe contador_muy_importante (6) | |
escribe contador_muy_importante (6) |
El segundo tipo es el semáforo (include/asm/semaphore.h): puede tener más de un receptáculo en algún momento (el número se decide en tiempo de inicialización), aunque es usado más comúnmente como un bloqueo de receptáculo-simple (un mutex). Si no puedes obtener el semáforo, tus tareas se pondrán en una cola, y serán despertadas cuando el semáforo sea liberado. Esto significa que la CPU hará algo mientras que estás esperando, pero hay muchos casos en los que simplemente no puedes dormir (ver Sección 4.8), y por lo tanto tienes que usar un spinlock en vez del semáforo.
Ningún tipo de bloqueo es recursivo: ver Sección 4.2.
Los semáforos todavía existen, porque son requeridos para la sincronización entre contextos de usuario, tal como veremos a continuación.
Si un bottom half comparte datos con el contexto de usuario, tienes dos problemas. El primero, el actual contexto de usuario puede ser interrumpido por un bottom half, y el segundo, la región crítica puede ser ejecutada desde otra CPU. Aquí es donde es usado spin_lock_bh() (include/linux/spinlock.h). El deshabilita los bottom halves es esta CPU, entonces coge el bloqueo. spin_unlock_bh() realiza lo inverso.
Esto además funciona perfectamente para UP ; el spinlock desaparece, y esta macro simplemente se transforma en local_bh_disable() (include/asm/softirq.h), la cual te protege de que el bottom half se ejecute.
Esto es exactamente lo mismo que lo anterior, porque local_bh_disable() actualmente también deshabilita todas las softirqs y tasklets en esta CPU. Debería de ser llamada `local_softirq_disable()', pero el nombre ha sido preservado por motivos históricos. De forma similar, en un mundo perfecto spin_lock_bh() debería de ser llamada spin_lock_softirq().
Algunas veces una tasklet quizás quiera compartir datos con otra tasklet, o con un bottom half.
Frecuentemente una softirq quizás quiera compartir datos con ella misma, con una tasklet, o con un bottom half.
La misma softirq puede ejecutarse en otras CPUs: puedes usar un array para cada CPU (ver Sección 4.3) para un mejor rendimiento. Si vas a llegar tan lejos como el uso de una softirq, probablemente te preocupes suficientemente sobre el rendimiento escalable para justificar la complejidad extra.
Necesitarás usar spin_lock() y spin_unlock() para compartir datos.
Bloquea a los datos, no al código.
Se reacio a introducir nuevos bloqueos.
Tabla 4-1. Consecuencias
CPU 1 | CPU 2 |
---|---|
Pilla bloqueo A -> OK | Pilla bloqueo B -> OK |
Pilla bloqueo B -> spin | Pilla bloqueo A -> spin |
new->next = i->next; i->next = new;
new->next = i->next; wmb(); i->next = new;
Aquí hay algún código esqueleto:
void create_foo(struct foo *x) { atomic_set(&x->use, 1); spin_lock_bh(&list_lock); ... inserta en la lista ... spin_unlock_bh(&list_lock); } struct foo *get_foo(int desc) { struct foo *ret; spin_lock_bh(&list_lock); ... encuentra en la lista ... if (ret) atomic_inc(&ret->use); spin_unlock_bh(&list_lock); return ret; } void put_foo(struct foo *x) { if (atomic_dec_and_test(&x->use)) kfree(foo); } void destroy_foo(struct foo *x) { spin_lock_bh(&list_lock); ... borra de la lista ... spin_unlock_bh(&list_lock); put_foo(x); }
copy_from_user()
copy_to_user()
get_user()
put_user()
kmalloc(GFP_KERNEL)
down_interruptible() y down()
Hay una función down_trylock() que puede ser usada dentro del contexto de interrupción, ya que no dormirá. up() tampoco dormirá.
printk() puede ser llamada en cualquier contexto, suficientemente interesante.
/* ESTE CÓDIGO ES MALO MALO MALO MALO: SI HUBIERA ALGO PEOR USUARÍA NOTACIÓN HÚNGARA */ spin_lock_bh(&list_lock); while (list) { struct foo *next = list->next; del_timer(&list->timer); kfree(list); list = next; } spin_unlock_bh(&list_lock);
retry: spin_lock_bh(&list_lock); while (list) { struct foo *next = list->next; if (!del_timer(&list->timer)) { /* Le da al cronómetro una oportunidad para borrarlo */ spin_unlock_bh(&list_lock); goto retry; } kfree(list); list = next; } spin_unlock_bh(&list_lock);
Gracias a Telsa Gwynne por darle el formato DocBook, ordenando y añadiéndole estilo.
Gracias a la intriga por no tener influencia en este documento.
Este documento ha sido traducido por Rubén Melcón <melkon@terra.es>; y es publicado por el Proyecto Lucas
Versión de la tradución 0.04 ( Julio de 2002 ).
Si tienes comentarios sobre la traducción, ponte en contacto con Rubén Melcón <melkon@terra.es>