Using catalogs: /etc/sgml/catalog Using stylesheet: /usr/share/sgml/docbook/utils-0.6.9/docbook-utils.dsl#html Working on: /home/jdavila/lucas/doc-manual-linux-bloqueo/doc-manual-linux-bloqueo.xml Gu�a Informal al Bloqueo

Gu�a Informal al Bloqueo

Paul Rusty Russell


Tabla de contenidos
1. Introducci�n
1.1. El Problema con la Concurrencia
2. Dos Tipos Principales de Bloqueos del N�cleo: Spinlocks y Sem�foros
2.1. Bloqueos y N�cleos Monoprocesador
2.2. Variantes de Bloqueo Lectura/Escritura
2.3. Bloqueando S�lo en el Contexto de Usuario
2.4. Bloqueando entre Contexto de Usuario y BHs (Bottom Halves)
2.5. Bloqueando Entre Contexto de Usuario y Tasklets/Soft IRQs
2.6. Bloqueando Entre Bottom Halves
2.6.1. El Mismo BH
2.6.2. Diferentes BHs
2.7. Bloqueando Entre Tasklets
2.7.1. La Misma Tasklet
2.7.2. Diferentes Tasklets
2.8. Bloqueando entre Softirqs
2.8.1. La Misma Softirq
2.8.2. Diferentes Softirqs
3. Contexto de IRQ de Hardware
3.1. Bloqueando entre IRQs Hardware y Softirqs/Tasklets/BHs
4. T�cnicas Comunes
4.1. En un Contexto de Interrupciones No Escritores
4.2. Deadlock: Simple y Avanzado
4.2.1. Preveniendo los Deadlocks
4.2.2. Sobreentusiasmo en la Prevenci�n de Deadlocks
4.3. Datos por cada CPU
4.4. Bloqueos Gran Lector
4.5. Eliminando los bloqueos: Ordenamiento de Lecturas y Escrituras
4.6. Eliminando los Bloqueos: Operaciones At�micas
4.7. Protegiendo Una Colecci�n de Objetos: Cuentas de Referencia
4.7.1. Macros Para Ayudarte
4.8. Cosas Que Duermen
4.9. La Follonera Sparc
4.10. Cron�metros de Carreras: Un Pasatiempo del N�cleo
5. Lecturas Adicionales
6. Gracias
7. Sobre la Traducci�n
Glosario
Lista de tablas
1-1. Resultados Esperados
1-2. Resultados Posibles
4-1. Consecuencias

Cap�tulo 1. Introducci�n

Bienvenido, a la Gu�a Informal de Bloqueo de N�cleo de Rusty. Este documento describe los sistemas de bloqueo en el n�cleo Linux como aproximaci�n al 2.4.

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.


Cap�tulo 2. Dos Tipos Principales de Bloqueos del N�cleo: Spinlocks y Sem�foros

Hay dos tipos principales de bloqueos del n�cleo. El tipo fundamental es el spinlock (include/asm/spinlock.h), que es un bloqueo muy simple recept�culo-simple; si no puedes coger el spinlock, entonces te mantienes intent�ndolo (spinning) hasta que puedas. Los spinlocks son muy peque�os y r�pidos, y pueden ser usados en cualquier sitio.

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.


2.4. Bloqueando entre Contexto de Usuario y BHs (Bottom Halves)

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.


2.5. Bloqueando Entre Contexto de Usuario y Tasklets/Soft IRQs

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().


2.8. Bloqueando entre Softirqs

Frecuentemente una softirq quiz�s quiera compartir datos con ella misma, con una tasklet, o con un bottom half.


2.8.1. La Misma Softirq

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.


Cap�tulo 3. Contexto de IRQ de Hardware

Las interrupciones hardware usualmente se comunican con un bottom half, tasklet o softirq. Frecuentemente esto complica el poner el trabajo en una cola, que el BH/softirq deber�a de sacar.


