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