ВходНаше всё Теги codebook 无线电组件 Поиск Опросы Закон Четверг
21 ноября
1460426
klen (31.08.2024 23:02, просмотров: 1120)
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 ... :)