Cap�tulo 4. T�cnicas Comunes

Esta secci�n lista algunos dilemas comunes y las soluciones est�ndar usadas en el c�digo del n�cleo Linux. Si usas estas, la gente encontrar� tu c�digo m�s f�cil de entender.

Si pudiera darte una parte de un aviso ser�a: nunca duermas con alguien m�s loco/a que t�. Pero si tuviera que darte un aviso en el bloqueo: mantente s�lo.

Bloquea a los datos, no al c�digo.

Se reacio a introducir nuevos bloqueos.

Suficientemente ajeno, esto es justo lo contrario de mi aviso cuando tienes que dormir con alguien m�s loco/a que t�.


4.2. Deadlock: Simple y Avanzado

Hay un fallo de codificaci�n donde un pedazo de c�digo intenta obtener un spinlock dos veces: �l esperar� siempre, esperando a que el bloqueo sea liberado (spinlocks, rwlocks y sem�foros no son recursivos en Linux). Esto es trivial de diagnosticar: no es un tipo de problema de estar-cinco-noches-despierto-hablando-con-los-suaves-conejitos-del-c�digo.

Para un caso ligeramente m�s complejo, imag�nate que tienes una regi�n compartida por un bottom half y un contexto de usuario. Si usas una llamada spin_lock() para protegerla, es posible que el contexto de usuario sea interrumpido por el bottom half mientras mantiene el bloqueo, y el bottom half entonces esperar� para siempre para obtener el mismo bloqueo.

Ambas son llamadas deadlock (bloqueo muerto), y como se mostr� antes, puede ocurrir con una CPU simple (aunque no en compilaciones para UP, ya que los spinlocks se desvanecen en la compilaci�n del n�cleo con CONFIG_SMP=n. A�n tendr�s corrupci�n de datos en el segundo ejemplo).

Este bloqueo completo es f�cil de diagnosticar: en equipos SMP el cron�metro guardi�n o compilado con DEBUG_SPINLOCKS establecido (include/linux/spinlock.h) nos mostrar� esto inmediatamente cuando suceda.

