Атомные операции в ARM
Я работал над встроенной ОС для ARM, однако есть несколько вещей, которые я не понял об архитектуре даже после ссылки на источник ARMARM и linux.
атомарные операции.
ARM ARM говорит, что инструкции Load и Store являются атомарными, и его выполнение гарантированно будет завершено до выполнения обработчика прерываний. Схемки
arch/arm/include/asm/atomic.h :
#define atomic_read(v) (*(volatile int *)&(v)->counter)
#define atomic_set(v,i) (((v)->counter) = (i))
однако проблема возникает, когда я хочу манипулировать этим значением атомарно используя инструкции cpu(atomic_inc, atomic_dec, atomic_cmpxchg и т. д..) которые используют LDREX и STREX для ARMv7 (моя цель).
ARMARM ничего не говорит о прерываниях, блокируемых в этом разделе, поэтому я предполагаю, что прерывание может произойти между LDREX и STREX. Дело в том, что он упоминает о блокировке шины памяти, которая, я думаю, полезна только для MP-систем, где может быть больше процессоров, пытающихся получить доступ к тому же месту одновременно. Но для UP (и, возможно, MP), если прерывание таймера (или IPI для SMP) срабатывает в этом небольшом окне LDREX и STREX, обработчик исключений выполняет возможные изменения контекста процессора и возвращается к новой задаче, однако Шокирующая часть входит сейчас, она выполняет "CLREX" и, следовательно, удаляет любую эксклюзивную блокировку, удерживаемую предыдущим потоком. Итак, насколько лучше использовать LDREX и STREX, чем LDR и STR для атомарности в системе UP ?
Я прочитал что-то об эксклюзивном мониторе блокировки, поэтому у меня есть возможная теория, что когда поток возобновляет и выполняет STREX, монитор ОС вызывает сбой этого вызова, который может быть обнаружен, и цикл может быть повторно выполнен с использованием нового значения в процессе (ветвь обратно в LDREX), я здесь ?
2 ответов
хорошо, получил ответ от своих сайт.
если переключатель контекста планирует процесс после того, как процесс выполнил исключение загрузки, но до того, как он выполнит исключение хранилища, исключение хранилища возвращает ложный отрицательный результат, когда процесс возобновляется, и память не обновляется. Это не влияет на функциональность программы, так как процесс может повторить операцию немедленно.
идея парадигмы load-linked/store-exclusive заключается в том, что если магазин следует очень скоро после загрузки, без промежуточных операций памяти, и если ничто другое не коснулось местоположения, магазин скорее для успеха, но если что-то еще коснулось местоположения магазина некоторых на провал. Нет никакой гарантии, что магазины не будут иногда терпеть неудачу без видимой причины; если время между загрузкой и магазином минимум, однако, и нет доступа к памяти между ними, цикл, такой как:
do
{
new_value = __LDREXW(dest) + 1;
} while (__STREXW(new_value, dest));
обычно можно рассчитывать на успех в течение нескольких попыток. Если для вычисления нового значения на основе старого требуется значительное вычисление, следует переписать цикл как:
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;
}
этот код должен будет перезапустить свой основной цикл, если что-то изменится *dest во время вычисления нового значения, но только небольшой цикл должен быть перезапущен, если _ _ STREXW не удастся по какой-то другой причине [что, надеюсь, не слишком вероятно, учитывая, что между __LDREXW и __STREXW будет только две инструкции]
дополнительное соглашение Примером ситуации, когда "вычислить новое значение на основе старого" может быть сложно, может быть ситуация, когда "значения" фактически являются ссылками на сложную структуру данных. Код может получить старую ссылку, получить новую структуру данных из старой, а затем обновить ссылку. Эта модель приходит гораздо чаще в рамках сбора мусора, чем в программировании "голого металла", но есть множество способов, которые могут возникнуть даже при программировании голого металла. Обычные распределители malloc/calloc обычно не являются потокобезопасными / прерываниями, но распределители для структур фиксированного размера часто являются. Если у вас есть " пул " из некоторого числа структур данных (скажем, 255), можно использовать что-то вроде:
#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);
}
Если часто не будет нескольких потоков / прерываний / что угодно пытаясь обновить то же самое одновременно, этот подход должен позволить безопасно выполнять обновления. Если приоритетное отношение будет существовать среди вещей, которые могут попытаться обновить тот же элемент, наивысший приоритет гарантированно преуспеет с первой попытки, следующий наивысший приоритет преуспеет с любой попыткой, которая не предваряется наивысшим приоритетом, и т. д. Если используется блокировка, первоочередной задаче, которая хотела бы выполнить обновление, придется подождать обновление с более низким приоритетом для завершения; используя парадигму CompareAndSwap, задача с самым высоким приоритетом не будет затронута более низкой (но заставит более низкую выполнять ненужную работу).