L'idea alla base del/negozio esclusiva paradigma carico-linked è che se se il negozio segue molto presto dopo il carico, senza operazioni di memoria successivi, e se non altro ha toccato la posizione, il negozio è probabilmente per riuscire, ma se qualcos'altro ha toccato la posizione il negozio è certi fallire. Non vi è alcuna garanzia che i negozi non a volte falliscano senza una ragione apparente; se il tempo tra carico e deposito è ridotta al minimo, tuttavia, e non ci sono accessi di memoria tra loro, un ciclo come:
do
{
new_value = __LDREXW(dest) + 1;
} while (__STREXW(new_value, dest));
possono generalmente essere invocata per riuscire in pochi tentativi.Se calcolare il nuovo valore in base al vecchio valore richiesto qualche elaborazione significativa, si dovrebbe riscrivere il ciclo come:
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;
}
Questo codice dovrà eseguire nuovamente il suo ciclo principale, se qualcosa cambia * dest, mentre il nuovo valore è stato calcolato , ma solo il piccolo anello dovrà essere eseguire nuovamente se il __STREXW non riesce per qualche altra ragione [che non è si spera troppo probabile, dato che ci saranno solo circa due istruzioni tra il __LDREXW e __STREXW]
Addendum Un esempio di una situazione in cui "calcolare un nuovo valore basato su vecchio" potrebbe essere complicato sarebbe uno dove i "valori" ar e efficacemente un riferimento a una struttura dati complessa. Il codice può recuperare il vecchio riferimento, ricavare una nuova struttura dati dal vecchio e quindi aggiornare il riferimento. Questo schema si presenta molto più spesso nei framework raccolti da garbage piuttosto che nella programmazione "bare metal", ma ci sono una varietà di modi in cui può verificarsi anche durante la programmazione di bare metal. Normalmente gli allocatori malloc/calloc non sono generalmente sicuri per i thread/interrupt-safe, ma spesso gli allocatori per strutture a dimensione fissa lo sono. Se uno ha un "pool" di un numero potere-di-due di strutture di dati (ad esempio 255), si potrebbe usare qualcosa come:
#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);
}
Se non ci saranno spesso più thread/interrupt/tutto ciò che cercano di aggiornare la stessa cosa allo stesso tempo, questo approccio dovrebbe consentire di eseguire gli aggiornamenti in modo sicuro. Se tra le cose che potrebbero tentare di aggiornare lo stesso articolo esisterà una relazione di priorità, quella con priorità più alta è garantita per riuscire al suo primo tentativo, la successiva priorità più alta avrà esito positivo in ogni tentativo che non è preceduto da la più alta priorità, ecc. Se si stava utilizzando il blocco, l'attività con la priorità più alta che voleva eseguire l'aggiornamento avrebbe dovuto attendere il completamento dell'aggiornamento con priorità più bassa; usando il paradigma CompareAndSwap, il task con la priorità più alta non sarà influenzato da quello inferiore (ma farà sì che quello inferiore debba fare uno spreco di lavoro).
Ho fatto esattamente la stessa cosa, ma la parte in cui è richiesto un calcolo significativo per il nuovo valore, mi lascia ancora. Usare il ciclo cmxchg ha senso perché quindi il monitor esclusivo non verrà cancellato da un interruttore di contesto, ma rifare il calcolo significativo richiede un sacco di spese generali poiché ho osservato che Street fallire senza ragioni apparenti (UP con IRQ mascherati in PSR) come menzionato nel tuo post. – sgupta
@ user1075375: vedere appendice – supercat
Questi (__LDREXW e __STREXW) sono intrinsecamente supportati nei compilatori Keil per processori a microcontrollore della serie Cortex-M, non generalmente disponibili per target ARM mainstream (ad esempio AArch64) e compilatori (ad esempio, gcc, llvm) destra? http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/BABDEEJC.html – ahcox