riscv32imAc: попробывал использовать атомарные инструкции для реализации
спинлока ради изучения - интересно и почти понятно :) вроде
работает. может кому будет интересно. процессоры riscv c расширением “A” Standard Extension for Atomic Instructions имеют особые атомарные инструкции подобные эксклюзивной записи и чтению и сбросу STREX LDREX CLREX в armv7-em. только их больше.
из того что я пробывал они не реализованы только в ch32v003 - там ядро riscv32ec - порезанный ну совсем до всего несколько тысяч транзисторов.
как известно компилятор такие инструкции не может использовать поскольку логику синхронизации потоков кода в линейном С/С++ коде объяснить невозможно, поэтому стандартная метода в таких случаях inline asm
/*
* riscv32а++.h
*
* Created on: 31 авг. 2024 г.
* Author: klen
*/
#pragma once
// The RISC-V Instruction Set Manual Volume I, Unprivileged Architecture Version 20240411
// Chapter 14. "A" Extension for Atomic Instructions, Version 2.1
// определения общие для расширения riscv32a
namespace riscv32a
{
__ais__ auto amo_swapw(volatile int32_t& obj, const int32_t val) noexcept { int32_t result; asm volatile ("amoswap.w %0, %2, %1" : "=r"(result), "+A"(obj) : "r"(val) : "memory"); return result; }
__ais__ void amo_addw (volatile int32_t& obj, const int32_t val) noexcept { int32_t result; asm volatile ("amoadd.w %0, %2, %1" : "=r"(result), "+A"(obj) : "r"(val) : "memory");}
__ais__ void amo_andw (volatile int32_t& obj, const int32_t val) noexcept { int32_t result; asm volatile ("amoand.w %0, %2, %1" : "=r"(result), "+A"(obj) : "r"(val) : "memory");}
__ais__ void amo_maxw (volatile int32_t& obj, const int32_t val) noexcept { int32_t result; asm volatile ("amomax.w %0, %2, %1" : "=r"(result), "+A"(obj) : "r"(val) : "memory");}
__ais__ void amo_minw (volatile int32_t& obj, const int32_t val) noexcept { int32_t result; asm volatile ("amomin.w %0, %2, %1" : "=r"(result), "+A"(obj) : "r"(val) : "memory");}
__ais__ void amo_orw (volatile int32_t& obj, const int32_t val) noexcept { int32_t result; asm volatile ("amoor.w %0, %2, %1" : "=r"(result), "+A"(obj) : "r"(val) : "memory");}
__ais__ void amo_xorw (volatile int32_t& obj, const int32_t val) noexcept { int32_t result; asm volatile ("amoxor.w %0, %2, %1" : "=r"(result), "+A"(obj) : "r"(val) : "memory");}
__ais__ void amo_maxuw(volatile uint32_t& obj, const uint32_t val) noexcept { uint32_t result; asm volatile ("amomaxu.w %0, %2, %1" : "=r"(result), "+A"(obj) : "r"(val) : "memory");}
__ais__ void amo_minuw(volatile uint32_t& obj, const uint32_t val) noexcept { uint32_t result; asm volatile ("amominu.w %0, %2, %1" : "=r"(result), "+A"(obj) : "r"(val) : "memory");}
// реализация аппаратного spinlock
// ожидание-захват блокировки
// obj - ссылка на бъект типа uint32_t хранящий состояние блокировкии (фактически 0 или ненулевой идентификатор владельца)
// owner - владелец выполняющий блокировку
__ais__ auto amo_spin_lock(volatile uint32_t& obj, const uint32_t owner = -1) noexcept
{
asm volatile (
"again%=: \n" // метка цикла
"lw t0, %0 \n" // чтение в t0 значения состояния объекта блокировки
"bnez t0, again%= \n" // если оно "1" -> зацикливаемся с проверкой
"amoswap.w.aq t0, %[owner], %[obj] \n" // атомарная запись индекса владельца по адресу объекта блокировки obj
"bnez t0, again%= \n" // циклическая проверка
: : [obj]"A"(obj), [owner]"r"(owner) : "t0", "memory"
) ;
}
// освобождение блокировки
__ais__ auto amo_spin_unlock(volatile uint32_t& obj ) noexcept
{
asm volatile ("amoswap.w.rl x0, x0, %[obj]" : : [obj]"A"(obj) : "memory" ); // атомарный сброс блокировки
}
class amo_spinlock_t
{
public:
__ai__ void lock (const uint32_t owner = -1) noexcept { amo_spin_lock(obj, owner); }
__ai__ auto lock_try (const uint32_t owner = -1) noexcept
{
if (obj) [[ likely ]]{ return false; }
else { amo_spin_lock(obj, owner); return true ; } ;
}
__ai__ void unlock() noexcept { amo_spin_unlock(obj); }
__ai__ auto owner () noexcept { return obj ;}
private:
volatile uint32_t obj ;
};
}
using namespace riscv32a ;
используем так :
volatile int32_t sl ;
void task1_code(void)
{
....
....
while(1)
{
....
....
amo_spin_lock(sl, task1);
// защищаемый блокировкой код
....
....
amo_spin_unlock(sl);
....
....
}
}
void task2_code(void)
{
....
....
while(1)
{
....
....
amo_spin_lock(sl, task2);
// защищаемый блокировкой код
....
....
amo_spin_unlock(sl);
....
....
}
}
}
что мы с этого имее и на кой хрен оно нам нужно?
замечание - будем предполагать что используем например FreeRTOS
1. тупое использование spinlock - на достаточно короких участках защищаемого блокировкой кода не выигрываем по скорости у критических секций реализованных в FreeRTOS на запрете прерываний которое требуется для исключения переклбючения потоков - а это круто, ибо теперь мы оставили в покое прерывания.
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS(); <------- ЖОППА!!!!
uxCriticalNesting++;
/* This is not the interrupt safe version of the enter critical function so
* assert() if it is being called from an interrupt context. Only API
* functions that end in "FromISR" can be used in an interrupt. Only assert if
* the critical nesting count is 1 to protect against recursive calls if the
* assert function also uses a critical section. */
if( uxCriticalNesting == 1 )
{
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
}
}
/*-----------------------------------------------------------*/
void vPortExitCritical( void )
{
configASSERT( uxCriticalNesting );
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS(); <------- ЖОППА!!!!
}
}
вот так! испольнение критической секции может спортить жизнь пропуском выстрой ракции на критическое событие... мотор сгорел и выпустил синий дым..
2. аналогично спинлоку используя amo_addw можно реализовать счетный семафор с аналогичными свойствами.
3. а теперь давайте подумаем мозгиком...а вот если делать не по тупому - можно улчушить FreeRTOS. если вы загляните в ее код то много где найдете такую хрень как оберка api
функциями vTaskSuspendAll()/xTaskResumeAll() ... и что мы там видим внутри? правильно - все тотже vPortEnterCritical() /vPortExitCritical() - большую часть времени выпольнен вызовов api FreeRTOS прерывания запрещены.
... ну нет у Ричарда Барри инструмента исключить коллизии чтения записи кроме как запретить и непущать!
в теперь представим что микроконтроллер стал двухядерным ... любой вызов апи будет лочить все ядра.. полный аншлаг.
я как бы намекаю на то что все можн очудненько ускорить улучшить. мне кажется когда FreeRTOS была версии 4 или 5 это эдак в 2010...2011 году за все эти косяки его нещадно тыкали носом в говнище. потом freertos стаола популярной как в свое врем USB и все забылось и простилось.
тогда я пробывал прокрутить фарш.. но чтото у меня тогда пошло не так. теперь наверно пришло время сделать подход к снаряду на riscv ... :)