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>