docs/sp_SP: Add memory-barriers.txt Spanish translation

Message ID 20221111174739.2471900-1-carlos.bilbao@amd.com
State New
Headers
Series docs/sp_SP: Add memory-barriers.txt Spanish translation |

Commit Message

Carlos Bilbao Nov. 11, 2022, 5:47 p.m. UTC
  Translate the following documents into Spanish:

- memory-barriers.txt

Also include it on index of Spanish translations, following the same
approach used by the Korean translation, as a note at the end of the index.

Signed-off-by: Carlos Bilbao <carlos.bilbao@amd.com>
---
 Documentation/translations/sp_SP/index.rst    |    4 +
 .../translations/sp_SP/memory-barriers.txt    | 3134 +++++++++++++++++
 2 files changed, 3138 insertions(+)
 create mode 100644 Documentation/translations/sp_SP/memory-barriers.txt
  

Comments

Jonathan Corbet Nov. 21, 2022, 9:10 p.m. UTC | #1
Carlos Bilbao <carlos.bilbao@amd.com> writes:

> Translate the following documents into Spanish:
>
> - memory-barriers.txt
>
> Also include it on index of Spanish translations, following the same
> approach used by the Korean translation, as a note at the end of the index.
>
> Signed-off-by: Carlos Bilbao <carlos.bilbao@amd.com>
> ---
>  Documentation/translations/sp_SP/index.rst    |    4 +
>  .../translations/sp_SP/memory-barriers.txt    | 3134 +++++++++++++++++
>  2 files changed, 3138 insertions(+)
>  create mode 100644 Documentation/translations/sp_SP/memory-barriers.txt
>
> diff --git a/Documentation/translations/sp_SP/index.rst b/Documentation/translations/sp_SP/index.rst
> index e20dd6e875e7..61dace3df9a6 100644
> --- a/Documentation/translations/sp_SP/index.rst
> +++ b/Documentation/translations/sp_SP/index.rst
> @@ -79,3 +79,7 @@ Traducciones al español
>  
>     howto
>     submitting-patches
> +
> +.. include:: ./memory-barriers.txt
> +    :literal:

That's an awfully big "note" to add at the end of the index file; I
don't really think that's the best experience for our readers.  Can this
be done using the wrapper documents like for English in current
docs-next?

Thanks,

jon
  
Carlos Bilbao Nov. 28, 2022, 2:53 p.m. UTC | #2
On 11/21/22 15:10, Jonathan Corbet wrote:

> Carlos Bilbao <carlos.bilbao@amd.com> writes:
>
>> Translate the following documents into Spanish:
>>
>> - memory-barriers.txt
>>
>> Also include it on index of Spanish translations, following the same
>> approach used by the Korean translation, as a note at the end of the index.
>>
>> Signed-off-by: Carlos Bilbao <carlos.bilbao@amd.com>
>> ---
>>   Documentation/translations/sp_SP/index.rst    |    4 +
>>   .../translations/sp_SP/memory-barriers.txt    | 3134 +++++++++++++++++
>>   2 files changed, 3138 insertions(+)
>>   create mode 100644 Documentation/translations/sp_SP/memory-barriers.txt
>>
>> diff --git a/Documentation/translations/sp_SP/index.rst b/Documentation/translations/sp_SP/index.rst
>> index e20dd6e875e7..61dace3df9a6 100644
>> --- a/Documentation/translations/sp_SP/index.rst
>> +++ b/Documentation/translations/sp_SP/index.rst
>> @@ -79,3 +79,7 @@ Traducciones al español
>>   
>>      howto
>>      submitting-patches
>> +
>> +.. include:: ./memory-barriers.txt
>> +    :literal:
> That's an awfully big "note" to add at the end of the index file; I
> don't really think that's the best experience for our readers.  Can this
> be done using the wrapper documents like for English in current
> docs-next?


Yes that makes more sense. Sending v2 :)


>
> Thanks,
>
> jon


Thanks,

Carlos
  

Patch

diff --git a/Documentation/translations/sp_SP/index.rst b/Documentation/translations/sp_SP/index.rst
index e20dd6e875e7..61dace3df9a6 100644
--- a/Documentation/translations/sp_SP/index.rst
+++ b/Documentation/translations/sp_SP/index.rst
@@ -79,3 +79,7 @@  Traducciones al español
 
    howto
    submitting-patches