Un problema m�s complejo es el tambi�n llamado `abrazo mortal', involucrando a dos o m�s bloqueos. Digamos que tienes una tabla hash: cada entrada en la tabla es un spinlock, y una cadena de objetos ordenados. Dentro de un manejador softirq, algunas veces quieres alterar un objeto de un lugar de la tabla hash a otro: coges el spinlock de la vieja cadena hash y el spinlock de la nueva cadena hash, y borras el objeto de la vieja y lo insertas en la nueva.

Aqu� hay dos problemas. El primero es que si tu c�digo siempre intenta mover el objeto a la misma cadena, �l se har� un deadlock cuando se intente bloquear dos veces. El segundo es que si la misma softirq u otra CPU est� intentando mover otro objeto en la direcci�n inversa podr�a pasar lo siguiente:

Las dos CPUs esperar�n para siempre, esperando a que el otro libere su bloqueo. �l parecer�, oler�, y se sentir� como si cayera el sistema.


4.5. Eliminando los bloqueos: Ordenamiento de Lecturas y Escrituras

Algunas veces es posible eliminar el bloqueo. Considera el siguiente caso del c�digo del cortafuegos 2.2, que inserta un elemento en una lista simplemente enlazada en el contexto de usuario:


        new->next = i->next;
        i->next = new;
    

Aqu� el autor (Alan Cox, que sab�a lo que estaba haciendo) asume que el asignamiento de punteros es at�mico. Esto es importante, porque los paquetes de red atravesar�an esta lista en bottom halves sin un bloqueo. Dependiendo del tiempo exacto, ellos ver�an el nuevo elemento en las lista con un puntero next v�lido, o no ver�an la lista todav�a. A�n se requiere un bloqueo contra otras CPUs insertando o borrando de la lista, por supuesto.

Por supuesto, las escrituras deben estar en este orden, en otro caso el nuevo elemento aparece en la lista con un puntero next inv�lido, y alguna otra CPU iterando en el tiempo equivocado saltar� a trav�s de �l a la basura. Porque las modernas CPUs reordenan, el c�digo de Alan actualmente se lee como sigue:


        new->next = i->next;
        wmb();
        i->next = new;

La funci�n wmb() es una barrera de escritura de memoria (include/asm/system.h): ni el compilador ni la CPU permitir�n alguna escritura a memoria despu�s de que wmb() sea visible a otro hardware antes de que alguna otra escritura se encuentre antes de wmb().

Como i386 no realiza reordenamiento de escritura, este bug nunca fue mostrada en esta plataforma. Es otras plataformas SMP, de cualquier forma, si que fue mostrado.

Tambi�n hay rmb() para ordenamiento de lectura: para asegurar que cualquier lectura previa de una variable ocurre antes de la siguiente lectura. La macro simple mb() combina rmb() y wmb().

Algunas operaciones at�micas est�n definidas para actuar como una barrera de memoria (esto es, como la macro mb(), pero si dudas, se expl�cito. Tambi�n, las operaciones de spinlock actuan como barreras parciales: las operaciones despu�s de obtener un spinlock nunca ser�n movidas para preceder a la llamada spin_lock(), y las operaciones antes de liberar un spinlock nunca ser�n movidas despu�s de la llamada spin_unlock().


4.7. Protegiendo Una Colecci�n de Objetos: Cuentas de Referencia

Bloqueando una colecci�n de objetos es bastante f�cil: coges un spinlock simple, y te aseguras de obtenerlo antes de buscar, a�adir o borrar un objeto.

El prop�sito de este bloqueo no es proteger los objetos individuales: quiz�s tengas un bloqueo separado dentro de cada uno de ellos. Es para proteger la estructura de datos conteniendo el objeto de las condiciones de carrera. Frecuentemente el mismo bloqueo es usado tambi�n para proteger los contenidos de todos los objetos, por simplicidad, pero ellos son inherentemente ortogonales (y muchas otras grandes palabras dise�adas para confundir).

Cambiando esto a un bloqueo de lectura-escritura frecuentemente ayudar� notablemente si las lecturas son m�s frecuentes que las escrituras. Si no, hay otra aproximaci�n que puedes usar para reducir el tiempo que es mantenido el bloqueo: las cuentas de referencia.

En esta aproximaci�n, un objeto tiene un due�o, quien establece la cuenta de referencia a uno. Cuando obtienes un puntero al objeto, incrementas la cuenta de referencia (una operaci�n 'obtener'). Cuando abandonas un puntero, decrementas la cuenta de referencia (una operaci�n 'poner'). Cuando el due�o quiere destruirlo, lo marca como muerto y hace una operaci�n poner.

Cualquiera que ponga la cuenta de referencia a cero (usualmente implementado con atomic_dec_and_test()) limpia y libera el objeto.

Esto significa que se garantiza que el objeto no se desvanecer� debajo de ti, incluso aunque no tengas m�s un bloqueo para la colecci�n.

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);
        }
    

4.8. Cosas Que Duermen

Nunca puedes llamar a las siguientes rutinas mientras est�s manteniendo un spinlock, porque ellas quiz�s se vayan a dormir. Esto tambi�n significa que necesitas estar en el contexto de usuario.

  • Accesos a userspace:

    • 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.


4.10. Cron�metros de Carreras: Un Pasatiempo del N�cleo

Los cron�metros pueden producir sus propios problemas con las carreras. Considera una colecci�n de objeros (listas, hash, etc) donde cada objeto tiene un cron�metro que lo va a destruir.

Si quieres destruir la colecci�n entera (digamos en el borrado de un m�dulo), quiz�s realices lo siguiente:


        /* 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);
    

Tarde o temprano, esto romper�a en SMP, porque un cron�metro puede acabar antes que spin_lock_bh(), y s�lo obtendr�a el bloqueo despu�s de spin_unlock_bh(), y entonces intentar�a liberar el elemento (�el cual ya ha sido liberado!).

Esto puede ser eliminado comprobando el resultado de del_timer(): si retorna 1, el cron�metro ha sido borrado. Si 0, significa (en este caso) que est� actualmente ejecut�ndose, por lo tanto lo que podemos hacer es:


        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);
    

Otro problema com�n es el borrando de cron�metros que se reinician a ellos mismos (llamando a add_timer() al final de su funci�n cron�metro). Porque este es un caso bastante com�n que es propenso a carreras, puedes poner una llamada a timer_exit() muy al funal de tu funci�n cron�metro, y usar del_timer_sync() para manejar este caso. �l retorna el n�mero de veces que el cron�metro tuvo que ser borrado antes de que finalmente lo par�ramos a�adi�ndolo otra vez.


Cap�tulo 5. Lecturas Adicionales


Cap�tulo 6. Gracias

Gracias a Telsa Gwynne por darle el formato DocBook, ordenando y a�adi�ndole estilo.

Gracias a Martin Pool, Philipp Rumpf, Stephen Rothwell, Paul Mackerras, Ruedi Aschwanden, Alan Cox, Manfred Spraul y Tim Waugh por la profunda lectura, correcci�n, encendido y comentarios.

Gracias a la intriga por no tener influencia en este documento.


Cap�tulo 7. Sobre la Traducci�n

Este documento es la traducci�n de "Unreliable Guide To Locking", documento que acompa�a al c�digo del n�cleo de Linux, versi�n 2.4.18.

Este documento ha sido traducido por Rub�n Melc�n ; 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

Glosario

bh

Bottom Half: por motivos hist�ricos, las funciones con `_bh' en ellas frecuentemente ahora se refieren a cualquier interrupci�n software, ej. spin_lock_bh() bloquea cualquier interrupci�n software en la CPU actual. Los Bottom Halves est�n desaprobados, y ser�n eventualmente reemplazados por las tasklets. S�lo un bottom half se estar� ejecutando a la vez.

