La idea detrás del paradigma/tienda exclusiva de carga ligada es que si si la tienda sigue muy pronto después de la carga, sin operaciones de memoria que intervienen, y si no otra cosa ha tocado la ubicación, la tienda es probable para tener éxito, pero si algo más ha tocado la ubicación de la tienda es cierto para fallar. No hay garantía de que las tiendas a veces no fallarán sin razón aparente; si el tiempo entre carga y almacenamiento se mantiene al mínimo, sin embargo, y hay accesos sin memoria entre ellos, como un bucle:
do
{
new_value = __LDREXW(dest) + 1;
} while (__STREXW(new_value, dest));
general se puede confiar para tener éxito dentro de unos pocos intentos.Si el cálculo del nuevo valor basado en el valor anterior requiere algún cálculo significativo, se debe reescribir el bucle como:
do
{
old_value = *dest;
new_value = complicated_function(old_value);
} while (CompareAndStore(dest, new_value, old_value) != 0);
... Assuming CompareAndStore is something like:
uint32_t CompareAndStore(uint32_t *dest, uint32_t new_value, uint_32 old_value)
{
do
{
if (__LDREXW(dest) != old_value) return 1; // Failure
} while(__STREXW(new_value, dest);
return 0;
}
Este código tendrá que volver a ejecutar el bucle principal si algo cambia * dest mientras que el nuevo valor se calcula , pero sólo se necesita el pequeño bucle que se vuelva a ejecutar si el __STREXW falla por alguna otra razón [que se espera no es demasiado probable, teniendo en cuenta que sólo habrá unos dos instrucciones entre la __LDREXW y __STREXW]
Adición Un ejemplo de una situación en la que "calcular un nuevo valor basado en el anterior" podría ser complicado sería una en la que los "valores" ar e efectivamente una referencia a una estructura de datos compleja. El código puede buscar la referencia anterior, derivar una nueva estructura de datos de la anterior y luego actualizar la referencia. Este patrón aparece mucho más a menudo en los marcos recolectados de basura que en la programación de "metal desnudo", pero hay una variedad de formas en que puede surgir incluso cuando se programa metal desnudo. Los asignadores malloc/calloc normales generalmente no son seguros para la rosca/interruptores, pero a menudo lo son los asignadores para estructuras de tamaño fijo. Si uno tiene un "pool" de algún número de potencias de dos de las estructuras de datos (digamos 255), se podría utilizar algo como:
#define FOO_POOL_SIZE_SHIFT 8
#define FOO_POOL_SIZE (1 << FOO_POOL_SIZE_SHIFT)
#define FOO_POOL_SIZE_MASK (FOO_POOL_SIZE-1)
void do_update(void)
{
// The foo_pool_alloc() method should return a slot number in the lower bits and
// some sort of counter value in the upper bits so that once some particular
// uint32_t value is returned, that same value will not be returned again unless
// there are at least (UINT_MAX)/(FOO_POOL_SIZE) intervening allocations (to avoid
// the possibility that while one task is performing its update, a second task
// changes the thing to a new one and releases the old one, and a third task gets
// given the newly-freed item and changes the thing to that, such that from the
// point of view of the first task, the thing never changed.)
uint32_t new_thing = foo_pool_alloc();
uint32_t old_thing;
do
{
// Capture old reference
old_thing = foo_current_thing;
// Compute new thing based on old one
update_thing(&foo_pool[new_thing & FOO_POOL_SIZE_MASK],
&foo_pool[old_thing & FOO_POOL_SIZE_MASK);
} while(CompareAndSwap(&foo_current_thing, new_thing, old_thing) != 0);
foo_pool_free(old_thing);
}
Si no será a menudo múltiples hilos/interrupciones/lo que sea que intentan actualice la misma cosa al mismo tiempo, este enfoque debería permitir que las actualizaciones se realicen de forma segura. Si existe una relación de prioridad entre las cosas que pueden intentar actualizar el mismo elemento, se garantiza que la más alta prioridad tendrá éxito en su primer intento, la próxima prioridad más alta tendrá éxito en cualquier intento que no sea reemplazado por el de mayor prioridad, etc. Si uno usaba el bloqueo, la tarea de mayor prioridad que quería realizar la actualización tendría que esperar a que termine la actualización de menor prioridad; utilizando el paradigma CompareAndSwap, la tarea de mayor prioridad no se verá afectada por la inferior (pero hará que la inferior tenga que hacer un trabajo desaprovechado).
He estado haciendo exactamente lo mismo, pero la parte en la que se requiere una informática significativa para el nuevo valor, todavía me desconcierta. Usar el bucle cmxchg tiene sentido porque entonces el monitor exclusivo no se eliminará con un cambio de contexto, pero volver a realizar la computación significante requiere mucha sobrecarga ya que he observado que calle falla sin razones aparentes (UP con IRQ enmascarados en PSR) como se menciona en su publicación. – sgupta
@ user1075375: Consulte el apéndice – supercat
Estos (__LDREXW y __STREXW) son intrínsecos admitidos en los compiladores de Keil para procesadores de nivel Microcontroler de la serie Cortex-M, generalmente no disponibles para los principales objetivos ARM (por ejemplo, AArch64) y compiladores (por ejemplo, gcc, llvm) ¿derecho? http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/BABDEEJC.html – ahcox