+
+.. include:: ./memory-barriers.txt
+    :literal:
+
diff --git a/Documentation/translations/sp_SP/memory-barriers.txt b/Documentation/translations/sp_SP/memory-barriers.txt
new file mode 100644
index 000000000000..f62bd797216d
--- /dev/null
+++ b/Documentation/translations/sp_SP/memory-barriers.txt
@@ -0,0 +1,3134 @@ 
+NOTE:
+This is a version of Documentation/memory-barriers.txt translated into
+Spanish by Carlos Bilbao <carlos.bilbao@amd.com>. If you find any
+difference between this document and the original file or a problem with
+the translation, please contact the maintainer of this file. Please also
+note that the purpose of this file is to be easier to read for non English
+(read: Spanish) speakers and is not intended as a fork. So if you have any
+comments or updates for this file please update the original English file
+first. The English version is definitive, and readers should look there if
+they have any doubt.
+
+			 ======================================
+			 BARRERAS DE MEMORIA EN EL KERNEL LINUX
+			 ======================================
+
+Documento original: David Howells <dhowells@redhat.com>
+    Paul E. McKenney <paulmck@linux.ibm.com>
+    Will Deacon <will.deacon@arm.com>
+    Peter Zijlstra <peterz@infradead.org>
+
+Traducido por: Carlos Bilbao <carlos.bilbao@amd.com>
+Nota: Si tiene alguna duda sobre la exactitud del contenido de esta
+traducción, la única referencia válida es la documentación oficial en
+inglés.
+
+===========
+ADVERTENCIA
+===========
+
+Este documento no es una especificación; es intencionalmente (por motivos
+de brevedad) y sin querer (por ser humanos) incompleta. Este documento
+pretende ser una guía para usar las diversas barreras de memoria
+proporcionadas por Linux, pero ante cualquier duda (y hay muchas) por favor
+pregunte. Algunas dudas pueden ser resueltas refiriéndose al modelo de
+consistencia de memoria formal y documentación en tools/memory-model/. Sin
+embargo, incluso este modelo debe ser visto como la opinión colectiva de
+sus maintainers en lugar de que como un oráculo infalible.
+
+De nuevo, este documento no es una especificación de lo que Linux espera
+del hardware.
+
+El propósito de este documento es doble:
+
+ (1) especificar la funcionalidad mínima en la que se puede confiar para
+     cualquier barrera en concreto, y
+
+ (2) proporcionar una guía sobre cómo utilizar las barreras disponibles.
+
+Tenga en cuenta que una arquitectura puede proporcionar más que el
+requisito mínimo para cualquier barrera en particular, pero si la
+arquitectura proporciona menos de eso, dicha arquitectura es incorrecta.
+
+Tenga en cuenta también que es posible que una barrera no valga (sea no-op)
+para alguna arquitectura porque por la forma en que funcione dicha
+arquitectura, la barrera explícita resulte innecesaria en ese caso.
+
+==========
+CONTENIDOS
+==========
+
+ (*) Modelo abstracto de acceso a memoria.
+
+     - Operaciones del dispositivo.
+     - Garantías.
+
+ (*) ¿Qué son las barreras de memoria?
+
+     - Variedades de barrera de memoria.
+     - ¿Qué no se puede asumir sobre las barreras de memoria?
+     - Barreras de dirección-dependencia (históricas).
+     - Dependencias de control.
+     - Emparejamiento de barreras smp.
+     - Ejemplos de secuencias de barrera de memoria.
+     - Barreras de memoria de lectura frente a especulación de carga.
+     - Atomicidad multicopia.
+
+ (*) Barreras explícitas del kernel.
+
+     - Barrera del compilador.
+     - Barreras de memoria de la CPU.
+
+ (*) Barreras de memoria implícitas del kernel.
+
+     - Funciones de adquisición de cerrojo.
+     - Funciones de desactivación de interrupciones.
+     - Funciones de dormir y despertar.
+     - Funciones varias.
+
+ (*) Efectos de barrera adquiriendo intra-CPU.
+
+     - Adquisición vs accesos a memoria.
+
+ (*) ¿Dónde se necesitan barreras de memoria?
+
+     - Interacción entre procesadores.
+     - Operaciones atómicas.
+     - Acceso a dispositivos.
+     - Interrupciones.
+
+ (*) Efectos de barrera de E/S del kernel.
+
+ (*) Modelo de orden mínimo de ejecución asumido.
+
+ (*) Efectos de la memoria caché de la CPU.
+
+     - Coherencia de caché.
+     - Coherencia de caché frente a DMA.
+     - Coherencia de caché frente a MMIO.
+
+ (*) Cosas que hacen las CPU.
+
+     - Y luego está el Alfa.
+     - Guests de máquinas virtuales.
+
+ (*) Ejemplos de usos.
+
+     - Buffers circulares.
+
+ (*) Referencias.
+
+
+====================================
+MODELO ABSTRACTO DE ACCESO A MEMORIA
+====================================
+
+Considere el siguiente modelo abstracto del sistema:
+
+		            :                :
+		            :                :
+		            :                :
+		+-------+   :   +--------+   :   +-------+
+		|       |   :   |        |   :   |       |
+		|       |   :   |        |   :   |       |
+		| CPU 1 |<----->| Memoria|<----->| CPU 2 |
+		|       |   :   |        |   :   |       |
+		|       |   :   |        |   :   |       |
+		+-------+   :   +--------+   :   +-------+
+		    ^       :       ^        :       ^
+		    |       :       |        :       |
+		    |       :       |        :       |
+		    |       :       v        :       |
+		    |       :   +--------+   :       |
+		    |       :   |        |   :       |
+		    |       :   | Disposi|   :       |
+		    +---------->| tivo   |<----------+
+		            :   |        |   :
+		            :   |        |   :
+		            :   +--------+   :
+		            :                :
+
+Cada CPU ejecuta un programa que genera operaciones de acceso a la memoria.
+En la CPU abstracta, el orden de las operaciones de memoria es muy
+relajado, y una CPU en realidad puede realizar las operaciones de memoria
+en el orden que desee, siempre que la causalidad del programa parezca
+mantenerse. De manera similar, el compilador también puede organizar las
+instrucciones que emite en el orden que quiera, siempre que no afecte al
+funcionamiento aparente del programa.
+
+Entonces, en el diagrama anterior, los efectos de las operaciones de
+memoria realizadas por un CPU son percibidos por el resto del sistema a
+medida que las operaciones cruzan la interfaz entre la CPU y el resto del
+sistema (las líneas discontinuas a puntos).
+
+Por ejemplo, considere la siguiente secuencia de eventos:
+
+	CPU 1		CPU 2
+	===============	===============
+	{ A == 1; B == 2 }
+	A = 3;		x = B;
+	B = 4;		y = A;
+
+El conjunto de accesos visto por el sistema de memoria en el medio se puede
+organizar en 24 combinaciones diferentes (donde LOAD es cargar y STORE es
+guardar):
+
+STORE A=3,	STORE B=4,	y=LOAD A->3,	x=LOAD B->4
+STORE A=3,	STORE B=4,	x=LOAD B->4,	y=LOAD A->3
+STORE A=3,	y=LOAD A->3,	STORE B=4,	x=LOAD B->4
+STORE A=3,	y=LOAD A->3,	x=LOAD B->2,	STORE B=4
+STORE A=3,	x=LOAD B->2,	STORE B=4,	y=LOAD A->3
+STORE A=3,	x=LOAD B->2,	y=LOAD A->3,	STORE B=4
+STORE B=4,	STORE A=3,	y=LOAD A->3,	x=LOAD B->4
+STORE B=4, ...
+...
+
+y por lo tanto puede resultar en cuatro combinaciones diferentes de
+valores:
+
+x == 2, y == 1
+x == 2, y == 3
+x == 4, y == 1
+x == 4, y == 3
+
+Además, los stores asignados por una CPU al sistema de memoria pueden no
+ser percibidos por los loads realizados por otra CPU en el mismo orden en
+que fueron realizados.
+
+Como otro ejemplo, considere esta secuencia de eventos:
+
+	CPU 1		CPU 2
+	===============	===============
+	{ A == 1, B == 2, C == 3, P == &A, Q == &C }
+	B = 4;		Q = P;
+	P = &B;		D = *Q;
+
+Aquí hay una dependencia obvia de la dirección, ya que el valor cargado en
+D depende en la dirección recuperada de P por la CPU 2. Al final de la
+secuencia, cualquiera de los siguientes resultados son posibles:
+
+  (Q == &A) y (D == 1)
+  (Q == &B) y (D == 2)
+  (Q == &B) y (D == 4)
+
+Tenga en cuenta que la CPU 2 nunca intentará cargar C en D porque la CPU
+cargará P en Q antes de emitir la carga de *Q.
+
+OPERACIONES DEL DISPOSITIVO
+---------------------------
+
+Algunos dispositivos presentan sus interfaces de control como colecciones
+de ubicaciones de memoria, pero el orden en que se accede a los registros
+de control es muy importante. Por ejemplo, imagine una tarjeta ethernet con
+un conjunto de registros a los que se accede a través de un registro de
+puerto de dirección (A) y un registro de datos del puerto (D). Para leer el
+registro interno 5, el siguiente código podría entonces ser usado:
+
+  *A = 5;
+  x = *D;
+
+pero esto podría aparecer como cualquiera de las siguientes dos secuencias:
+
+  STORE *A = 5, x = LOAD *D
+  x = LOAD *D, STORE *A = 5
+
+el segundo de las cuales casi con certeza resultará en mal funcionamiento,
+ya que se estableció la dirección _después_ de intentar leer el registro.
+
+
+GARANTÍAS
+---------
+
+Hay algunas garantías mínimas que se pueden esperar de una CPU:
+
+ (*) En cualquier CPU dada, los accesos a la memoria dependiente se
+     emitirán en orden, con respeto a sí mismo. Esto significa que para:
+
+	Q = READ_ONCE(P); D = READ_ONCE(*Q);
+
+     donde READ_ONCE() es LEER_UNA_VEZ(), la CPU emitirá las siguientes
+     operaciones de memoria:
+
+	Q = LOAD P, D = LOAD *Q
+
+     y siempre en ese orden. Sin embargo, en DEC Alpha, READ_ONCE() también
+     emite una instrucción de barrera de memoria, de modo que una CPU DEC
+     Alpha, sin embargo emite las siguientes operaciones de memoria:
+
+	Q = LOAD P, MEMORY_BARRIER, D = LOAD *Q, MEMORY_BARRIER
+
+     Ya sea en DEC Alpha o no, READ_ONCE() también evita que el compilador
+     haga cosas inapropiadas.
+
+ (*) Los loads y stores superpuestos dentro de una CPU en particular
+     parecerán ser ordenados dentro de esa CPU. Esto significa que para:
+
+	a = READ_ONCE(*X); WRITE_ONCE(*X, b);
+
+     donde WRITE_ONCE() es ESCRIBIR_UNA_VEZ(), la CPU solo emitirá la
+     siguiente secuencia de operaciones de memoria:
+
+	a = LOAD *X, STORE *X = b
+
+     Y para:
+
+	WRITE_ONCE(*X, c); d = READ_ONCE(*X);
+
+     la CPU solo emitirá:
+
+	STORE *X = c, d = LOAD *X
+
+  (Los loads y stores se superponen si están destinados a piezas
+  superpuestas de memoria).
+
+Y hay una serie de cosas que _deben_ o _no_ deben asumirse:
+
+ (*) _No_debe_ asumirse que el compilador hará lo que usted quiera
+     con referencias de memoria que no están protegidas por READ_ONCE() y
+     WRITE ONCE(). Sin ellos, el compilador tiene derecho a hacer todo tipo
+     de transformaciones "creativas", que se tratan en la sección BARRERA
+     DEL COMPILADOR.
+
+   (*) _No_debe_ suponerse que se emitirán loads y stores independientes
+       en el orden dado. Esto significa que para:
+
+	X = *A; Y = *B; *D = Z;
+
+     podemos obtener cualquiera de las siguientes secuencias:
+
+    X = LOAD *A,  Y = LOAD *B,  STORE *D = Z
+   	X = LOAD *A,  STORE *D = Z, Y = LOAD *B
+   	Y = LOAD *B,  X = LOAD *A,  STORE *D = Z
+   	Y = LOAD *B,  STORE *D = Z, X = LOAD *A
+   	STORE *D = Z, X = LOAD *A,  Y = LOAD *B
+   	STORE *D = Z, Y = LOAD *B,  X = LOAD *A
+
+ (*) Se _debe_ suponer que los accesos de memoria superpuestos pueden
+     fusionarse o ser descartados. Esto significa que para:
+
+	X = *A; Y = *(A + 4);
+
+  podemos obtener cualquiera de las siguientes secuencias:
+
+X = LOAD *A; Y = LOAD *(A + 4);
+Y = LOAD *(A + 4); X = LOAD *A;
+{X, Y} = LOAD {*A, *(A + 4) };
+
+  Y para:
+
+*A = X; *(A + 4) = Y;
+
+  podemos obtener cualquiera de:
+
+STORE *A = X; STORE *(A + 4) = Y;
+STORE *(A + 4) = Y; STORE *A = X;
+STORE {*A, *(A + 4) } = {X, Y};
+
+Y hay anti-garantías:
+
+(*) Estas garantías no se aplican a los campos de bits, porque los
+    compiladores a menudo generan código para modificarlos usando
+    secuencias de lectura-modificación-escritura no atómica. No intente
+    utilizar campos de bits para sincronizar algoritmos paralelos.
+
+(*) Incluso en los casos en que los campos de bits están protegidos por
+    cerrojos (o "cerrojos", o "locks"), todos los componentes en un campo
+    de bits dado deben estar protegidos por un candado. Si dos campos en un
+    campo de bits dado están protegidos por diferentes locks, las
+    secuencias de lectura-modificación-escritura no atómicas del lock
+    pueden causar una actualización a una campo para corromper el valor de
+    un campo adyacente.
+
+(*) Estas garantías se aplican solo a escalares correctamente alineados y
+    dimensionados. De "tamaño adecuado" significa actualmente variables que
+    son del mismo tamaño que "char", "short", "int" y "long".
+    "Adecuadamente alineado" significa la alineación natural, por lo tanto,
+    no hay restricciones para "char", alineación de dos bytes para "short",
+    alineación de cuatro bytes para "int", y alineación de cuatro u ocho
+    bytes para "long", en sistemas de 32 y 64 bits, respectivamente. Tenga
+    en cuenta que estos garantías se introdujeron en el estándar C11, así
+    que tenga cuidado cuando utilice compiladores anteriores a C11 (por
+    ejemplo, gcc 4.6). La parte de la norma que contiene esta garantía es
+    la Sección 3.14, que define "ubicación de memoria" de la siguiente
+    manera:
+
+    ubicación de memoria
+  ya sea un objeto de tipo escalar, o una secuencia máxima
+  de campos de bits adyacentes, todos con ancho distinto de cero
+
+  NOTE 1: Dos hilos de ejecución pueden actualizar y acceder
+  ubicaciones de memoria separadas sin interferir entre
+  ellos.
+
+  NOTE 2: Un campo de bits y un miembro adyacente que no es un campo de
+  bits están en ubicaciones de memoria separadas. Lo mismo sucede con
+  dos campos de bits, si uno se declara dentro de un declaración de
+  estructura anidada y el otro no, o si las dos están separados por una
+  declaración de campo de bits de longitud cero, o si están separados por
+  un miembro no declarado como campo de bits. No es seguro actualizar
+  simultáneamente dos campos de bits en la misma estructura si entre
+  todos los miembros declarados también hay campos de bits, sin importar
+  cuál resulta ser el tamaño de estos campos de bits intermedios.
+
+
+==================================
+¿QUÉ SON LAS BARRERAS DE MEMORIA?
+==================================
+
+Como se puede leer arriba, las operaciones independientes de memoria se
+realizan de manera efectiva en orden aleatorio, pero esto puede ser un
+problema para la interacción CPU-CPU y para la E/S ("I/O"). Lo que se
+requiere es alguna forma de intervenir para instruir al compilador y al
+CPU para restringir el orden.
+
+Las barreras de memoria son este tipo de intervenciones. Imponen una
+percepción de orden parcial, sobre las operaciones de memoria a ambos lados
+de la barrera.
+
+Tal cumplimiento es importante porque las CPUs y otros dispositivos en un
+sistema pueden usar una variedad de trucos para mejorar el rendimiento,
+incluido el reordenamiento, diferimiento y combinación de operaciones de
+memoria; cargas especulativas; predicción de "branches" especulativos y
+varios tipos de almacenamiento en caché. Las barreras de memoria se
+utilizan para anular o suprimir estos trucos, permitiendo que el código
+controle sensatamente la interacción de múltiples CPU y/o dispositivos.
+
+
+VARIEDADES DE BARRERA DE MEMORIA
+---------------------------------
+
+Las barreras de memoria vienen en cuatro variedades básicas:
+
+ (1) Barreras de memoria al escribir o almacenar (Write or store memory
+     barriers).
+
+     Una barrera de memoria de escritura garantiza que todas las
+     operaciones de STORE especificadas antes de que la barrera aparezca
+     suceden antes de todas las operaciones STORE especificadas después
+     de la barrera, con respecto a los otros componentes del sistema.
+
+     Una barrera de escritura es un orden parcial solo en los stores; No
+     es requerido que tenga ningún efecto sobre los loads.
+
+     Se puede considerar que una CPU envía una secuencia de operaciones de
+     store al sistema de memoria a medida que pasa el tiempo. Todos los
+     stores _antes_ de una barrera de escritura ocurrirán _antes_ de todos
+     los stores después de la barrera de escritura.
+
+     [!] Tenga en cuenta que las barreras de escritura normalmente deben
+     combinarse con read o barreras de address-dependency barriers
+     (dependencia de dirección); consulte la subsección
+     "Emparejamiento de barreras smp".
+
+
+ (2) Barrera de dependencia de dirección (histórico).
+
+     Una barrera de dependencia de dirección es una forma más débil de
+     barrera de lectura. En el caso de que se realicen dos loads de manera
+     que la segunda dependa del resultado de la primera (por ejemplo: el
+     primer load recupera la dirección a la que se dirigirá el segundo
+     load), una barrera de dependencia de dirección sería necesaria para
+     asegurarse de que el objetivo de la segunda carga esté actualizado
+     después de acceder a la dirección obtenida por la primera carga.
+
+     Una barrera de dependencia de direcciones es una ordenación parcial en
+     laods de direcciones interdependientes; no se requiere que tenga
+     ningún efecto en los stores, ya sean cargas de memoria o cargas
+     de memoria superpuestas.
+
+     Como se mencionó en (1), las otras CPU en el sistema pueden verse como
+     secuencias de stores en el sistema de memoria que la considerada CPU
+     puede percibir. Una barrera de dependencia de dirección emitida por
+     la CPU en cuestión garantiza que para cualquier carga que la preceda,
+     si esa carga toca alguna secuencia de stores de otra CPU, entonces
+     en el momento en que la barrera se complete, los efectos de todos los
+     stores antes del cambio del load serán perceptibles por cualquier
+     carga emitida después la barrera de la dependencia de la dirección.
+
+     Consulte la subsección "Ejemplos de secuencias de barrera de memoria"
+     para ver los diagramas mostrando las restricciones de orden.
+
+     [!] Tenga en cuenta que la primera carga realmente tiene que tener una
+     dependencia de _dirección_ y no es una dependencia de control. Si la
+     dirección para la segunda carga depende de la primera carga, pero la
+     dependencia es a través de un condicional en lugar de -en realidad-
+     cargando la dirección en sí, entonces es una dependencia de _control_
+     y se requiere una barrera de lectura completa o superior. Consulte la
+     subsección "Dependencias de control" para más información.
+
+     [!] Tenga en cuenta que las barreras de dependencia de dirección
+     normalmente deben combinarse con barreras de escritura; consulte la
+     subsección "Emparejamiento de barreras smp".
+
+     [!] Desde el kernel v5.9, se eliminó la API del kernel para barreras
+     de memoria de direcciones explícitas. Hoy en día, las APIs para marcar
+     cargas de variables compartidas, como READ_ONCE() y rcu_dereference(),
+     proporcionan barreras de dependencia de dirección implícitas.
+
+ (3) Barreras de memoria al leer o cargar (Read or load memory
+    barriers).
+
+     Una barrera de lectura es una barrera de dependencia de direcciones,
+     más una garantía de que todas las operaciones de LOAD especificadas
+     antes de la barrera parecerán ocurrir antes de todas las operaciones
+     de LOAD especificadas después de la barrera con respecto a los demás
+     componentes del sistema.
+
+     Una barrera de lectura es un orden parcial solo en cargas; no es
+     necesario que tenga ningún efecto en los stores.
+
+     Las barreras de memoria de lectura implican barreras de dependencia de
+     direcciones, y por tanto puede sustituirlas por estas.
+
+     [!] Tenga en mente que las barreras de lectura normalmente deben
+     combinarse con barreras de escritura; consulte la subsección
+     "Emparejamiento de barreras smp".
+
+ (4) Barreras de memoria generales
+
+     Una barrera de memoria general proporciona la garantía de que todas
+     las operaciones LOAD y STORE especificadas antes de que la barrera
+     aparezca suceden antes de que todas las operaciones LOAD y STORE
+     especificadas después de la barrera con respecto a los demás
+     componentes del sistema.
+
+     Una barrera de memoria general es un orden parcial tanto en
+     operaciones de carga como de almacenamiento.
+
+     Las barreras de memoria generales implican barreras de memoria tanto
+     de lectura como de escritura, de modo que pueden sustituir a
+     cualquiera.
+
+Y un par de variedades implícitas:
+
+ (5)  ACQUIRE (de adquisición).
+
+     Esto actúa como una barrera permeable unidireccional. Garantiza que
+     toda las operaciones de memoria después de la operación ACQUIRE
+     parezcan suceder después de la ACQUIRE con respecto a los demás
+     componentes del sistema. Las operaciones ACQUIRE incluyen operaciones
+     LOCK y smp_load_acquire(), y operaciones smp_cond_load_acquire().
+
+     Las operaciones de memoria que ocurren antes de una operación ACQUIRE
+     pueden parecer suceder después de que se complete.
+
+     Una operación ACQUIRE casi siempre debe estar emparejada con una
+     operación RELEASE (de liberación).
+
+
+ (6) Operaciones RELEASE (de liberación).
+
+     Esto también actúa como una barrera permeable unidireccional.
+     Garantiza que todas las operaciones de memoria antes de la operación
+     RELEASE parecerán ocurrir antes de la operación RELEASE con respecto a
+     los demás componentes del sistema. Las operaciones de RELEASE incluyen
+     operaciones de UNLOCK y operaciones smp_store_release().
+
+     Las operaciones de memoria que ocurren después de una operación
+     RELEASE pueden parecer suceder antes de que se complete.
+
+     El uso de las operaciones ACQUIRE y RELEASE generalmente excluye la
+     necesidad de otros tipos de barrera de memoria. Además, un par
+     RELEASE+ACQUIRE NO garantiza actuar como una barrera de memoria
+     completa. Sin embargo, después de un ACQUIRE de una variable dada,
+     todos los accesos a la memoria que preceden a cualquier anterior
+     RELEASE en esa misma variable están garantizados como visibles. En
+     otras palabras, dentro de la sección crítica de una variable dada,
+     todos los accesos de todas las secciones críticas anteriores para esa
+     variable habrán terminado de forma garantizada.
+
+     Esto significa que ACQUIRE actúa como una operación mínima de
+     "adquisición" y RELEASE actúa como una operación mínima de
+     "liberación".
+
+Un subconjunto de las operaciones atómicas descritas en atomic_t.txt
+contiene variantes de ACQUIRE y RELEASE, además de definiciones
+completamente ordenadas o relajadas (sin barrera semántica). Para
+composiciones atómicas que realizan tanto un load como store, la semántica
+ACQUIRE se aplica solo a la carga y la semántica RELEASE se aplica sólo a
+la parte de la operación del store.
+
+Las barreras de memoria solo son necesarias cuando existe la posibilidad de
+interacción entre dos CPU o entre una CPU y un dispositivo. Si se puede
+garantizar que no habrá tal interacción en ninguna pieza de código en
+particular, entonces las barreras de memoria son innecesarias en ese
+fragmento de código.
+
+Tenga en cuenta que estas son las garantías _mínimas_. Diferentes
+arquitecturas pueden proporcionar garantías más sustanciales, pero no se
+puede confiar en estas fuera de esa arquitectura en específico.
+
+
+¿QUÉ NO SE PUEDE ASUMIR SOBRE LAS BARRERAS DE LA MEMORIA?
+---------------------------------------------------------
+
+Hay ciertas cosas que las barreras de memoria del kernel Linux no
+garantizan:
+
+ (*) No hay garantía de que ninguno de los accesos a la memoria
+     especificados antes de una barrera de memoria estará _completo_ al
+     completarse una instrucción de barrera de memoria; se puede considerar
+     que la barrera dibuja una línea en la cola de acceso del CPU que no
+     pueden cruzar los accesos del tipo correspondiente.
+
+ (*) No hay garantía de que la emisión de una barrera de memoria en una CPU
+     tenga cualquier efecto directo en otra CPU o cualquier otro hardware
+     en el sistema. El efecto indirecto será el orden en que la segunda CPU
+     ve los efectos de los primeros accesos que ocurren de la CPU, pero lea
+     el siguiente argumento:
+
+ (*) No hay garantía de que una CPU vea el orden correcto de los efectos
+     de los accesos de una segunda CPU, incluso _si_ la segunda CPU usa una
+     barrera de memoria, a menos que la primera CPU _también_ use una
+     barrera de memoria coincidente (vea el subapartado "Emparejamiento de
+     barrera SMP").
+
+ (*) No hay garantía de que alguna pieza intermedia fuera del hardware[*]
+     del CPU no reordenará los accesos a la memoria. Los mecanismos de
+     coherencia de caché del CPU deben propagar los efectos indirectos de
+     una barrera de memoria entre las CPU, pero es posible que no lo hagan
+     en orden.
+
+	[*] Para obtener información sobre bus mastering DMA y coherencia, lea:
+
+	    Documentation/driver-api/pci/pci.rst
+	    Documentation/core-api/dma-api-howto.rst
+	    Documentation/core-api/dma-api.rst
+
+
+BARRERA DE DEPENDENCIA DE DIRECCIÓN (HISTÓRICO)
+-----------------------------------------------
+
+A partir de la versión 4.15 del kernel Linux, se agregó un smp_mb() a
+READ_ONCE() para DEC Alpha, lo que significa que las únicas personas que
+necesitan prestar atención a esta sección son aquellas que trabajan en el
+código específico de la arquitectura DEC Alpha y aquellas que trabajan en
+READ_ONCE() por dentro. Para aquellos que lo necesitan, y para aquellos que
+estén interesados ​​desde un punto de vista histórico, aquí está la historia
+de las barreras de dependencia de dirección.
+
+[!] Si bien las dependencias de direcciones se observan tanto en carga a
+carga como en relaciones de carga a store, las barreras de dependencia de
+dirección no son necesarias para situaciones de carga a store.
+
+El requisito de las barreras de dependencia de dirección es un poco sutil,
+y no siempre es obvio que sean necesarias. Para ilustrar, considere la
+siguiente secuencia de eventos:
+
+	CPU 1		      CPU 2
+	===============	      ===============
+	{ A == 1, B == 2, C == 3, P == &A, Q == &C }
+	B = 4;
+	<barrera de escritura>
+	WRITE_ONCE(P, &B);
+			      Q = READ_ONCE_OLD(P);
+			      D = *Q;
+
+[!] READ_ONCE_OLD() corresponde a READ_ONCE() del kernel anterior a 4.15,
+que no implica una barrera de dependencia de direcciones.
+
+Hay una clara dependencia de dirección aquí, y parecería que al final de
+la secuencia, Q debe ser &A o &B, y que:
+
+	(Q == &A) implica (D == 1)
+	(Q == &B) implica (D == 4)
+
+¡Pero! La percepción de la CPU 2 de P puede actualizarse _antes_ de su
+percepción de B, por lo tanto dando lugar a la siguiente situación:
+
+	(Q == &B) y (D == 2) ????
+
+Si bien esto puede parecer una falla en el mantenimiento de la coherencia
+o la causalidad, no lo es, y este comportamiento se puede observar en
+ciertas CPU reales (como DEC Alfa).
+
+Para lidiar con esto, READ_ONCE() proporciona una barrera de dependencia
+de dirección implícita desde el lanzamiento del kernel v4.15:
+
+	CPU 1		      CPU 2
+	===============	      ===============
+	{ A == 1, B == 2, C == 3, P == &A, Q == &C }
+	B = 4;
+	<barrera de escritura>
+	WRITE_ONCE(P, &B);
+			      Q = READ_ONCE(P);
+			      <barrera de dependencia de dirección implícita>
+			      D = *Q;
+
+Esto refuerza la ocurrencia de una de las dos implicaciones, y previene la
+tercera posibilidad de surgir.
+
+
+[!] Tenga en cuenta que esta situación extremadamente contraria a la
+intuición surge más fácilmente en máquinas con cachés divididos, de modo
+que, por ejemplo, un banco de caché procesa líneas de caché pares y el otro
+banco procesa líneas impares de caché. El puntero P podría almacenarse en
+una línea de caché impar y la variable B podría almacenarse en una línea de
+caché con número par. Entonces, si el banco de números pares de la memoria
+caché de la CPU de lectura está extremadamente ocupado mientras que el
+banco impar está inactivo, uno podría ver el nuevo valor del puntero P
+(&B), pero el antiguo valor de la variable B (2).
+
+
+No se requiere una barrera de dependencia de dirección para ordenar
+escrituras dependientes porque las CPU que admite el kernel Linux no
+escriben hasta que están seguros (1) de que la escritura realmente
+sucederá, (2) de la ubicación de la escritura, y (3) del valor a escribir.
+Pero, por favor, lea atentamente la sección "DEPENDENCIAS DEL CONTROL" y el
+archivo Documentation/RCU/rcu_dereference.rst: el compilador puede romperse
+y romper dependencias en muchas formas altamente creativas.
+
+	CPU 1		      CPU 2
+	===============	      ===============
+	{ A == 1, B == 2, C = 3, P == &A, Q == &C }
+	B = 4;
+	<barrera de escritura>
+	WRITE_ONCE(P, &B);
+			      Q = READ_ONCE_OLD(P);
+			      WRITE_ONCE(*Q, 5);
+
+Por lo tanto, no se requiere ninguna barrera de dependencia de direcciones
+para ordenar la lectura en Q con el load en *Q. En otras palabras, este
+resultado está prohibido, incluso sin una barrera de dependencia de
+dirección implícita del READ_ONCE() moderno:
+
+	(Q == &B) && (B == 4)
+
+Tenga en cuenta que este patrón debe ser raro. Después de todo, el objetivo
+del orden de dependencia es -prevenir- escrituras en la estructura de
+datos, junto con los costosos errores de caché asociados con tales
+escrituras. Este patrón se puede utilizar para registrar raras condiciones
+de error y similares, y el orden natural de las CPUs evita que se pierdan
+tales registros.
+
+
+Tenga en cuenta que el orden proporcionado por una dependencia de dirección
+es local para la CPU que lo contiene. Lea la sección sobre "Atomicidad
+multicopia" para más información.
+
+
+La barrera de dependencia de dirección es muy importante para el sistema
+RCU, por ejemplo. Vea rcu_assign_pointer() y rcu_dereference() en
+include/linux/rcupdate.h. Esto permite que el objetivo actual de un puntero
+RCU sea reemplazado con un nuevo objetivo modificado, sin que el objetivo
+del reemplazo parezca estar inicializado de manera incompleta.
+
+Consulte también la subsección sobre "Coherencia de caché" para obtener un
+ejemplo más completo.
+
+DEPENDENCIAS DE CONTROL
+-----------------------
+
+Las dependencias de control pueden ser un poco complicadas porque los
+compiladores actuales no las entienden. El propósito de esta sección es
+ayudarle a prevenir que la ignorancia del compilador rompa su código.
+
+Una dependencia de control load-load (de carga a carga) requiere una
+barrera de memoria de lectura completa, no simplemente una barrera
+(implícita) de dependencia de direcciones para que funcione correctamente.
+Considere el siguiente fragmento de código:
+
+	q = READ_ONCE(a);
+	<barrera implícita de dependencia de direcciones>
+	if (q) {
+		/* BUG: No hay dependencia de dirección!!! */
+		p = READ_ONCE(b);
+	}
+
+Esto no tendrá el efecto deseado porque no hay una dependencia de dirección
+real, sino más bien una dependencia de control que la CPU puede
+cortocircuitar al intentar predecir el resultado por adelantado, para que
+otras CPU vean la carga de b como si hubiera ocurrido antes que la carga de
+a. En cuyo caso lo que realmente se requiere es:
+
+  	q = READ_ONCE(a);
+  	if (q) {
+  		<barrera de lectura>
+  		p = READ_ONCE(b);
+  	}
+
+Sin embargo, los stores no se especulan. Esto significa que ordenar -es-
+provisto para dependencias de control de load-store, como en el siguiente
+ejemplo:
+
+	q = READ_ONCE(a);
+	if (q) {
+		WRITE_ONCE(b, 1);
+	}
+
+Las dependencias de control se emparejan normalmente con otros tipos de
+barreras. Dicho esto, tenga en cuenta que ni READ_ONCE() ni WRITE_ONCE()
+son opcionales! Sin READ_ONCE(), el compilador podría combinar la carga de
+'a' con otras cargas de 'a'. Sin WRITE_ONCE(), el compilador podría
+combinar el store de 'b' con otros stores de 'b'. Cualquiera de estos casos
+puede dar lugar a efectos en el orden muy contrarios a la intuición.
+
+Peor aún, si el compilador puede probar (decir) que el valor de la
+variable 'a' siempre es distinta de cero, estaría dentro de sus derechos
+para optimizar el ejemplo original eliminando la declaración "if", como:
+
+	q = a;
+	b = 1;  /* BUG: Compilador y CPU pueden ambos reordernar!!! */
+
+Así que no deje de lado READ_ONCE().
+
+Es tentador tratar de hacer cumplir el orden en stores idénticos en ambos
+caminos del "if" de la siguiente manera:
+
+	q = READ_ONCE(a);
+	if (q) {
+		barrier();
+		WRITE_ONCE(b, 1);
+		hacer_algo();
+	} else {
+		barrier();
+		WRITE_ONCE(b, 1);
+		hacer_otra_cosa();
+	}
+
+Desafortunadamente, los compiladores actuales transformarán esto de la
+siguiente manera en casos de alto nivel de optimización:
+
+  	q = READ_ONCE(a);
+  	barrier();
+  	WRITE_ONCE(b, 1);  /* BUG: No hay orden en load de a!!! */
+  	if (q) {
+  		/* WRITE_ONCE(b, 1); -- movido arriba, BUG!!! */
+  		hacer_algo();
+  	} else {
+  		/* WRITE_ONCE(b, 1); -- movido arriba, BUG!!! */
+  		hacer_otra_cosa();
+  	}
+
+Ahora no hay condicional entre la carga de 'a' y el store de 'b', lo que
+significa que la CPU está en su derecho de reordenarlos: El condicional es
+absolutamente necesario y debe estar presente en el código ensamblador
+incluso después de que se hayan aplicado todas las optimizaciones del
+compilador. Por lo tanto, si necesita ordenar en este ejemplo, necesita
+explícitamente barreras de memoria, por ejemplo, smp_store_release():
+
+
+	q = READ_ONCE(a);
+	if (q) {
+		smp_store_release(&b, 1);
+		hacer_algo();
+	} else {
+		smp_store_release(&b, 1);
+		hacer_otra_cosa();
+	}
+
+Por el contrario, sin barreras de memoria explícita, el control de un if
+con dos opciones está garantizado solo cuando los stores difieren, por
+ejemplo:
+
+  	q = READ_ONCE(a);
+  	if (q) {
+  		WRITE_ONCE(b, 1);
+  		hacer_algo();
+  	} else {
+  		WRITE_ONCE(b, 2);
+  		hacer_otra_cosa();
+  	}
+
+Aún se requiere el inicial READ_ONCE() para evitar que el compilador toque
+el valor de 'a'.
+
+Además, debe tener cuidado con lo que hace con la variable local 'q', de lo
+contrario, el compilador podría adivinar el valor y volver a eliminar el
+necesario condicional. Por ejemplo:
+
+  	q = READ_ONCE(a);
+  	if (q % MAX) {
+  		WRITE_ONCE(b, 1);
+  		hacer_algo();
+  	} else {
+  		WRITE_ONCE(b, 2);
+  		hacer_otra_cosa();
+  	}
+
+Si MAX se define como 1, entonces el compilador sabe que (q % MAX) es igual
+a cero, en cuyo caso el compilador tiene derecho a transformar el código
+anterior en el siguiente:
+
+  	q = READ_ONCE(a);
+  	WRITE_ONCE(b, 2);
+  	hacer_otra_cosa();
+
+Dada esta transformación, la CPU no está obligada a respetar el orden entre
+la carga de la variable 'a' y el store de la variable 'b'. Es tentador
+agregar una barrier(), pero esto no ayuda. El condicional se ha ido, y la
+barrera no lo traerá de vuelta. Por lo tanto, si confia en este orden, debe
+asegurarse de que MAX sea mayor que uno, tal vez de la siguiente manera:
+
+  	q = READ_ONCE(a);
+  	BUILD_BUG_ON(MAX <= 1); /* Orden de carga de a con store de b */
+  	if (q % MAX) {
+  		WRITE_ONCE(b, 1);
+  		hacer_algo();
+  	} else {
+  		WRITE_ONCE(b, 2);
+  		hacer_otra_cosa();
+  	}
+
+Tenga en cuenta una vez más que los stores de 'b' difieren. Si fueran
+idénticos, como se señaló anteriormente, el compilador podría sacar ese
+store fuera de la declaración 'if'.
+
+También debe tener cuidado de no confiar demasiado en el cortocircuito
+de la evaluación booleana. Considere este ejemplo:
+
+  	q = READ_ONCE(a);
+  	if (q || 1 > 0)
+  	WRITE_ONCE(b, 1);
+
+Debido a que la primera condición no puede fallar y la segunda condición es
+siempre cierta, el compilador puede transformar este ejemplo de la
+siguiente manera, rompiendo la dependencia del control:
+
+  	q = READ_ONCE(a);
+  	WRITE_ONCE(b, 1);
+
+Este ejemplo subraya la necesidad de asegurarse de que el compilador no
+pueda adivinar su código. Más generalmente, aunque READ_ONCE() fuerza
+al compilador para emitir código para una carga dada, no fuerza al
+compilador para usar los resultados.
+
+Además, las dependencias de control se aplican solo a la cláusula then y
+la cláusula else de la sentencia if en cuestión. En particular, no se
+aplica necesariamente al código que sigue a la declaración if:
+
+  	q = READ_ONCE(a);
+  	if (q) {
+  		WRITE_ONCE(b, 1);
+  	} else {
+  		WRITE_ONCE(b, 2);
+  	}
+  	WRITE_ONCE(c, 1);  /* BUG: No hay orden para la lectura de 'a'. */
+
+Es tentador argumentar que, de hecho, existe un orden porque el compilador
+no puede reordenar accesos volátiles y tampoco puede reordenar escrituras
+en 'b' con la condición. Desafortunadamente para esta línea de
+razonamiento, el compilador podría compilar las dos escrituras en 'b' como
+instrucciones de movimiento condicional, como en este fantástico idioma
+pseudo-ensamblador:
+
+        	ld r1,a
+        	cmp r1,$0
+        	cmov,ne r4,$1
+        	cmov,eq r4,$2
+        	st r4,b
+        	st $1,c
+
+Una CPU débilmente ordenada no tendría dependencia de ningún tipo entre la
+carga de 'a' y el store de 'c'. Las dependencias de control se extenderían
+solo al par de instrucciones cmov y el store dependiente de ellas. En
+resumen, las dependencias de control se aplican solo a los stores en la
+cláusula then y la cláusula else de la sentencia if en cuestión (incluidas
+las funciones invocado por esas dos cláusulas), no al código que sigue a
+esa declaración if.
+
+
+Tenga muy en cuenta que el orden proporcionado por una dependencia de
+control es local a la CPU que lo contiene. Vea el apartado de "Atomicidad
+multicopia" para más información.
+
+
+En resumen:
+
+  (*) Las dependencias de control pueden ordenar cargas anteriores para
+      stores posteriores. Sin embargo, no garantizan ningún otro tipo de
+      orden: No cargas previas contra cargas posteriores, ni
+      almacenamientos previos y luego nada. Si necesita tales formas de
+      orden, use smp_rmb(), smp_wmb() o, en el caso de stores anteriores y
+      cargas posteriores, smp_mb().
+
+  (*) Si ambos caminos de la declaración "if" comienzan con stores
+      idénticos de la misma variable, entonces esos stores deben ser
+      ordenados, ya sea precediéndoles a ambos con smp_mb() o usando
+      smp_store_release() para realizar el store. Tenga en cuenta que -no-
+      es suficiente usar barrier() al comienzo de cada caso de la
+      declaración "if" porque, como se muestra en el ejemplo anterior, la
+      optimización de los compiladores puede destruir la dependencia de
+      control respetando al pie de la letra la ley de barrier().
+
+  (*) Las dependencias de control requieren al menos un condicional en
+      tiempo de ejecución entre la carga anterior y el almacenamiento
+      posterior, y este condicional debe implicar la carga previa. Si el
+      compilador es capaz de optimizar el condicional y quitarlo, también
+      habrá optimizado el ordenar. El uso cuidadoso de READ_ONCE() y
+      WRITE_ONCE() puede ayudar a preservar el necesario condicional.
+
+  (*) Las dependencias de control requieren que el compilador evite
+      reordenar las dependencia hasta su inexistencia. El uso cuidadoso de
+      READ_ONCE() o atomic{,64}_read() puede ayudarle a preservar la
+      dependencia de control. Consulte la sección BARRERA DEL COMPILADOR
+      para obtener más información al respecto.
+
+  (*) Las dependencias de control se aplican solo a la cláusula then y la
+      cláusula else de la sentencia "if" que contiene la dependencia de
+      control, incluyendo cualquier función a la que llamen dichas dos
+      cláusulas. Las dependencias de control no se aplican al código que
+      sigue a la instrucción if que contiene la dependencia de control.
+
+  (*) Las dependencias de control se emparejan normalmente con otros tipos
+      de barreras.
+
+  (*) Las dependencias de control no proporcionan atomicidad multicopia. Si
+      usted necesita todas las CPU para ver un store dado al mismo tiempo,
+      emplee smp_mb().
+
+  (*) Los compiladores no entienden las dependencias de control. Por lo
+      tanto es su trabajo asegurarse de que no rompan su código.
+
+
+EMPAREJAMIENTO DE BARRERAS SMP
+------------------------------
+
+Cuando se trata de interacciones CPU-CPU, ciertos tipos de barrera de
+memoria deben estar siempre emparejados. La falta del apropiado
+emparejamiento es casi seguro un error.
+
+Las barreras generales se emparejan entre sí, aunque también se emparejan
+con la mayoría de otro tipo de barreras, aunque sin atomicidad multicopia.
+Una barrera de adquisición se empareja con una barrera de liberación, pero
+ambas también pueden emparejarse con otras barreras, incluidas, por
+supuesto, las barreras generales. Una barrera de escritura se empareja con
+una barrera de dependencia de dirección, una dependencia de control, una
+barrera de adquisición, una barrera de liberación, una barrera de lectura
+o una barrera general. Del mismo modo, una barrera de lectura se empareja
+con una de dependencia de control o barrera de dependencia de dirección con
+una barrera de escritura, una barrera de adquisición, una barrera de
+liberación o una barrera general:
+
+	CPU 1		      CPU 2
+	===============	      ===============
+	WRITE_ONCE(a, 1);
+	<barrera de escritura>
+	WRITE_ONCE(b, 2);     x = READ_ONCE(b);
+			      <barrera de lectura>
+			      y = READ_ONCE(a);
+
+O bien:
+
+	CPU 1		      CPU 2
+	===============	      ===============================
+	a = 1;
+	<barrera de escritura>
+	WRITE_ONCE(b, &a);    x = READ_ONCE(b);
+			      <barrera de dependencia de dirección implícita>
+			      y = *x;
+
+O incluso:
+
+	CPU 1		      CPU 2
+	===============	      ===============================
+	r1 = READ_ONCE(y);
+	<barrera general>
+	WRITE_ONCE(x, 1);     if (r2 = READ_ONCE(x)) {
+			         <barrera de control implícita>
+			         WRITE_ONCE(y, 1);
+			      }
+
+	assert(r1 == 0 || r2 == 0);
+
+Básicamente, la barrera de lectura siempre tiene que estar ahí, aunque
+puede ser del tipo "más débil".
+
+[!] Tenga en cuenta que normalmente se esperaría que los stores antes de la
+barrera de escritura se hagan coincidir con los stores después de la
+barrera de lectura o la barrera de dependencia de dirección, y viceversa:
+
+	CPU 1                               CPU 2
+	===================                 ===================
+	WRITE_ONCE(a, 1);    }----   --->{  v = READ_ONCE(c);
+	WRITE_ONCE(b, 2);    }    \ /    {  w = READ_ONCE(d);
+	<barrera de escritura>            \        <barrera de lectura>
+	WRITE_ONCE(c, 3);    }    / \    {  x = READ_ONCE(a);
+	WRITE_ONCE(d, 4);    }----   --->{  y = READ_ONCE(b);
+
+
+EJEMPLOS DE SECUENCIAS DE BARRERA DE MEMORIA
+--------------------------------------------
+
+En primer lugar, las barreras de escritura actúan como orden parcial en las
+operaciones de store. Considere la siguiente secuencia de eventos:
+
+	CPU 1
+	=======================
+	STORE A = 1
+	STORE B = 2
+	STORE C = 3
+	<barrera de escritura>
+	STORE D = 4
+	STORE E = 5
+
+Esta secuencia de eventos es finalizado para con el sistema de coherencia
+de memoria en un orden que el resto del sistema podría percibir como el
+conjunto desordenado { STORE A, STORE B, STORE C} todo ocurriendo antes del
+conjunto desordenado { STORE D, STORE E}:
+
+
+	+-------+       :      :
+	|       |       +------+
+	|       |------>| C=3  |     }     /\
+	|       |  :    +------+     }-----  \  -----> Eventos perceptibles para
+	|       |  :    | A=1  |     }        \/       el resto del sistema
+	|       |  :    +------+     }
+	| CPU 1 |  :    | B=2  |     }
+	|       |       +------+     }
+	|       |   wwwwwwwwwwwwwwww }   <--- En este momento la barrera de
+	|       |       +------+     }        escritura requiere que todos los
+	|       |  :    | E=5  |     }        stores anteriores a la barrera
+	|       |  :    +------+     }        sean confirmados antes de que otros
+	|       |------>| D=4  |     }        store puedan suceder
+	|       |       +------+
+	+-------+       :      :
+	                   |
+	                   | Secuencia por la cual los stores son confirmados al
+	                   | sistema de memoria por parte del CPU 1
+	                   V
+
+En segundo lugar, las barreras de dependencia de dirección actúan como
+órdenes parciales sobre la dirección de cargas dependientes. Considere la
+siguiente secuencia de eventos:
+
+	CPU 1			CPU 2
+	=======================	=======================
+		{ B = 7; X = 9; Y = 8; C = &Y }
+	STORE A = 1
+	STORE B = 2
+	<barrera de escritura>
+	STORE C = &B		LOAD X
+	STORE D = 4		LOAD C (consigue &B)
+				LOAD *C (lee B)
+
+Sin intervención, la CPU 2 puede percibir los eventos en la CPU 1 en orden
+aleatorio a efectos prácticos, a pesar de la barrera de escritura emitida
+por la CPU 1:
+
+	+-------+       :      :                :       :
+	|       |       +------+                +-------+  | Secuencia de
+	|       |------>| B=2  |-----       --->| Y->8  |  | actualizado de
+	|       |  :    +------+     \          +-------+  | percepción en CPU 2
+	| CPU 1 |  :    | A=1  |      \     --->| C->&Y |  V
+	|       |       +------+       |        +-------+
+	|       |   wwwwwwwwwwwwwwww   |        :       :
+	|       |       +------+       |        :       :
+	|       |  :    | C=&B |---    |        :       :       +-------+
+	|       |  :    +------+   \   |        +-------+       |       |
+	|       |------>| D=4  |    ----------->| C->&B |------>|       |
+	|       |       +------+       |        +-------+       |       |
+	+-------+       :      :       |        :       :       |       |
+	                               |        :       :       |       |
+	                               |        :       :       | CPU 2 |
+	                               |        +-------+       |       |
+	    Percepción de B      --->  |        | B->7  |------>|       |
+	    aparentemente incorrecta!  |        +-------+       |       |
+	                               |        :       :       |       |
+	                               |        +-------+       |       |
+	    La carga de X frena --->    \       | X->9  |------>|       |
+	    el mantenimiento de          \      +-------+       |       |
+	    la coherencia de B            ----->| B->2  |       +-------+
+	                                        +-------+
+	                                        :       :
+
+
+En el ejemplo anterior, la CPU 2 percibe que B es 7, a pesar de la carga de
+*C (que sería B) viniendo después del LOAD de C.
+
+Sin embargo, si se colocara una barrera de dependencia de dirección entre
+la carga de C y la carga de *C (es decir: B) en la CPU 2:
+
+	CPU 1			CPU 2
+	=======================	=======================
+		{ B = 7; X = 9; Y = 8; C = &Y }
+	STORE A = 1
+	STORE B = 2
+	<barrera de escritura>
+	STORE C = &B		LOAD X
+	STORE D = 4		LOAD C (consigue &B)
+				<barrera de dependencia de dirección>
+				LOAD *C (reads B)
+
+entonces ocurrirá lo siguiente:
+
+	+-------+       :      :                :       :
+	|       |       +------+                +-------+
+	|       |------>| B=2  |-----       --->| Y->8  |
+	|       |  :    +------+     \          +-------+
+	| CPU 1 |  :    | A=1  |      \     --->| C->&Y |
+	|       |       +------+       |        +-------+
+	|       |   wwwwwwwwwwwwwwww   |        :       :
+	|       |       +------+       |        :       :
+	|       |  :    | C=&B |---    |        :       :       +-------+
+	|       |  :    +------+   \   |        +-------+       |       |
+	|       |------>| D=4  |    ----------->| C->&B |------>|       |
+	|       |       +------+       |        +-------+       |       |
+	+-------+       :      :       |        :       :       |       |
+	                               |        :       :       |       |
+	                               |        :       :       | CPU 2 |
+	                               |        +-------+       |       |
+	                               |        | X->9  |------>|       |
+	                               |        +-------+       |       |
+	  Se asegura de que      --->   \   aaaaaaaaaaaaaaaaa   |       |
+	  los efectos anteriores al      \      +-------+       |       |
+	  store de C sean percibidos      ----->| B->2  |------>|       |
+	  por los siguientes loads              +-------+       |       |
+	                                        :       :       +-------+
+
+
+Y en tercer lugar, una barrera de lectura actúa como un orden parcial sobre
+las cargas. Considere la siguiente secuencia de eventos:
+
+	CPU 1			CPU 2
+	=======================	=======================
+		{ A = 0, B = 9 }
+	STORE A=1
+	<barrera de escritura>
+	STORE B=2
+				LOAD B
+				LOAD A
+
+Sin intervención, la CPU 2 puede elegir percibir los eventos en la CPU 1 en
+algún orden aleatorio a efectos prácticos, a pesar de la barrera de
+escritura emitida por la CPU 1:
+
+	+-------+       :      :                :       :
+	|       |       +------+                +-------+
+	|       |------>| A=1  |------      --->| A->0  |
+	|       |       +------+      \         +-------+
+	| CPU 1 |   wwwwwwwwwwwwwwww   \    --->| B->9  |
+	|       |       +------+        |       +-------+
+	|       |------>| B=2  |---     |       :       :
+	|       |       +------+   \    |       :       :       +-------+
+	+-------+       :      :    \   |       +-------+       |       |
+	                             ---------->| B->2  |------>|       |
+	                                |       +-------+       | CPU 2 |
+	                                |       | A->0  |------>|       |
+	                                |       +-------+       |       |
+	                                |       :       :       +-------+
+	                                 \      :       :
+	                                  \     +-------+
+	                                   ---->| A->1  |
+	                                        +-------+
+	                                        :       :
+
+Sin embargo, si se colocara una barrera de lectura entre la carga de B y la
+carga de A en la CPU 2:
+
+	CPU 1			CPU 2
+	=======================	=======================
+		{ A = 0, B = 9 }
+	STORE A=1
+	<barrera de escritura>
+	STORE B=2
+				LOAD B
+				<barrera de lectura>
+				LOAD A
+
+entonces el orden parcial impuesto por la CPU 1 será percibido
+correctamente por la CPU 2:
+
+	+-------+       :      :                :       :
+	|       |       +------+                +-------+
+	|       |------>| A=1  |------      --->| A->0  |
+	|       |       +------+      \         +-------+
+	| CPU 1 |   wwwwwwwwwwwwwwww   \    --->| B->9  |
+	|       |       +------+        |       +-------+
+	|       |------>| B=2  |---     |       :       :
+	|       |       +------+   \    |       :       :       +-------+
+	+-------+       :      :    \   |       +-------+       |       |
+	                             ---------->| B->2  |------>|       |
+	                                |       +-------+       | CPU 2 |
+	                                |       :       :       |       |
+	                                |       :       :       |       |
+	  En este punto la barrera ----> \  rrrrrrrrrrrrrrrrr   |       |
+	  de lectura consigue que         \     +-------+       |       |
+	  todos los efectos anteriores     ---->| A->1  |------>|       |
+	  al almacenamiento de B sean           +-------+       |       |
+	  perceptibles por la CPU 2             :       :       +-------+
+
+
+Para ilustrar esto de manera más completa, considere lo que podría pasar si
+el código contenía una carga de A a cada lado de la barrera de lectura:
+
+	CPU 1			CPU 2
+	=======================	=======================
+		{ A = 0, B = 9 }
+	STORE A=1
+	<barrera de escritura>
+	STORE B=2
+				LOAD B
+				LOAD A [primer load de A]
+				<rbarrera de lectura>
+				LOAD A [segundo load de A]
+
+Aunque las dos cargas de A ocurren después de la carga de B, ambas pueden
+obtener diferentes valores:
+
+	+-------+       :      :                :       :
+	|       |       +------+                +-------+
+	|       |------>| A=1  |------      --->| A->0  |
+	|       |       +------+      \         +-------+
+	| CPU 1 |   wwwwwwwwwwwwwwww   \    --->| B->9  |
+	|       |       +------+        |       +-------+
+	|       |------>| B=2  |---     |       :       :
+	|       |       +------+   \    |       :       :       +-------+
+	+-------+       :      :    \   |       +-------+       |       |
+	                             ---------->| B->2  |------>|       |
+	                                |       +-------+       | CPU 2 |
+	                                |       :       :       |       |
+	                                |       :       :       |       |
+	                                |       +-------+       |       |
+	                                |       | A->0  |------>| 1st   |
+	                                |       +-------+       |       |
+	  En este punto la barrera ----> \  rrrrrrrrrrrrrrrrr   |       |
+	  de lectura consigue que         \     +-------+       |       |
+	  todos los efectos anteriores     ---->| A->1  |------>|       |
+	  al almacenamiento de B sean           +-------+       |       |
+	  perceptibles por la CPU 2             :       :       +-------+
+
+Pero puede ser que la actualización a A desde la CPU 1 se vuelva
+perceptible para la CPU 2 antes de que la barrera de lectura se complete de
+todos modos:
+
+	+-------+       :      :                :       :
+	|       |       +------+                +-------+
+	|       |------>| A=1  |------      --->| A->0  |
+	|       |       +------+      \         +-------+
+	| CPU 1 |   wwwwwwwwwwwwwwww   \    --->| B->9  |
+	|       |       +------+        |       +-------+
+	|       |------>| B=2  |---     |       :       :
+	|       |       +------+   \    |       :       :       +-------+
+	+-------+       :      :    \   |       +-------+       |       |
+	                             ---------->| B->2  |------>|       |
+	                                |       +-------+       | CPU 2 |
+	                                |       :       :       |       |
+	                                 \      :       :       |       |
+	                                  \     +-------+       |       |
+	                                   ---->| A->1  |------>| 1st   |
+	                                        +-------+       |       |
+	                                    rrrrrrrrrrrrrrrrr   |       |
+	                                        +-------+       |       |
+	                                        | A->1  |------>| 2nd   |
+	                                        +-------+       |       |
+	                                        :       :       +-------+
+
+La garantía es que la segunda carga siempre dará como resultado A == 1 si
+la carga de B resultó en B == 2. No existe tal garantía para la primera
+carga de A; esto puede dar como resultado A == 0 o A == 1.
+
+
+BARRERAS DE MEMORIA DE LECTURA FRENTE A ESPECULACIÓN DE CARGA
+-------------------------------------------------------------
+
+Muchas CPU especulan con las cargas: es decir, ven que necesitarán cargar
+un elemento de la memoria, y encuentran un momento en el que no están
+usando el bus para ningún otra carga, y también en la carga por adelantado,
+aunque en realidad no lo hayan llegado a ese punto en el flujo de ejecución
+de instrucciones todavía. Esto permite que la instrucción de carga real
+potencialmente complete de inmediato, porque la CPU ya tiene el valor a
+mano.
+
+Puede resultar que la CPU en realidad no necesitara el valor, tal vez
+porque una condición eludió la carga, en cuyo caso puede descartar el valor
+o simplemente almacenar en caché para su uso posterior.
+
+Considere:
+
+	CPU 1			CPU 2
+	=======================	=======================
+				LOAD B
+				DIVIDE		} Instrucciones de división
+				DIVIDE		} tardan mucho en terminar
+				LOAD A
+
+donde DIVIDE es DIVIDIR. Que podría aparecer como esto:
+
+	                                        :       :       +-------+
+	                                        +-------+       |       |
+	                                    --->| B->2  |------>|       |
+	                                        +-------+       | CPU 2 |
+	                                        :       :DIVIDE |       |
+	                                        +-------+       |       |
+	La CPU ocupada con la división ---> --->| A->0  |~~~~   |       |
+	especula sobre el LOAD de A             +-------+   ~   |       |
+	                                        :       :   ~   |       |
+	                                        :       :DIVIDE |       |
+	                                        :       :   ~   |       |
+	Una vez completadas las divisiones  --> :       :   ~-->|       |
+	la CPU puede realizar el                :       :       |       |
+	LOAD con efecto inmediato               :       :       +-------+
+
+
+Colocando una barrera de lectura o una barrera de dependencia de dirección
+justo antes de la segundo carga:
+
+
+
+	CPU 1			CPU 2
+	=======================	=======================
+				LOAD B
+				DIVIDE
+				DIVIDE
+				<rbarrera de lectura>
+				LOAD A
+
+obligará a reconsiderar cualquier valor obtenido especulativamente en una
+medida dependiente del tipo de barrera utilizada. Si no se hizo ningún
+cambio en la ubicación de memoria especulada, entonces el valor especulado
+solo se usará:
+
+	                                        :       :       +-------+
+	                                        +-------+       |       |
+	                                    --->| B->2  |------>|       |
+	                                        +-------+       | CPU 2 |
+	                                        :       :DIVIDE |       |
+	                                        +-------+       |       |
+  La CPU ocupada con la división ---> --->| A->0  |~~~~   |       |
+  especula sobre el LOAD de A             +-------+   ~   |       |
+	                                        :       :   ~   |       |
+	                                        :       :DIVIDE |       |
+	                                        :       :   ~   |       |
+	                                        :       :   ~   |       |
+	                                    rrrrrrrrrrrrrrrr~   |       |
+	                                        :       :   ~   |       |
+	                                        :       :   ~-->|       |
+	                                        :       :       |       |
+	                                        :       :       +-------+
+
+
+pero si había una actualización o una invalidación de otra CPU pendiente,
+entonces la especulación será cancelada y el valor recargado:
+
+	                                        :       :       +-------+
+	                                        +-------+       |       |
+	                                    --->| B->2  |------>|       |
+	                                        +-------+       | CPU 2 |
+	                                        :       :DIVIDE |       |
+	                                        +-------+       |       |
+  La CPU ocupada con la división ---> --->| A->0  |~~~~   |       |
+  especula sobre el LOAD de A             +-------+   ~   |       |
+	                                        :       :   ~   |       |
+	                                        :       :DIVIDE |       |
+	                                        :       :   ~   |       |
+	                                        :       :   ~   |       |
+	                                    rrrrrrrrrrrrrrrrr   |       |
+	                                        +-------+       |       |
+	La especulación es descartada --->  --->| A->1  |------>|       |
+	y un valor actualizado                  +-------+       |       |
+	es conseguido                           :       :       +-------+
+
+ATOMICIDAD MULTICOPIA
+---------------------
+
+La atomicidad multicopia es una noción profundamente intuitiva sobre el
+orden que no es siempre proporcionada por los sistemas informáticos reales,
+a saber, que un determinada store se vuelve visible al mismo tiempo para
+todos las CPU o, alternativamente, que todas las CPU acuerdan el orden en
+que todos los stores se vuelven visibles. Sin embargo, el soporte para
+atomicidad multicopia completa descartaría valiosas optimizaciones
+hardware, por lo que una versión más débil conocida como ``otra atomicidad
+multicopia'' en cambio, solo garantiza que un store dado se vuelva visible
+al mismo tiempo en todas las -otras- CPUs. El resto de este documento
+discute esta versión más débil, pero por brevedad lo llamaremos simplemente
+``atomicidad multicopia''.
+
+El siguiente ejemplo demuestra la atomicidad multicopia:
+
+	CPU 1			CPU 2			CPU 3
+	=======================	=======================	=======================
+		{ X = 0, Y = 0 }
+	STORE X=1		r1=LOAD X (reads 1)	LOAD Y (reads 1)
+				<barrera general>	<barrera de lectura>
+				STORE Y=r1		LOAD X
+
+Suponga que la carga de la CPU 2 desde X devuelve 1, que luego almacena en
+Y, y la carga de la CPU 3 desde Y devuelve 1. Esto indica que el store de
+la CPU 1 a X precede a la carga de la CPU 2 desde X y el store de esa CPU 2
+a Y precede la carga de la CPU 3 desde Y. Además, las barreras de memoria
+garantizan que la CPU 2 ejecuta su carga antes que su almacenamiento, y la
+CPU 3 carga desde Y antes de cargar desde X. La pregunta entonces es
+"¿Puede la carga de la CPU 3 desde X devolver 0?"
+
+Debido a que la carga de la CPU 3 desde X en cierto sentido viene después
+de la carga de la CPU 2, es natural esperar que la carga de la CPU 3 desde
+X deba devolver 1. Esta expectativa se deriva de la atomicidad multicopia:
+si una carga que se ejecuta en la CPU B sigue una carga de la misma
+variable que se ejecuta en la CPU A (y la CPU A no almacenó originalmente
+el valor que leyó), entonces en sistemas atómicos multicopia, la carga de
+la CPU B debe devolver el mismo valor que hizo la carga de la CPU A o algún
+valor posterior. Sin embargo, el kernel Linux no requiere que los sistemas
+sean atómicos multicopia.
+
+El uso de una barrera de memoria general en el ejemplo anterior compensa
+cualquier falta de atomicidad multicopia. En el ejemplo, si la carga de la
+CPU 2 de X devuelve 1 y la carga de la CPU 3 de Y devuelve 1, entonces la
+carga de la CPU 3 desde X debe de hecho también devolver 1.
+
+Sin embargo, las dependencias, las barreras de lectura y las barreras de
+escritura no siempre son capaces de compensar la atomicidad no multicopia.
+Por ejemplo, supongamos que la barrera general de la CPU 2 se elimina del
+ejemplo anterior, dejando solo la dependencia de datos que se muestra a
+continuación:
+
+	CPU 1			CPU 2			CPU 3
+	=======================	=======================	=======================
+		{ X = 0, Y = 0 }
+	STORE X=1		r1=LOAD X (escribe 1)	LOAD Y (lee 1)
+				<dependencia de datos>	<barrera de lectura>
+				STORE Y=r1		LOAD X (lee 0)
+
+Esta sustitución permite que la atomicidad no multicopia se desenfrene: en
+este ejemplo, es perfectamente legal que la carga de la CPU 2 desde X
+devuelva 1, la carga de la CPU 3 desde Y devuelva 1, y su carga desde X
+tenga valor 0.
+
+El punto clave es que aunque la dependencia de datos de la CPU 2 ordena su
+carga y store, no garantiza ordenar el store de la CPU 1. De forma que, si
+este ejemplo se ejecuta en un sistema atómico no multicopia donde las CPU 1
+y 2 comparten un buffer de almacenamiento o un nivel de caché, la CPU 2
+podría tener acceso anticipado de escritura a CPU 1. Por lo tanto, se
+requieren barreras generales para garantizar que todas las CPU acurden el
+orden combinado de accesos múltiples.
+
+Las barreras generales pueden compensar no solo la atomicidad no
+multicopia, pero también pueden generar orden adicional que puede asegurar
+que -todas- las CPU percibirán el mismo orden de -todas- las operaciones.
+Por el contrario, una cadena de parejas de liberación-adquisición no
+proporciona este orden adicional, lo que significa que solo se garantiza
+que las CPU de la cadena estén de acuerdo en el orden combinado de los
+accesos. Por ejemplo, cambiando a código C en deferencia al fantasma de
+Herman Hollerith:
+
+	int u, v, x, y, z;
+
+	void cpu0(void)
+	{
+		r0 = smp_load_acquire(&x);
+		WRITE_ONCE(u, 1);
+		smp_store_release(&y, 1);
+	}
+
+	void cpu1(void)
+	{
+		r1 = smp_load_acquire(&y);
+		r4 = READ_ONCE(v);
+		r5 = READ_ONCE(u);
+		smp_store_release(&z, 1);
+	}
+
+	void cpu2(void)
+	{
+		r2 = smp_load_acquire(&z);
+		smp_store_release(&x, 1);
+	}
+
+	void cpu3(void)
+	{
+		WRITE_ONCE(v, 1);
+		smp_mb();
+		r3 = READ_ONCE(u);
+	}
+
+Dado que cpu0(), cpu1() y cpu2() participan en una cadena de parejas
+smp_store_release()/smp_load_acquire(), el siguiente resultado estaría
+prohibido:
+
+	r0 == 1 && r1 == 1 && r2 == 1
+
+Además, debido a la relación liberación-adquisición entre cpu0() y cpu1(),
+cpu1() debe ver las escrituras de cpu0(), de modo que el siguiente
+resultado estaría prohibido:
+
+	r1 == 1 && r5 == 0
+
+Sin embargo, el orden proporcionado por una cadena de
+liberación-adquisición es local a las CPU que participan en esa cadena y no
+se aplica a cpu3(), al menos aparte de los stores. Por lo tanto, es posible
+el siguiente resultado:
+
+	r0 == 0 && r1 == 1 && r2 == 1 && r3 == 0 && r4 == 0
+
+Por otro lado, también el siguiente resultado es posible:
+
+	r0 == 0 && r1 == 1 && r2 == 1 && r3 == 0 && r4 == 0 && r5 == 1
+
+Aunque cpu0(), cpu1() y cpu2() verán sus respectivas lecturas y escrituras
+en orden, las CPU que no participan en la cadena de liberación-adquisición
+pueden estar en desacuerdo con el orden. Este desacuerdo se debe al hecho
+de que las instrucciones de barrera de memoria débiles utilizadas para
+implementar smp_load_acquire() y smp_store_release() no son necesarios para
+ordenar stores anteriores contra cargas posteriores en todos los casos.
+Esto significa que cpu3() puede ver el store de cpu0() suceder -después- de
+la carga de cpu1() desde v, aunque tanto cpu0() como cpu1() están de
+acuerdo en que estas dos operaciones ocurrieron en el orden previsto.
+
+Sin embargo, tenga en cuenta que smp_load_acquire() no es mágico. En
+particular, simplemente lee de su argumento en orden. Es decir, -no-
+asegura que se leerá cualquier valor en particular. Por lo tanto, los
+siguiente resultados son posibles:
+
+	r0 == 0 && r1 == 0 && r2 == 0 && r5 == 0
+
+Tenga en cuenta que este resultado puede ocurrir incluso en un mítico
+sistema, consistente en secuencia, donde nunca se reordena nada.
+
+Para reiterar, si su código requiere un orden completo de todas las
+operaciones, utilice barreras generales en todo momento.
+
+
+==============================
+BARRERAS EXPLÍCITAS DEL KERNEL
+==============================
+
+El kernel Linux tiene una variedad de diferentes barreras que actúan a
+diferentes niveles:
+
+  (*) Barrera del compilador.
+
+  (*) Barreras de memoria de la CPU.
+
+
+BARRERA DEL COMPILADOR
+-----------------------
+
+El kernel de Linux tiene una función de barrera del compilador explícita
+que evita que el el compilador mueva los accesos a la memoria de cualquier
+lado al otro:
+
+	barrier();
+
+Esta es una barrera general: no hay variantes de barrier() para casos de
+lectura-lectura o escritura-escritura. Sin embargo, READ_ONCE() y
+WRITE_ONCE() pueden ser considerado como formas débiles de barrier() que
+afectan solo específicos accesos marcados por READ_ONCE() o WRITE_ONCE().
+
+La función barrier() produce los siguientes efectos:
+
+ (*) Evita que el compilador reordene los accesos tras barrier() para
+     preceder a cualquier acceso que preceda a barrier(). Un ejemplo de uso
+     de esta propiedad es facilitar la comunicación entre código del
+     interrupt-handler (encargo de gestionar interrupciones) y el código
+     que fue interrumpido.
+
+ (*) Dentro de un bucle ("loop"), obliga al compilador a cargar las
+     variables utilizadas en ese loop condicional en cada paso a través de
+     ese loop.
+
+Las funciones READ_ONCE() y WRITE_ONCE() pueden evitar cualquier cantidad
+de optimizaciones que, si bien son perfectamente seguras en código de un
+solo subproceso, pueden resultar fatales en código concurrente. Aquí hay
+algunos ejemplos de tal tipo de optimizaciones:
+
+(*) El compilador está en su derecho de reordenar cargas y stores de la
+    misma variable, y en algunos casos, la CPU está dentro de su
+    derecho de reordenar cargas a la misma variable. Esto significa que
+    el siguiente código:
+
+ a[0] = x;
+ a[1] = x;
+
+     Podría resultar en un valor más antiguo de x almacenado en a[1] que en
+     a[0]. Evite que tanto el compilador como la CPU hagan esto de la
+     siguiente manera:
+
+	a[0] = READ_ONCE(x);
+	a[1] = READ_ONCE(x);
+
+     En resumen, READ_ONCE() y WRITE_ONCE() proporcionan coherencia de
+     caché para accesos desde múltiples CPUs a una sola variable.
+
+     (*) El compilador tiene derecho a juntar cargas sucesivas de la misma
+         variable. Tal fusión puede hacer que el compilador "optimice" el
+         siguiente código:
+
+    	while (tmp = a)
+    		hacer_algo_con(tmp);
+
+        en el siguiente código, que, aunque en cierto sentido es legítimo
+        para un código de un solo subproceso, es casi seguro que no es lo
+        que el desarrollador pretendía:
+
+   	if (tmp = a)
+   		for (;;)
+   			hacer_algo_con(tmp);
+
+        Use READ_ONCE() para evitar que el compilador le haga esto:
+
+   	while (tmp = READ_ONCE(a))
+   		hacer_algo_con(tmp);
+
+ (*) El compilador tiene derecho a recargar una variable, por ejemplo,
+    en los casos en que la alta presión de los registros impida que el
+    compilador mantenga todos los datos de interés en registros. El
+    compilador podría por lo tanto, optimizar la variable 'tmp' de nuestro
+    ejemplo anterior:
+
+	while (tmp = a)
+		hacer_algo_con(tmp);
+
+     Esto podría resultar en el siguiente código, que es perfectamente
+     seguro en código de subproceso único, pero puede ser fatal en código
+     concurrente:
+
+	while (a)
+		hacer_algo_con(a);
+
+    Por ejemplo, la versión optimizada de este código podría resultar en
+    pasar un cero a hacer_algo_con() en el caso de que la variable a sea
+    modificada por alguna otra CPU, entre la instrucción "while" y la
+    llamada a hacer_algo_con().
+
+    De nuevo, use READ_ONCE() para evitar que el compilador haga esto:
+
+	while (tmp = READ_ONCE(a))
+		hacer_algo_con(tmp);
+
+     Tenga en cuenta que si el compilador se queda sin registros, podría
+     guardar tmp en la pila ("stack"). El overhead (coste en eficiencia) de
+     este guardado y posterior restauración es por lo que los compiladores
+     recargan las variables. Hacerlo es perfectamente seguro para código de
+     subproceso único, por lo que debe informar al compilador sobre los
+     casos donde no sea seguro.
+
+ (*) El compilador está en su derecho de omitir una carga por completo si
+     sabe cual será su valor. Por ejemplo, si el compilador puede probar
+     que el valor de la variable 'a' siempre es cero, puede optimizar este
+     código:
+
+	while (tmp = a)
+		hacer_algo_con(tmp);
+
+     En esto:
+
+	do { } while (0);
+
+     Esta transformación es una victoria para un código de un solo
+     subproceso, porque se deshace de una carga y un branch. El problema es
+     que el compilador llevará a cabo su prueba asumiendo que la CPU actual
+     es la única actualizando la variable 'a'. Si la variable 'a' es
+     compartida, entonces la prueba del compilador será errónea. Use
+     READ_ONCE() para decirle al compilador que no sabe tanto como cree:
+
+	while (tmp = READ_ONCE(a))
+		hacer_algo_con(tmp);
+
+     Pero, por favor, tenga en cuenta que el compilador también está
+     observando de cerca lo que usted hace con el valor después de
+     READ_ONCE(). Por ejemplo, suponga que Ud. hace lo siguiente y MAX es
+     una macro de preprocesador con el valor 1:
+
+	while ((tmp = READ_ONCE(a)) % MAX)
+		hacer_algo_con(tmp);
+
+     Entonces el compilador sabe que el resultado del operador "%" aplicado
+     a MAX siempre será cero, nuevamente permitiendo que el compilador
+     optimice el código hasta su casi inexistencia. (Aún se cargará desde
+     la variable 'a'.)
+
+ (*) De manera similar, el compilador tiene derecho a omitir un store por
+     completo si sabe que la variable ya tiene el valor almacenado.
+     Nuevamente, el compilador asume que la CPU actual es la única que
+     almacena la variable, lo que puede hacer que el compilador haga
+     algo incorrecto para las variables compartidas. Por ejemplo, suponga
+     que tiene lo siguiente:
+
+	a = 0;
+	... Código que no almacena la variable a ...
+	a = 0;
+
+     El compilador observa que el valor de la variable 'a' ya es cero, por
+     lo que bien podría omitir el segundo store. Esto supondría una fatal
+     sorpresa, si alguna otra CPU hubiera almacenado la variable 'a'
+     mientras tanto.
+
+     Use WRITE_ONCE() para evitar que el compilador haga este tipo de
+     suposición equivocada:
+
+	WRITE_ONCE(a, 0);
+	... Código que no almacena la variable a  ...
+	WRITE_ONCE(a, 0);
+
+  (*) El compilador tiene derecho a reordenar los accesos a memoria a menos
+     que le diga que no. Por ejemplo, considere la siguiente interacción
+     entre el código de nivel de proceso y un controlador de interrupción:
+
+	void nivel_de_procesamiento(void)
+	{
+		msg = ACQUIRE_mensaje();
+		flag = true;
+	}
+
+	void controlador_interrupcion(void)
+	{
+		if (flag)
+			procesar_mensaje(msg);
+	}
+
+     No hay nada que impida que el compilador transforme
+     nivel_de_procesamiento() a lo siguiente, que de hecho, bien podría ser
+     una victoria para código de un solo subproceso:
+
+	void nivel_de_procesamiento(void)
+	{
+		flag = true;
+		msg = ACQUIRE_mensaje();
+	}
+
+     Si la interrupción ocurre entre estas dos declaraciones, entonces
+     controlador_interrupcion() podría recibir un mensaje ilegible. Use
+     READ_ONCE() para evitar esto de la siguiente manera:
+
+	void nivel_de_procesamiento(void)
+	{
+		WRITE_ONCE(msg, ACQUIRE_mensaje());
+		WRITE_ONCE(flag, true);
+	}
+
+	void controlador_interrupcion(void)
+	{
+		if (READ_ONCE(flag))
+			procesar_mensaje(READ_ONCE(msg));
+	}
+
+     Tenga en cuenta que los envoltorios ("wrappers") READ_ONCE() y
+     WRITE_ONCE() en controlador_interrupcion() son necesarios si este
+     controlador de interrupciones puede ser interrumpido por algo que
+     también accede a 'flag' y 'msg', por ejemplo, una interrupción anidada
+     o un NMI. De lo contrario, READ_ONCE() y WRITE_ONCE() no son
+     necesarios en controlador_interrupcion() aparte de con fines de
+     documentación. (Tenga también en cuenta que las interrupciones
+     anidadas no ocurren típicamente en los kernels Linux modernos, de
+     hecho, si un controlador de interrupciones regresa con interrupciones
+     habilitadas, obtendrá un WARN_ONCE().)
+
+     Debe suponer que el compilador puede mover READ_ONCE() y WRITE_ONCE()
+     a código que no contiene READ_ONCE(), WRITE_ONCE(), barrier(), o
+     primitivas similares.
+
+     Este efecto también podría lograrse usando barrier(), pero READ_ONCE()
+     y WRITE_ONCE() son más selectivos: Con READ_ONCE() y WRITE_ONCE(), el
+     compilador solo necesita olvidar el contenido de ubicaciones de
+     memoria indicadas, mientras que con barrier() el compilador debe
+     descartar el valor de todas las ubicaciones de memoria que tiene
+     actualmente almacenadas en caché, en cualquier registro de la máquina.
+     Por supuesto, el compilador también debe respetar el orden en que
+     ocurren READ_ONCE() y WRITE_ONCE(), aunque la CPU, efectivamente, no
+     necesita hacerlo.
+
+ (*) El compilador tiene derecho a inventar stores para una variable,
+     como en el siguiente ejemplo:
+
+	if (a)
+		b = a;
+	else
+		b = 42;
+
+    El compilador podría ahorrar un branch al optimizar esto de la
+    siguiente manera:
+
+    	b = 42;
+    	if (a)
+    		b = a;
+
+     En el código de un solo subproceso, esto no solo es seguro, sino que
+     también ahorra un branch. Desafortunadamente, en código concurrente,
+     esta optimización podría causar que alguna otra CPU vea un valor falso
+     de 42, incluso si la variable 'a' nunca fue cero, al cargar la
+     variable 'b'. Use WRITE_ONCE() para evitar esto de la siguiente
+     manera:
+
+	if (a)
+		WRITE_ONCE(b, a);
+	else
+		WRITE_ONCE(b, 42);
+
+    El compilador también puede inventar cargas. Estos casos suelen ser
+    menos perjudiciales, pero pueden dar como resultado "bouncing" de la
+    línea de caché y, por lo tanto, bajo rendimiento y escalabilidad.
+    Utilice READ_ONCE() para evitar cargas inventadas.
+
+ (*) Para ubicaciones de memoria alineadas cuyo tamaño les permita
+     acceder con una sola instrucción de referencia de memoria, evite el
+     "desgarro de la carga" (load tearing) y "desgarro del store" (store
+     tearing), en el que un solo gran acceso es reemplazado por múltiples
+     accesos menores. Por ejemplo, dada una arquitectura que tiene
+     instrucciones de almacenamiento de 16 bits con campos inmediatos de 7
+     bits, el compilador podría tener la tentación de usar dos
+     instrucciones inmediatas de almacenamiento de 16 bits para implementar
+     el siguiente store de 32 bits:
+
+	p = 0x00010002;
+
+     Tenga en cuenta que GCC realmente usa este tipo de optimización, lo
+     cual no es sorprendente dado que probablemente costaría más de dos
+     instrucciones el construir la constante y luego almacenarla. Por lo
+     tanto, esta optimización puede ser una victoria en un código de un
+     solo subproceso. De hecho, un error reciente (desde que se solucionó)
+     hizo que GCC usara incorrectamente esta optimización en un store
+     volátil. En ausencia de tales errores, el uso de WRITE_ONCE() evita el
+     desgarro del store en el siguiente ejemplo:
+
+	struct __attribute__((__packed__)) foo {
+		short a;
+		int b;
+		short c;
+	};
+	struct foo foo1, foo2;
+	...
+
+	foo2.a = foo1.a;
+	foo2.b = foo1.b;
+	foo2.c = foo1.c;
+
+     Debido a que no hay envoltorios READ_ONCE() o WRITE_ONCE() y no
+     hay markings volátiles, el compilador estaría en su derecho de
+     implementar estas tres declaraciones de asignación como un par de
+     cargas de 32 bits, seguido de un par de stores de 32 bits. Esto
+     resultaría en una carga con desgarro en 'foo1.b' y store del desgarro
+     en 'foo2.b'. READ_ONCE() y WRITE_ONCE() nuevamente evitan el desgarro
+     en este ejemplo:
+
+	foo2.a = foo1.a;
+	WRITE_ONCE(foo2.b, READ_ONCE(foo1.b));
+	foo2.c = foo1.c;
+
+Aparte de esto, nunca es necesario usar READ_ONCE() y WRITE_ONCE() en una
+variable que se ha marcado como volátil. Por ejemplo, dado que 'jiffies'
+está marcado como volátil, nunca es necesario usar READ_ONCE(jiffies). La
+razón de esto es que READ_ONCE() y WRITE_ONCE() se implementan como
+conversiones volátiles, lo que no tiene efecto cuando su argumento ya está
+marcado como volátil.
+
+Tenga en cuenta que estas barreras del compilador no tienen un efecto
+directo en la CPU, que luego puede reordenar las cosas como quiera.
+
+
+BARRERAS DE MEMORIA DE LA CPU
+-----------------------------
+
+El kernel de Linux tiene siete barreras básicas de memoria de CPU:
+
+TIPO			OBLIGATORIO	SMP CONDICIONAL
+=======================	===============	===============
+GENERAL			mb()		smp_mb()
+WRITE			wmb()		smp_wmb()
+READ			rmb()		smp_rmb()
+DEPEDENCIA DE DIRECCIÓN			READ_ONCE()
+
+
+Todas las barreras de memoria, excepto las barreras de dependencia de
+direcciones, implican una barrera del compilador. Las dependencias de
+direcciones no imponen ningún orden de compilación adicional.
+
+Además: en el caso de las dependencias de direcciones, se esperaría que el
+compilador emita las cargas en el orden correcto (por ejemplo, `a[b]`
+tendría que cargar el valor de b antes de cargar a[b]), sin embargo, no hay
+garantía alguna en la especificación de C sobre que el compilador no puede
+especular el valor de b (por ejemplo, es igual a 1) y carga a[b] antes que
+b (ej. tmp = a[1]; if (b != 1) tmp = a[b]; ). También existe el problema de
+que un compilador vuelva a cargar b después de haber cargado a[b], teniendo
+así una copia más nueva de b que a[b]. Aún no se ha conseguido un consenso
+acerca de estos problemas, sin embargo, el macro READ_ONCE() es un buen
+lugar para empezar a buscar.
+
+Las barreras de memoria SMP se reducen a barreras de compilador cuando se
+compila a monoprocesador, porque se supone que una CPU parecerá ser
+auto-consistente, y ordenará correctamente los accesos superpuestos
+respecto a sí misma. Sin embargo, consulte la subsección "Guests de
+máquinas virtuales" mas adelante.
+
+[!] Tenga en cuenta que las barreras de memoria SMP _deben_ usarse para
+controlar el orden de referencias a memoria compartida en sistemas SMP,
+aunque el uso de bloqueo en su lugar sea suficiente.
+
+Las barreras obligatorias no deben usarse para controlar los efectos de
+SMP, ya que dichas barreras imponen una sobrecarga innecesaria en los
+sistemas SMP y UP. Se pueden, sin embargo, usar para controlar los efectos
+MMIO en los accesos a través de ventanas E/S de memoria relajada. Estas
+barreras son necesarias incluso en sistemas que no son SMP, ya que afectan
+al orden en que las operaciones de memoria aparecen en un dispositivo, al
+prohibir tanto al compilador como a la CPU que sean reordenados.
+
+
+Hay algunas funciones de barrera más avanzadas:
+
+ (*) smp_store_mb(var, valor)
+
+     Asigna el valor a la variable y luego inserta una barrera de memoria
+     completa después de ella. No se garantiza insertar nada más que una
+     barrera del compilador en una compilación UP.
+
+
+ (*) smp_mb__before_atomic();
+ (*) smp_mb__after_atomic();
+
+     Estos se pueden usar con funciones RMW atómicas que no implican
+     barreras de memoria, pero donde el código necesita una barrera de
+     memoria. Ejemplos de funciones RMW atómicas que no implican una
+     barrera de memoria son, por ejemplo, agregar, restar, operaciones
+     condicionales (fallidas), funciones _relaxed, pero no atomic_read o
+     atomic_set. Un ejemplo común donde se puede requerir una barrera es
+     cuando se usan operaciones atómicas como referencia de contador.
+
+     Estos también se utilizan para funciones atómicas RMW bitop que no
+     implican una barrera de memoria (como set_bit y clear_bit).
+
+     Como ejemplo, considere una pieza de código que marca un objeto como
+     muerto y luego disminuye el contador de referencias del objeto:
+
+	obj->dead = 1;
+	smp_mb__before_atomic();
+	atomic_dec(&obj->ref_count);
+
+     Esto asegura que la marca de muerte en el objeto se perciba como
+     fijada *antes* de que disminuya el contador de referencia.
+
+     Consulte Documentation/atomic_{t,bitops}.txt para obtener más
+     información.
+
+
+ (*) dma_wmb();
+ (*) dma_rmb();
+ (*) dma_mb();
+
+     Estos son usados con memoria consistente para garantizar el orden de
+     escrituras o lecturas de memoria compartida accesible tanto para la
+     CPU como para un dispositivo compatible con DMA.
+
+     Por ejemplo, considere un controlador de dispositivo que comparte
+     memoria con otro dispositivo y usa un valor de estado del descriptor
+     para indicar si el descriptor pertenece al dispositivo o a la CPU, y
+     un "doorbell" (timbre, punto de acceso) para avisarle cuando haya
+     nuevos descriptores disponibles:
+
+	if (desc->status != DEVICE_OWN) {
+		/* no leer los datos hasta que tengamos el descriptor */
+		dma_rmb();
+
+		/* leer/modificar datos */
+		read_data = desc->data;
+		desc->data = write_data;
+
+		/* flush de modificaciones antes de la actualización de estado */
+		dma_wmb();
+
+		/* asignar propiedad */
+		desc->status = DEVICE_OWN;
+
+		/* notificar al dispositivo de nuevos descriptores */
+		writel(DESC_NOTIFY, doorbell);
+	}
+
+     El dma_rmb() nos permite garantizar que el dispositivo ha liberado su
+     propiedad antes de que leamos los datos del descriptor, y el dma_wmb()
+     permite garantizar que los datos se escriben en el descriptor antes de
+     que el dispositivo pueda ver que ahora tiene la propiedad. El dma_mb()
+     implica tanto un dma_rmb() como un dma_wmb(). Tenga en cuenta que, al
+     usar writel(), no se necesita un wmb() anterior para garantizar que
+     las escrituras de la memoria caché coherente se hayan completado antes
+     escribiendo a la región MMIO. El writel_relaxed() más barato no
+     proporciona esta garantía y no debe utilizarse aquí.
+
+     Consulte la subsección "Efectos de barrera de E/S del kernel" para
+     obtener más información sobre accesorios de E/S relajados y el archivo
+     Documentation/core-api/dma-api.rst para más información sobre memoria
+     consistente.
+
+ (*) pmem_wmb();
+
+     Es es para uso con memoria persistente para garantizar que los stores
+     para los que las modificaciones se escriben en el almacenamiento
+     persistente llegaron a dominio de durabilidad de la plataforma.
+
+     Por ejemplo, después de una escritura no temporal en la región pmem,
+     usamos pmem_wmb() para garantizar que los stores hayan alcanzado el
+     dominio de durabilidad de la plataforma. Esto garantiza que los stores
+     han actualizado el almacenamiento persistente antes de cualquier
+     acceso a datos o transferencia de datos causada por instrucciones
+     posteriores. Esto es además del orden realizado por wmb().
+
+     Para la carga desde memoria persistente, las barreras de memoria de
+     lectura existentes son suficientes para garantizar el orden de
+     lectura.
+
+ (*) io_stop_wc();
+
+     Para accesos a memoria con atributos de combinación de escritura (por
+     ejemplo, los devueltos por ioremap_wc(), la CPU puede esperar a que
+     los accesos anteriores se junten con posteriores. io_stop_wc() se
+     puede utilizar para evitar la combinación de accesos a memoria de
+     de escritura antes de esta macro, con los posteriores, cuando dicha
+     espera tenga implicaciones en el rendimiento.
+
+=========================================
+BARRERAS DE MEMORIA IMPLÍCITAS DEL KERNEL
+=========================================
+
+Algunas de las otras funciones en el kernel Linux implican barreras de
+memoria, entre estas encontramos funciones de bloqueo y planificación
+("scheduling").
+
+Esta especificación es una garantía _mínima_; cualquier arquitectura
+particular puede proporcionar garantías más sustanciales, pero no se puede
+confiar en estas fuera de código específico de arquitectura.
+
+
+FUNCIONES DE ADQUISICIÓN DE CERROJO
+-----------------------------------
+
+El kernel Linux tiene una serie de abstracciones de bloqueo:
+
+ (*) spin locks (cerrojos en loop)
+ (*) R/W spin lock (cerrojos de escritura/lectura)
+ (*) mutex
+ (*) semáforos
+ (*) R/W semáforos
+
+En todos los casos existen variantes de las operaciones "ACQUIRE" y
+"RELEASE" para cada uno de ellos. Todas estas operaciones implican ciertas
+barreras:
+
+ (1) Implicaciones de la operación ACQUIRE:
+
+     Las operaciones de memoria emitidas después del ACQUIRE se completarán
+     después de que la operación ACQUIRE haya finalizado.
+
+     Las operaciones de memoria emitidas antes de ACQUIRE pueden
+     completarse después que la operación ACQUIRE se ha completado.
+
+ (2) Implicaciones de la operación RELEASE:
+
+     Las operaciones de memoria emitidas antes de la RELEASE se
+     completarán antes de que la operación de RELEASE se haya completado.
+
+     Las operaciones de memoria emitidas después de la RELEASE pueden
+     completarse antes de que la operación de RELEASE se haya completado.
+
+ (3) Implicación de ACQUIRE vs ACQUIRE:
+
+     Todas las operaciones ACQUIRE emitidas antes de otra operación
+     ACQUIRE serán completadas antes de esa operación ACQUIRE.
+
+ (4) Implicación de ACQUIRE vs RELEASE:
+
+     Todas las operaciones ACQUIRE emitidas antes de una operación RELEASE
+     serán completadas antes de la operación RELEASE.
+
+ (5) Implicación de ACQUIRE condicional fallido:
+
+     Ciertas variantes de bloqueo de la operación ACQUIRE pueden fallar, ya
+     sea debido a no poder obtener el bloqueo de inmediato, o debido a que
+     recibieron una señal de desbloqueo mientras dormían esperando que el
+     cerrojo estuviera disponible. Los fallos en cerrojos no implican
+     ningún tipo de barrera.
+
+[!] Nota: una de las consecuencias de que los cerrojos en ACQUIRE y RELEASE
+sean barreras unidireccionales, es que los efectos de las instrucciones
+fuera de una sección crítica pueden filtrarse al interior de la sección
+crítica.
+
+No se puede suponer que un ACQUIRE seguido de una RELEASE sea una barrera
+de memoria completa dado que es posible que un acceso anterior a ACQUIRE
+suceda después del ACQUIRE, y un acceso posterior a la RELEASE suceda antes
+del RELEASE, y los dos accesos puedan entonces cruzarse:
+
+	*A = a;
+	ACQUIRE M
+	RELEASE M
+	*B = b;
+
+puede ocurrir como:
+
+	ACQUIRE M, STORE *B, STORE *A, RELEASE M
+
+Cuando ACQUIRE y RELEASE son bloqueo de adquisición y liberación,
+respectivamente, este mismo orden puede ocurrir si el cerrojo ACQUIRE y
+RELEASE son para la misma variable de bloqueo, pero solo desde la
+perspectiva de otra CPU que no tiene ese bloqueo. En resumen, un ACQUIRE
+seguido de un RELEASE NO puede entenderse como una barrera de memoria
+completa.
+
+De manera similar, el caso inverso de un RELEASE seguido de un ACQUIRE no
+implica una barrera de memoria completa. Por lo tanto, la ejecución de la
+CPU de los tramos críticos correspondientes a la RELEASE y la ACQUIRE
+pueden cruzarse, de modo que:
+
+	*A = a;
+	RELEASE M
+	ACQUIRE N
+	*B = b;
+
+puede ocurrir como:
+
+	ACQUIRE N, STORE *B, STORE *A, RELEASE M
+
+Podría parecer que este nuevo orden podría introducir un punto muerto.
+Sin embargo, esto no puede suceder porque si tal punto muerto amenazara
+con suceder, el RELEASE simplemente se completaría, evitando así el
+interbloqueo ("deadlock", punto muerto).
+
+	¿Por qué funciona esto?
+
+	Un punto clave es que solo estamos hablando de la CPU re-haciendo el
+  orden, no el compilador. Si el compilador (o, ya puestos, el
+  desarrollador) cambió las operaciones, un deadlock -podría- ocurrir.
+
+	Pero supongamos que la CPU reordenó las operaciones. En este caso, el
+	desbloqueo precede al bloqueo en el código ensamblador. La CPU
+  simplemente eligió intentar ejecutar primero la última operación de
+  bloqueo. Si hay un interbloqueo, esta operación de bloqueo simplemente
+  esperará (o tratará de dormir, pero hablaremos de eso más adelante). La
+  CPU eventualmente ejecutará la operación de desbloqueo (que precedió a la
+	operación de bloqueo en el código ensamblador), lo que desenmascará el
+  potencial punto muerto, permitiendo que la operación de bloqueo tenga
+  éxito.
+
+	Pero, ¿y si el cerrojo es un cerrojo que duerme ("sleeplock")? En tal
+  caso, el código intentará entrar al scheduler, donde eventualmente
+  encontrará una barrera de memoria, que forzará la operación de desbloqueo
+  anterior para completar, nuevamente desentrañando el punto muerto. Podría
+	haber una carrera de desbloqueo del sueño ("sleep-unlock race"), pero la
+  primitiva de bloqueo necesita resolver tales carreras correctamente en
+  cualquier caso.
+
+Es posible que los cerrojos y los semáforos no proporcionen ninguna
+garantía de orden en sistemas compilados en UP, por lo que no se puede
+contar con tal situación para lograr realmente nada en absoluto,
+especialmente con respecto a los accesos de E/S, a menos que se combinen
+con operaciones de inhabilitación de interrupciones.
+
+Consulte también la sección "Efectos de barrera adquiriendo intra-CPU".
+
+
+Como ejemplo, considere lo siguiente:
+
+	*A = a;
+	*B = b;
+	ACQUIRE
+	*C = c;
+	*D = d;
+	RELEASE
+	*E = e;
+	*F = f;
+
+La siguiente secuencia de eventos es aceptable:
+
+	ACQUIRE, {*F,*A}, *E, {*C,*D}, *B, RELEASE
+
+  [+] Tenga en cuenta que {*F,*A} indica un acceso combinado.
+
+Pero ninguno de los siguientes lo son:
+
+	{*F,*A}, *B,	ACQUIRE, *C, *D,	RELEASE, *E
+	*A, *B, *C,	ACQUIRE, *D,		RELEASE, *E, *F
+	*A, *B,		ACQUIRE, *C,		RELEASE, *D, *E, *F
+	*B,		ACQUIRE, *C, *D,	RELEASE, {*F,*A}, *E
+
+
+
+FUNCIONES DE DESACTIVACIÓN DE INTERRUPCIONES
+--------------------------------------------
+
+Las funciones que deshabilitan interrupciones (equivalentes a ACQUIRE) y
+habilitan interrupciones (equivalentes a RELEASE) actuarán únicamente como
+barrera del compilador. Por consiguiente, si la memoria o la E/S requieren
+barreras en tal situación, deben ser provistas por algún otro medio.
+
+
+FUNCIONES DE DORMIR Y DESPERTAR
+-------------------------------
+
+Dormir y despertar son eventos marcados ("flagged") en los datos globales
+que se pueden ver como una interacción entre dos piezas de datos: el estado
+de la task (hilo, proceso, tarea) que espera el evento y los datos globales
+utilizados para indicar el evento. Para asegurarse de que estos parezcan
+suceder en el orden correcto, las primitivas para comenzar el proceso de ir
+a dormir, y las primitivas para iniciar un despertar implican ciertas
+barreras.
+
+En primer lugar, el agente durmiente normalmente sigue algo similar a esta
+secuencia de eventos:
+
+	for (;;) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		if (evento_indicado)
+			break;
+		schedule(); // planificar
+	}
+
+Una barrera de memoria general se obtiene automáticamente mediante
+set_current_state() después de haber alterado el estado de la tarea:
+
+	CPU 1
+	===============================
+	set_current_state(); // hacer_estado_actual()
+	  smp_store_mb();
+	    STORE current->state
+	    <barrera general>
+	LOAD evento_indicado
+
+set_current_state() puede estar envuelto por:
+
+	prepare_to_wait(); // preparese_para_esperar();
+	prepare_to_wait_exclusive(); // prepararse_para_solo_esperar();
+
+que por lo tanto también implican una barrera de memoria general después de
+establecer el estado. Toda la secuencia anterior está disponible en varias
+formas, todas las cuales obtienen la barrera de memoria en el lugar
+correcto:
+
+	wait_event();
+	wait_event_interruptible();
+	wait_event_interruptible_exclusive();
+	wait_event_interruptible_timeout();
+	wait_event_killable();
+	wait_event_timeout();
+	wait_on_bit();
+	wait_on_bit_lock();
+
+
+En segundo lugar, el código que realiza una activación normalmente se
+asemeja a algo como esto:
+
+	evento_indicado = 1;
+	wake_up(&event_wait_queue); // despertar
+
+o:
+
+	evento_indicado = 1;
+	wake_up_process(event_daemon); // despertar proceso
+
+wake_up() ejecuta una barrera de memoria general si despierta algo. Si no
+despierta nada, entonces una barrera de memoria puede o no ser ejecutada;
+no debe confiar en ello. La barrera se produce antes del acceso al estado
+de la tarea. En particular, se encuentra entre el STORE para indicar el
+evento y el STORE para configurar TASK_RUNNING (hilo ejecutando):
+
+	CPU 1 (Durmiente)			CPU 2 (Despertadora)
+	===============================	===============================
+	set_current_state();		STORE evento_indicado
+	  smp_store_mb();		wake_up();
+	    STORE current->state	  ...
+	    <barrera general>		  <barrera general>
+	LOAD evento_indicado		  if ((LOAD task->state) & TASK_NORMAL)
+					    STORE task->state
+
+donde "task" es el subproceso que se está despertando y es igual al
+"current" (hilo actual) de la CPU 1.
+
+Para reiterar, se garantiza la ejecución de una barrera de memoria general
+mediante wake_up() si algo está realmente despierto, pero de lo contrario
+no existe tal garantía. Para entender esto, considere la siguiente
+secuencia de eventos, donde X e Y son ambos cero inicialmente:
+
+	CPU 1				CPU 2
+	===============================	===============================
+	X = 1;				Y = 1;
+	smp_mb();			wake_up();
+	LOAD Y				LOAD X
+
+Si ocurre una reactivación ("wakeup"), una (al menos) de las dos cargas
+debe ver 1. Si, por otro lado, no ocurre una reactivación, ambas cargas
+pueden ver 0.
+
+wake_up_process() siempre ejecuta una barrera de memoria general. La
+barrera, de nuevo, ocurre antes de que se acceda al estado del hilo. En
+particular, si wake_up(), en el fragmento anterior, fuera reemplazado por
+una llamada a wake_up_process(), las dos cargas verían 1, garantizado.
+
+Las funciones de activación disponibles incluyen:
+
+	complete();
+	wake_up();
+	wake_up_all();
+	wake_up_bit();
+	wake_up_interruptible();
+	wake_up_interruptible_all();
+	wake_up_interruptible_nr();
+	wake_up_interruptible_poll();
+	wake_up_interruptible_sync();
+	wake_up_interruptible_sync_poll();
+	wake_up_locked();
+	wake_up_locked_poll();
+	wake_up_nr();
+	wake_up_poll();
+	wake_up_process();
+
+En términos de orden de la memoria, todas estas funciones proporcionan las
+mismas garantías que un wake_up() (o más fuertes).
+
+[!] Tenga en cuenta que las barreras de la memoria implicadas por el
+durmiente y el despierto _no_ ordenan varios stores antes del despertar con
+respecto a cargas de los valores guardados después de que el durmiente haya
+llamado a set_current_state(). Por ejemplo, si el durmiente hace:
+
+	set_current_state(TASK_INTERRUPTIBLE);
+	if (evento_indicado)
+		break;
+	__set_current_state(TASK_RUNNING);
+	hacer_algo(my_data);
+
+y el que despierta hace:
+
+	my_data = valor;
+	evento_indicado = 1;
+	wake_up(&event_wait_queue);
+
+no existe garantía de que el cambio a event_indicated sea percibido por
+el durmiente de manera que venga después del cambio a my_data. En tal
+circunstancia, el código en ambos lados debe sacar sus propias barreras de
+memoria entre los separados accesos a datos. Por lo tanto, el durmiente
+anterior debería hacer:
+
+	set_current_state(TASK_INTERRUPTIBLE);
+	if (evento_indicado) {
+		smp_rmb();
+		hacer_algo(my_data);
+	}
+
+y el que despierta debería hacer:
+
+	my_data = value;
+	smp_wmb();
+	evento_indicado = 1;
+	wake_up(&event_wait_queue);
+
+FUNCIONES VARIAS
+----------------
+
+Otras funciones que implican barreras:
+
+ (*) schedule() y similares implican barreras completas de memoria.
+
+
+========================================
+EFECTOS DE BARRERA ADQUIRIENDO INTRA-CPU
+========================================
+
+En los sistemas SMP, las primitivas de bloqueo proveen una forma más
+sustancial de barrera: una que afecta el orden de acceso a la memoria en
+otras CPU, dentro del contexto de conflicto en cualquier bloqueo en
+particular.
+
+
+ADQUISICIÓN VS ACCESOS A MEMORIA
+--------------------------------
+
+Considere lo siguiente: el sistema tiene un par de spinlocks (M) y (Q), y
+tres CPU; entonces la siguiente secuencia de eventos debería ocurrir:
+
+	CPU 1				CPU 2
+	===============================	===============================
+	WRITE_ONCE(*A, a);		WRITE_ONCE(*E, e);
+	ACQUIRE M			ACQUIRE Q
+	WRITE_ONCE(*B, b);		WRITE_ONCE(*F, f);
+	WRITE_ONCE(*C, c);		WRITE_ONCE(*G, g);
+	RELEASE M			RELEASE Q
+	WRITE_ONCE(*D, d);		WRITE_ONCE(*H, h);
+
+Entonces no hay garantía sobre en qué orden verá la CPU 3 los accesos a *A
+hasta que *H ocurra, además de las restricciones impuestas por los bloqueos
+separados en las distintas CPUs. Podría, por ejemplo, ver:
+
+	*E, ACQUIRE M, ACQUIRE Q, *G, *C, *F, *A, *B, RELEASE Q, *D, *H, RELEASE M
+
+Pero no verá ninguno de:
+
+	*B, *C or *D preceding ACQUIRE M
+	*A, *B or *C following RELEASE M
+	*F, *G or *H preceding ACQUIRE Q
+	*E, *F or *G following RELEASE Q
+
+========================================
+¿DÓNDE SE NECESITAN BARRERAS DE MEMORIA?
+========================================
+
+Bajo operación normal, el re-ordenamiento de una operación de memoria
+generalmente no va a suponer un problema, ya que para una pieza de código
+lineal de un solo subproceso seguirá pareciendo que funciona correctamente,
+incluso si está en un kernel SMP. Existen, sin embargo, cuatro
+circunstancias en las que reordenar definitivamente _podría_ ser un
+problema:
+
+ (*) Interacción entre procesadores.
+
+ (*) Operaciones atómicas.
+
+ (*) Acceso a dispositivos.
+
+ (*) Interrupciones.
+
+
+INTERACCIÓN ENTRE PROCESADORES
+------------------------------
+
+Cuando se da un sistema con más de un procesador, más de una CPU en el
+sistema puede estar trabajando en el mismo conjunto de datos al mismo
+tiempo. Esto puede causar problemas de sincronización, y la forma habitual
+de tratar con estos es utilizar cerrojos. Sin embargo, los cerrojos son
+bastante caros, por lo que puede ser preferible operar sin el uso de un
+cerrojo a ser posible. En cuyo caso, es posible que las operaciones que
+afectan a ambas CPU deban ordenarse cuidadosamente para evitar un
+funcionamiento incorrecto.
+
+Considere, por ejemplo, la ruta lenta del semáforo R/W. Aquí hay un proceso
+de espera en cola del semáforo, en virtud de que tiene una parte de su pila
+vinculada a la lista de procesos en espera del semáforo:
+
+	struct rw_semaphore {
+		...
+		spinlock_t lock;
+		struct list_head waiters;
+	};
+
+	struct rwsem_waiter {
+		struct list_head list;
+		struct task_struct *task;
+	};
+
+Para despertar a un proceso que espera ("waiter") en particular, las
+funciones up_read() o up_write() tienen que:
+
+ (1) leer el siguiente puntero del registro de este proceso que espera,
+     para saber dónde está el registro del siguiente waiter;
+
+ (2) leer el puntero a la estructura de tareas del waiter;
+
+ (3) borrar el puntero de la tarea para decirle al waiter que se le ha dado
+     el semáforo;
+
+ (4) llamar a wake_up_process() en la tarea; y
+
+ (5) liberar la referencia retenida en la estructura de tareas del waiter.
+
+En otras palabras, tiene que realizar esta secuencia de eventos:
+
+	LOAD waiter->list.next;
+	LOAD waiter->task;
+	STORE waiter->task;
+	CALL wakeup
+	RELEASE task
+
+y si alguno de estos pasos ocurre fuera de orden, entonces todo puede que
+funcione defectuosamente.
+
+Una vez que se ha puesto en cola y soltado el bloqueo de semáforo, el
+proceso que espera no consigue el candado de nuevo; en cambio, solo espera
+a que se borre su puntero de tarea antes de continuar. Dado que el registro
+está en la pila del proceso que espera, esto significa que si el puntero de
+la tarea se borra _antes_ de que se lea el siguiente puntero de la lista,
+otra CPU podría comenzar a procesar el proceso que espera y podría romper
+el stack del proceso que espera antes de que la función up*() tenga la
+oportunidad de leer el puntero que sigue.
+
+Considere entonces lo que podría suceder con la secuencia de eventos
+anterior:
+
+	CPU 1				CPU 2
+	===============================	===============================
+					down_xxx()
+					Poner waiter en la "queue" (cola)
+					Dormir
+	up_yyy()
+	LOAD waiter->task;
+	STORE waiter->task;
+					Despertado por otro evento
+	<preempt>
+					Reanudar el procesamiento
+					down_xxx() regresa
+					llamada a foo()
+					foo() estropea *waiter
+	</preempt>
+	LOAD waiter->list.next;
+	--- OOPS ---
+
+Esto podría solucionarse usando el bloqueo de semáforo, pero luego la
+función down_xxx() tiene que obtener innecesariamente el spinlock
+nuevamente, después de ser despertado el hilo.
+
+La forma de lidiar con esto es insertar una barrera de memoria SMP general:
+
+	LOAD waiter->list.next;
+	LOAD waiter->task;
+	smp_mb();
+	STORE waiter->task;
+	CALL wakeup
+	RELEASE task
+
+En este caso, la barrera garantiza que todos los accesos a memoria antes de
+la barrera parecerán suceder antes de todos los accesos a memoria después
+de dicha barrera con respecto a las demás CPU del sistema. _No_ garantiza
+que todos los accesos a memoria antes de la barrera se completarán en el
+momento en que la instrucción de la barrera en sí se complete.
+
+En un sistema UP, donde esto no sería un problema, la función smp_mb() es
+solo una barrera del compilador, asegurándose así de que el compilador
+emita las instrucciones en el orden correcto sin realmente intervenir en la
+CPU. Como solo hay un CPU, la lógica de orden de dependencias de esa CPU se
+encargará de todo lo demás.
+
+
+OPERACIONES ATÓMICAS
+--------------------
+
+Si bien son, técnicamente, consideraciones de interacción entre
+procesadores, las operaciones atómicas se destacan especialmente porque
+algunas de ellas implican barreras de memoria completa y algunas otras no,
+pero se confía mucho en ellos en su conjunto a lo largo del kernel.
+
+Consulte Documentation/atomic_t.txt para obtener más información.
+
+
+ACCESO A DISPOSITIVOS
+---------------------
+
+Un driver puede ser interrumpido por su propia rutina de servicio de
+interrupción y, por lo tanto, las dos partes del driver pueden interferir
+con los intentos de controlar o acceder al dispositivo.
+
+Esto puede aliviarse, al menos en parte, desactivando las interrupciones
+locales (una forma de bloqueo), de modo que las operaciones críticas sean
+todas contenidas dentro la sección de interrupción desactivada en el
+controlador. Mientras la interrupción del driver está ejecutando la rutina,
+es posible que el "core" del controlador no se ejecute en la misma CPU y no
+se permita que su interrupción vuelva a ocurrir hasta que la interrupción
+actual haya sido resuelta, por lo tanto, el controlador de interrupción no
+necesita bloquearse contra esto.
+
+Sin embargo, considere un driver que estaba hablando con una tarjeta
+ethernet que tiene un registro de direcciones y un registro de datos. Si
+el core de ese controlador habla con la tarjeta estando en desactivación de
+interrupción y luego se invoca el controlador de interrupción del
+controlador:
+
+	IRQ LOCALES DESACTIVADAS
+	writew(ADDR, 3);
+	writew(DATA, y);
+	IRQ LOCALES ACTIVADAS
+	<interrupción>
+	writew(ADDR, 4);
+	q = readw(DATA);
+	</interrupción>
+
+El almacenamiento en el registro de datos puede ocurrir después del segundo
+almacenamiento en el registro de direcciones si las reglas de orden son lo
+suficientemente relajadas:
+
+  	STORE *ADDR = 3, STORE *ADDR = 4, STORE *DATA = y, q = LOAD *DATA
+
+Si se relajan las reglas de orden, se debe asumir que los accesos
+realizados dentro de la sección con interrupción deshabilitada pueden
+filtrarse fuera de esta y pueden intercalarse con accesos realizados en una
+interrupción - y viceversa - a menos que se utilicenn barreras implícita o
+explícitas.
+
+Normalmente, esto no será un problema porque los accesos de E/S realizados
+dentro de tales secciones incluirán operaciones de carga síncronas en
+registros E/S estrictamente ordenados, que forman barreras de E/S
+implícitas.
+
+
+Una situación similar puede ocurrir entre una rutina de interrupción y dos
+rutinas ejecutándose en separadas CPU que se comunican entre sí. Si tal
+caso es probable, entonces se deben usar bloqueos de desactivación de
+interrupciones para garantizar el orden.
+
+
+=====================================
+ Efectos de barrera de E/S del kernel
+=====================================
+
+La interfaz con periféricos a través de accesos de E/S es profundamente
+específica para cada arquitectura y dispositivo. Por lo tanto, los drivers
+que son inherentemente no portátiles pueden depender de comportamientos
+específicos de sus sistemas de destino, con el fin de lograr la
+sincronización de la manera más ligera posible. Para drivers que deseen ser
+portátiles entre múltiples arquitecturas e implementaciones de bus, el
+kernel ofrece una serie de funciones de acceso que proporcionan varios
+grados de garantías de orden:
+
+ (*) readX(), writeX():
+
+	Las funciones de acceso MMIO readX() y writeX() usan un puntero al
+	periférico al que se accede como un parámetro __iomem *. para punteros
+	asignados los atributos de E/S predeterminados (por ejemplo, los
+        devueltos por ioremap()), las garantías de orden son las siguientes:
+
+	1. Se ordenan todos los accesos readX() y writeX() a un mismo periférico
+	   entre estos. Esto asegura que los registros de acceso MMIO por el
+	   mismo subproceso de la CPU a un dispositivo en particular llegarán en
+	   el orden del programa.
+
+	2. Se ordena un writeX() emitido por un subproceso de CPU que contiene un
+	   spinlock antes de un writeX() al mismo periférico desde otro
+           subproceso de CPU, si emitido después de una adquisición posterior del
+	   mismo spinlock. Esto garantiza que ese registro MMIO escribe en un
+           dispositivo en particular, mientras que se obtiene un spinlock en un
+           orden consistente con las adquisiciones del cerrojo.
+
+	3. Un writeX() por un subproceso de la CPU al periférico primero esperará
+	   a la finalización de todas las escrituras anteriores en la memoria
+           emitidas por, o bien propagadas por, el mismo subproceso. Esto asegura
+	   que las escrituras de la CPU a un búfer DMA de salida asignadas por
+           dma_alloc_coherent() serán visibles para un motor ("engine") DMA
+           cuando la CPU escriba en sus registros de control MMIO, para activar
+           la transferencia.
+
+	4. Un readX() de un subproceso del CPU, desde el periférico, se
+	   completará antes de que cualquier lectura subsiguiente de memoria por
+	   el mismo subproceso pueda comenzar. Esto asegura que las lecturas de
+           la CPU desde un búfer DMA entrantes asignadas por
+           dma_alloc_coherent(), no verán datos obsoletos después de leer el
+           registro de estado MMIO del motor DMA, para establecer que la
+           transferencia DMA se haya completado.
+
+	5. Un readX() por un subproceso del CPU, desde el periférico, se
+	   completará antes de que cualquier bucle delay() subsiguiente pueda
+           comenzar a ejecutarse en el mismo subproceso. Esto asegura que dos
+           escrituras del CPU a registros MMIO en un periférico llegarán al menos
+	   con 1us de diferencia, si la primera escritura se lee inmediatamente
+           de vuelta con readX() y se llama a udelay(1) antes del segundo
+           writeX():
+
+		writel(42, DEVICE_REGISTER_0); // Llega al dispositivo ...
+		readl(DEVICE_REGISTER_0);
+		udelay(1);
+		writel(42, DEVICE_REGISTER_1); // al menos 1us antes de esto....
+
+Las propiedades de orden de los punteros __iomem obtenidos con valores de
+atributos que no sean los valores por defecto (por ejemplo, los devueltos
+por ioremap_wc()) son específicos de la arquitectura subyacente y, por lo
+tanto, las garantías enumeradas anteriormente no pueden por lo general ser
+aseguradas para accesos a este tipo de "mappings" (asignaciones).
+
+ (*) readX_relaxed(), writeX_relaxed():
+
+	Son similares a readX() y writeX(), pero proporcionan una garantía de
+        orden de memoria más débil. Específicamente, no garantizan orden con
+	respecto al bloqueo, los accesos normales a la memoria o los bucles
+        delay() (es decir, los puntos 2-5 arriba) pero todavía se garantiza que
+        se ordenarán con respecto a otros accesos desde el mismo hilo de la CPU,
+        al mismo periférico, cuando se opera en punteros __iomem asignados con el
+        valor predeterminado para los atributos de E/S.
+
+ (*) readsX(), writesX():
+
+	Los puntos de entrada readsX() y writesX() MMIO están diseñados para
+        acceder FIFOs mapeados en memoria y basados en registros que residen en
+        periféricos, que no son capaces de realizar DMA. Por tanto, sólo
+        proporcionan garantías de orden readX_relaxed() y writeX_relaxed(), como
+        se documentó anteriormente.
+
+ (*) inX(), outX():
+
+	Los puntos de entrada inX() y outX() están destinados a acceder a mapas
+        de puertos "legacy" (antiguos) de periféricos de E/S, que pueden requerir
+	instrucciones especiales en algunas arquitecturas (especialmente, en
+        x86). El número de puerto del periférico que se está accedido se pasa
+        como un argumento.
+
+	Dado que muchas arquitecturas de CPU acceden finalmente a estos
+        periféricos a través de un mapeo interno de memoria virtual, las
+        garantías de orden portátiles proporcionadas por inX() y outX() son las
+        mismas que las proporcionadas por readX() y writeX(), respectivamente, al
+        acceder a una asignación con los valores de atributos de E/S
+        predeterminados (los que haya por defecto).
+
+        Los drivers de dispositivos pueden esperar que outX() emita una
+        transacción de escritura no publicada, que espera una respuesta de
+        finalización del periférico de E/S antes de regresar. Esto no está
+        garantizado por todas las arquitecturas y por lo tanto no forma parte de
+        la semántica de orden portátil.
+
+ (*) insX(), outsX():
+
+        Como arriba, los puntos de entrada insX() y outsX() proporcionan el mismo
+        orden garantizado por readsX() y writesX() respectivamente, al acceder a
+        un mapping con los atributos de E/S predeterminados.
+
+ (*) ioreadX(), iowriteX():
+
+        Estos funcionarán adecuadamente para el tipo de acceso que realmente están
+        haciendo, ya sea inX()/outX() o readX()/writeX().
+
+Con la excepción de los puntos de entrada (insX(), outsX(), readsX() y
+writesX()), todo lo anterior supone que el periférico subyacente es
+little-endian y, por lo tanto, realizará operaciones de intercambio de
+bytes en arquitecturas big-endian.
+
+
+===========================================
+MODELO DE ORDEN MÍNIMO DE EJECUCIÓN ASUMIDO
+===========================================
+
+Debe suponerse que la CPU conceptual está débilmente ordenada, pero que
+mantiene la apariencia de causalidad del programa con respecto a sí misma.
+Algunas CPU (como i386 o x86_64) están más limitadas que otras (como
+powerpc o frv), por lo que el caso más relajado (es decir, DEC Alpha) se
+debe asumir fuera de código específico de arquitectura.
+
+Esto significa que se debe considerar que la CPU ejecutará su flujo de
+instrucciones en el orden que se quiera - o incluso en paralelo - siempre
+que si una instrucción en el flujo depende de una instrucción anterior,
+entonces dicha instrucción anterior debe ser lo suficientemente completa[*]
+antes de que la posterior instrucción puede proceder; en otras palabras:
+siempre que la apariencia de causalidad se mantenga.
+
+ [*] Algunas instrucciones tienen más de un efecto, como cambiar el
+     código de condición, cambio de registros o cambio de memoria - y
+     distintas instrucciones pueden depender de diferentes efectos.
+
+Una CPU puede también descartar cualquier secuencia de instrucciones que
+termine sin tener efecto final. Por ejemplo, si dos instrucciones
+adyacentes cargan un valor inmediato en el mismo registro, la primera puede
+descartarse.
+
+
+De manera similar, se debe suponer que el compilador podría reordenar la
+corriente de instrucciones de la manera que crea conveniente, nuevamente
+siempre que la apariencia de causalidad se mantenga.
+
+
+=====================================
+EFECTOS DE LA MEMORIA CACHÉ DE LA CPU
+=====================================
+
+La forma en que se perciben las operaciones de memoria caché en todo el
+sistema se ve afectada, hasta cierto punto, por los cachés que se
+encuentran entre las CPU y la memoria, y por el sistema de coherencia en
+memoria que mantiene la consistencia de estado en el sistema.
+
+En cuanto a la forma en que una CPU interactúa con otra parte del sistema a
+través del caché, el sistema de memoria tiene que incluir los cachés de la
+CPU y barreras de memoria, que en su mayor parte actúan en la interfaz
+entre la CPU y su caché (las barreras de memoria lógicamente actúan sobre
+la línea de puntos en el siguiente diagrama):
+
+	    <--- CPU --->         :       <----------- Memoria ----------->
+	                          :
+	+--------+    +--------+  :   +--------+    +-----------+
+	|  Core  |    | Cola   |  :   | Cache  |    |           |    +---------+
+	|  CPU   |    | de     |  :   | CPU    |    |           |    |         |
+	|        |--->| acceso |----->|        |<-->|           |    |         |
+	|        |    | a      |  :   |        |    |           |--->| Memoria |
+	|        |    | memoria|  :   |        |    |           |    |         |
+	+--------+    +--------+  :   +--------+    | Mecanismo |    |         |
+	                          :                 | de        |    +---------+
+	                          :                 | Coherencia|
+	                          :                 | de la     |    +--------+
+	+--------+    +--------+  :   +--------+    | cache     |    |	      |
+	|  Core  |    | Cola   |  :   | Cache  |    |           |    |        |
+	|  CPU   |    | de     |  :   | CPU    |    |           |--->| Dispos |
+	|        |--->| acceso |----->|        |<-->|           |    | itivo  |
+	|        |    | a      |  :   |        |    |           |    |        |
+	|        |    | memoria|  :   |        |    |           |    +--------+
+	+--------+    +--------+  :   +--------+    +-----------+
+	                          :
+	                          :
+
+Aunque es posible que una carga o store en particular no aparezca fuera de
+la CPU que lo emitió, ya que puede haber sido satisfecha dentro del propio
+caché de la CPU, seguirá pareciendo como si el acceso total a la memoria
+hubiera tenido lugar para las otras CPUs, ya que los mecanismos de
+coherencia de caché migrarán la cacheline sobre la CPU que accede y se
+propagarán los efectos en caso de conflicto.
+
+El núcleo de la CPU puede ejecutar instrucciones en el orden que considere
+adecuado, siempre que parezca mantenerse la causalidad esperada del
+programa. Algunas de las instrucciones generan operaciones de carga y
+almacenamiento que luego van a la cola de accesos a memoria a realizar. El
+núcleo puede colocarlos en la cola en cualquier orden que desee, y
+continuar su ejecución hasta que se vea obligado a esperar que una
+instrucción sea completada.
+
+De lo que se ocupan las barreras de la memoria es de controlar el orden en
+que los accesos cruzan, desde el lado de la CPU, hasta el lado de memoria,
+y el orden en que los otros observadores perciben los efectos en el sistema
+que sucedan por esto.
+
+[!] Las barreras de memoria _no_ son necesarias dentro de una CPU
+determinada, ya que las CPU siempre ven sus propias cargas y stores como si
+hubieran sucedido en el orden del programa.
+
+[!] Los accesos a MMIO u otros dispositivos pueden pasar por alto el
+sistema de caché. Esto depende de las propiedades de la ventana de memoria
+a través de la cual se accede a los dispositivos y/o el uso de
+instrucciones especiales de comunicación con dispositivo que pueda tener la
+CPU.
+
+
+COHERENCIA DE CACHÉ FRENTE A DMA
+---------------------------------
+
+No todos los sistemas mantienen coherencia de caché con respecto a los
+dispositivos que realizan DMA. En tales casos, un dispositivo que intente
+DMA puede obtener datos obsoletos de la RAM, porque las líneas de caché
+"sucias" pueden residir en los cachés de varias CPU, y es posible que no
+se hayan vuelto a escribir en la RAM todavía. Para hacer frente a esto, la
+parte apropiada del kernel debe vaciar los bits superpuestos de caché en
+cada CPU (y tal vez también invalidarlos).
+
+Además, los datos enviados por DMA a RAM, por un dispositivo, pueden ser
+sobrescritos por líneas de caché sucias que se escriben de nuevo en la RAM
+desde el caché de una CPU, después de que el dispositivo haya puesto sus
+propios datos, o las líneas de caché presentes en el caché de la CPU pueden
+simplemente ocultar el hecho de que la memoria RAM se haya actualizado,
+hasta el momento en que la caché se descarta de la memoria caché de la CPU
+y se vuelve a cargar. Para hacer frente a esto, la parte apropiada del
+kernel debe invalidar los bits superpuestos del caché en cada CPU.
+
+Consulte Documentation/core-api/cachetlb.rst para obtener más información
+sobre administración de la memoria caché.
+
+
+COHERENCIA DE CACHÉ FRENTE A MMIO
+---------------------------------
+
+La E/S mapeada en memoria generalmente se lleva a cabo a través de
+ubicaciones de memoria que forman parte de una ventana del espacio de
+memoria de la CPU, que tiene diferentes propiedades asignadas que la
+ventana habitual dirigida a RAM.
+
+Entre dichas propiedades, suele existir el hecho de que tales accesos
+eluden el almacenamiento en caché por completo e ir directamente a los
+buses del dispositivo. Esto significa que los accesos MMIO pueden, en
+efecto, superar los accesos a la memoria caché que se emitieron
+anteriormente. Una barrera de memoria no es suficiente en tal caso, sino
+que el caché debe ser vaciado entre la escritura de la memoria caché, y el
+acceso MMIO, si los dos son de cualquier manera dependiente.
+
+
+=======================
+COSAS QUE HACEN LAS CPU
+=======================
+
+Un programador podría dar por sentado que la CPU realizará las operaciones
+de memoria exactamente en el orden especificado, de modo que si a la CPU se
+entrega, por ejemplo, el siguiente fragmento de código a ejecutar:
+
+	a = READ_ONCE(*A);
+	WRITE_ONCE(*B, b);
+	c = READ_ONCE(*C);
+	d = READ_ONCE(*D);
+	WRITE_ONCE(*E, e);
+
+esperarían entonces que la CPU complete la operación de memoria para cada
+instrucción antes de pasar a la siguiente, lo que lleva a una definida
+secuencia de operaciones vistas por observadores externos en el sistema:
+
+  	LOAD *A, STORE *B, LOAD *C, LOAD *D, STORE *E.
+
+La realidad es, por supuesto, mucho más intrincada. Para muchas CPU y
+compiladores, la anterior suposición no se sostiene porque:
+
+ (*) es más probable que las cargas deban completarse de inmediato para
+     permitir progreso en la ejecución, mientras que los stores a menudo se
+     pueden aplazar sin problema;
+
+ (*) las cargas se pueden hacer especulativamente, y el resultado es
+     descartado si resulta innecesario;
+
+ (*) las cargas se pueden hacer de forma especulativa, lo que lleva a que
+     se haya obtenido el resultado en el momento equivocado de la secuencia
+     de eventos esperada;
+
+ (*) el orden de los accesos a memoria se puede reorganizar para promover
+     un mejor uso de los buses y cachés de la CPU;
+
+ (*) las cargas y los stores se pueden combinar para mejorar el rendimiento
+     cuando se habla con memoria o hardware de E/S, que puede realizar
+     accesos por lotes a ubicaciones adyacentes, reduciendo así los costes
+     de configuración de transacciones (la memoria y los dispositivos PCI
+     pueden ambos pueden hacer esto); y
+
+ (*) la caché de datos de la CPU puede afectar al orden, y mientras sus
+     mecanismos de coherencia pueden aliviar esto, una vez que el store
+     haya accedido al caché- no hay garantía de que la gestión de la
+     coherencia se propague en orden a otras CPU.
+
+Entonces, digamos que lo que otra CPU podría observar en el fragmento de
+código anterior es:
+
+	LOAD *A, ..., LOAD {*C,*D}, STORE *E, STORE *B
+
+	(Donde "LOAD {*C,*D}" es una carga combinada)
+
+
+Sin embargo, se garantiza que una CPU es autoconsistente: verá que sus
+ _propios_ accesos parecen estar correctamente ordenados, sin necesidad de
+barrera de memoria. Por ejemplo con el siguiente código:
+
+	U = READ_ONCE(*A);
+	WRITE_ONCE(*A, V);
+	WRITE_ONCE(*A, W);
+	X = READ_ONCE(*A);
+	WRITE_ONCE(*A, Y);
+	Z = READ_ONCE(*A);
+
+y asumiendo que no hay intervención de una influencia externa, se puede
+suponer que el resultado final se parecerá a:
+
+	U == el valor original de *A
+	X == W
+	Z == Y
+	*A == Y
+
+El código anterior puede hacer que la CPU genere la secuencia completa de
+accesos de memoria:
+
+	U=LOAD *A, STORE *A=V, STORE *A=W, X=LOAD *A, STORE *A=Y, Z=LOAD *A
+
+en ese orden, pero, sin intervención, la secuencia puede contener casi
+cualquier combinación de elementos combinados o descartados, siempre que la
+perspectiva del programa del mundo siga siendo consistente. Tenga en cuenta
+que READ_ONCE() y WRITE_ONCE() -no- son opcionales en el ejemplo anterior,
+ya que hay arquitecturas donde una CPU determinada podría reordenar cargas
+sucesivas en la misma ubicación. En tales arquitecturas, READ_ONCE() y
+WRITE_ONCE() hacen lo que sea necesario para evitar esto, por ejemplo, en
+Itanium los casts volátiles utilizados por READ_ONCE() y WRITE_ONCE() hacen
+que GCC emita las instrucciones especiales ld.acq y st.rel
+(respectivamente) que impiden dicha reordenación.
+
+El compilador también puede combinar, descartar o diferir elementos de la
+secuencia antes incluso de que la CPU los vea.
+
+Por ejemplo:
+
+	*A = V;
+	*A = W;
+
+puede reducirse a:
+
+	*A = W;
+
+ya que, sin una barrera de escritura o WRITE_ONCE(), puede que se asuma
+que el efecto del almacenamiento de V a *A se pierde. Similarmente:
+
+  	*A = Y;
+  	Z = *A;
+
+puede, sin una barrera de memoria o un READ_ONCE() y WRITE_ONCE(), esto
+sea reducido a:
+
+  	*A = Y;
+  	Z = Y;
+
+y la operación LOAD nunca aparezca fuera de la CPU.
+
+
+Y LUEGO ESTÁ EL ALFA
+--------------------
+
+La CPU DEC Alpha es una de las CPU más relajadas que existen. No solo eso,
+algunas versiones de la CPU Alpha tienen un caché de datos dividido, lo que
+les permite tener dos líneas de caché relacionadas semánticamente,
+actualizadas en momentos separados. Aquí es donde la barrera de dependencia
+de dirección realmente se vuelve necesaria, ya que se sincronizan ambos
+cachés con el sistema de coherencia de memoria, lo que hace que parezca un
+cambio en el puntero, frente a que los nuevos datos se produzcan en el
+orden correcto.
+
+Alpha define el modelo de memoria del kernel Linux, aunque a partir de
+v4.15, la adición al kernel de Linux de smp_mb() a READ_ONCE() en Alpha
+redujo en gran medida su impacto en el modelo de memoria.
+
+
+GUESTS DE MÁQUINAS VIRTUALES
+-----------------------------
+
+Los "guests" (invitados) que se ejecutan en máquinas virtuales pueden verse
+afectados por los efectos de SMP incluso si el "host" (huésped) en sí se
+compila sin compatibilidad con SMP. Este es un efecto de la interacción con
+un host SMP mientras ejecuta un kernel UP. El uso obligatorio de barreras
+para este caso de uso sería posible, pero a menudo no son óptimas.
+
+Para hacer frente a este caso de manera óptima, están disponibles macros de
+bajo nivel virt_mb() etc. Estas tienen el mismo efecto que smp_mb(), etc.
+cuando SMP está habilitado, pero generan código idéntico para sistemas SMP
+y no SMP. Por ejemplo, los invitados de máquinas virtuales debería usar
+virt_mb() en lugar de smp_mb() al sincronizar contra un (posiblemente SMP)
+anfitrión.
+
+Estos son equivalentes a sus contrapartes smp_mb() etc. en todos los demás
+aspectos, en particular, no controlan los efectos MMIO: para controlar los
+efectos MMIO, utilice barreras obligatorias.
+
+
+================
+EJEMPLOS DE USOS
+================
+
+BUFFERS CIRCULARES
+------------------
+
+Las barreras de memoria se pueden utilizar para implementar almacenamiento
+en búfer circular, sin necesidad de un cerrojo para serializar al productor
+con el consumidor. Vea:
+
+	Documentation/core-api/circular-buffers.rst
+
+para más detalles.
+
+
+===========
+REFERENCIAS
+===========
+
+Alpha AXP Architecture Reference Manual, Segunda Edición (por Sites & Witek,
+Digital Press)
+	Capítulo 5.2: Physical Address Space Characteristics
+	Capítulo 5.4: Caches and Write Buffers
+	Capítulo 5.5: Data Sharing
+	Capítulo 5.6: Read/Write Ordering
+
+AMD64 Architecture Programmer's Manual Volumen 2: System Programming
+	Capítulo 7.1: Memory-Access Ordering
+	Capítulo 7.4: Buffering and Combining Memory Writes
+
+ARM Architecture Reference Manual (ARMv8, for ARMv8-A architecture profile)
+	Capítulo B2: The AArch64 Application Level Memory Model
+
+IA-32 Intel Architecture Software Developer's Manual, Volumen 3:
+System Programming Guide
+	Capítulo 7.1: Locked Atomic Operations
+	Capítulo 7.2: Memory Ordering
+	Capítulo 7.4: Serializing Instructions
+
+The SPARC Architecture Manual, Version 9
+	Capítulo 8: Memory Models
+	Appendix D: Formal Specification of the Memory Models
+	Appendix J: Programming with the Memory Models
+
+Storage in the PowerPC (por Stone and Fitzgerald)
+
+UltraSPARC Programmer Reference Manual
+	Capítulo 5: Memory Accesses and Cacheability
+	Capítulo 15: Sparc-V9 Memory Models
+
+UltraSPARC III Cu User's Manual
+	Capítulo 9: Memory Models
+
+UltraSPARC IIIi Processor User's Manual
+	Capítulo 8: Memory Models
+
+UltraSPARC Architecture 2005
+	Capítulo 9: Memory
+	Appendix D: Formal Specifications of the Memory Models
+
+UltraSPARC T1 Supplement to the UltraSPARC Architecture 2005
+	Capítulo 8: Memory Models
+	Appendix F: Caches and Cache Coherency
+
+Solaris Internals, Core Kernel Architecture, p63-68:
+	Capítulo 3.3: Hardware Considerations for Locks and
+			Synchronization
+
+Unix Systems for Modern Architectures, Symmetric Multiprocessing and Caching
+for Kernel Programmers:
+	Capítulo 13: Other Memory Models
+
+Intel Itanium Architecture Software Developer's Manual: Volumen 1:
+	Sección 2.6: Speculation
+	Sección 4.4: Memory Access