Interrupci�n Hardware / IRQ Hardware

Petici�n de interrupci�n Hardware. in_irq() retorna true en un manejador de interrupciones hardware (tambi�n retorna true cuando las interrupciones son bloqueadas).

Contexto de Interrupciones

No el contexto de usuario: procesando una irq hardware o software. Indicado por la macro in_interrupt() retornando true (aunque tambi�n retorna true cuando las interrupciones o los BHs son bloqueados).

SMP

Symmetric Multi-Processor (Multi-Procesamiento Sim�trico): n�cleos compilados para m�quinas con m�ltiples CPUs. (CONFIG_SMP=y).

softirq

Estrictamente hablando, una de las 32 interuupciones software enumeradas que pueden ejecutarse en m�ltiples CPUs a la vez. Algunas veces usadas tambi�n para referirse a las tasklets y bottom halves (esto es, todas las interrupciones software).

Interrupci�n Software / IRQ Software

Manejador de interrupciones software. in_irq() retorna false; in_softirq() retorna true. Tasklets, softirqs y bottom halves caen todos en la categor�a de `interrupciones software'.

tasklet

Una interrupci�n software din�micamente registrable, que est� garantizada que s�lo se ejecutar� en una CPU a la vez.

UP

Uni-Processor (Mono-Procesador): No-SMP. (CONFIG_SMP=n).

Contexto de Usuario

El n�cleo ejecut�ndose en nombre de un proceso particular o hilo del n�cleo (dado por la macro current()). No te confundas con el espacio de usuario. Puede ser interrumpido por las interrupciones software o hardware.

Espacio de Usuario

Un proceso ejecutando su propio c�digo fuera del n�cleo.

Done.