Имитация LDREX / STREX (загрузка / сохранение без учета) в Cortex-M0 - PullRequest
10 голосов
/ 21 апреля 2011

В наборе команд Cortex-M3 существует семейство инструкций LDREX / STREX, так что если местоположение читается с помощью инструкции LDREX, следующая инструкция STREX может записать этот адрес только в том случае, если известно, что адрес былнетронутым.Как правило, эффект состоит в том, что STREX будет успешным, если после LDREX не было никаких прерываний («исключений» на языке ARM), но в противном случае произошел сбой.

Какой самый практичный способ симулировать такое поведение в Cortex M0?Я хотел бы написать C-код для M3 и сделать его переносимым на M0.На M3 можно сказать что-то вроде:

__inline void do_inc(unsigned int *dat)
{
  while(__strex(__ldrex(dat)+1,dat)) {}
}

для выполнения атомарного приращения.Единственными способами, которые я могу придумать для достижения аналогичной функциональности в Cortex-M0, было бы либо:

  1. иметь "ldrex" отключить исключения и иметь "strex" и "clrex" их повторно включить,с требованием, чтобы вскоре за каждым «ldrex» следовал либо «strex», либо «clrex».
  2. Пусть "ldrex", "strex" и "clrex" будут очень маленькими подпрограммами в ОЗУ, причем одна команда "ldrex" будет исправлена ​​либо в "str r1, [r2]", либо в "mov r0, # 1".».Пусть подпрограмма «ldrex» вставит инструкцию «str» в подпрограмму «strex», а там будет подпрограмма «clrex» mov r0, # 1 ».Есть все исключения, которые могут сделать недействительным вызов последовательности «ldrex» «clrex».

В зависимости от того, как используются функции ldrex / strex, отключение прерываний может работать разумно, но кажется странным изменить семантику «исключая нагрузку», чтобы вызвать плохие побочные эффекты, если она будет отменена.Идея исправления кода выглядит так, как будто бы она достигла желаемой семантики, но кажется неуклюжей.

(Кстати, дополнительный вопрос: мне интересно, почему STREX на M3 хранит указатель успеха / неудачи в регистре, а не простоустановка флага? Его фактическая операция требует четырех дополнительных битов в коде операции, требует, чтобы регистр был доступен для хранения индикации успеха / неудачи, и требует, чтобы "cmp r0, # 0" использовался, чтобы определить, удалось ли это.Ожидали ли вы, что компиляторы не смогут разумно обрабатывать внутреннюю сущность STREX, если они не получат результат в регистре? Для переноса в регистр требуется две короткие инструкции.)

Ответы [ 3 ]

4 голосов
/ 25 апреля 2011

Ну ... у вас все еще осталось SWP, но это менее мощная атомарная инструкция.

Отключение прерываний обязательно сработает.: -)

Редактировать:

Нет SWP для -m0, извините суперкат.

ОК, кажется, у вас осталось только отключение прерываний.Вы можете использовать встроенный ассемблер gcc в качестве руководства, как отключить и правильно восстановить его: http://repo.or.cz/w/cbaos.git/blob/HEAD:/arch/arm-cortex-m0/include/lock.h

1 голос
/ 06 февраля 2015

Cortex-M3 был разработан для тяжелой многозадачности с малой задержкой и малым джиттером, то есть контроллер прерываний взаимодействует с ядром, чтобы сохранить гарантии на количество циклов с момента запуска прерывания до обработки прерывания. Ldrex / strex был реализован как способ взаимодействия со всем этим (под всем, что я имею в виду маскирование прерываний и другие детали, такие как установка атомарного бита с помощью псевдонимов битовых полос), в качестве одноядерного, не MMU, некэш-кэша µC иначе мало пользы для этого. Если бы он не реализовал это, задача с низким приоритетом должна была бы удерживать блокировку, и это могло бы генерировать инверсии с небольшим приоритетом, генерируя задержки и джиттер, что является сложной системой реального времени (это дизайн для этого, хотя концепция слишком широка) не может справиться, по крайней мере, не в порядке, допустимом семантикой «повторных попыток», которой обладает отказавший ldrex / strex.

Если учесть, что, строго говоря, с точки зрения синхронизации и джиттера, Cortex-M0 имеет более традиционный профиль синхронизации прерываний (то есть он не будет прерывать команды на ядре при поступлении прерывания), поскольку подвергается большему воздействию джиттер и латентность. По этому вопросу (опять же, строго по времени) он более сопоставим со старыми моделями (например, arm7tdmi), в которых также отсутствует атомная загрузка / изменение / сохранение, а также атомарные приращения и уменьшения и другие совместные инструкции с малой задержкой, требующие отключения / прерывания. включить чаще.

Я использую что-то подобное в Cortex-M3:

#define unlikely(x) __builtin_expect((long)(x),0)
    static inline int atomic_LL(volatile void *addr) {
      int dest;

  __asm__ __volatile__("ldrex %0, [%1]" : "=r" (dest) : "r" (addr));
  return dest;
}

static inline int atomic_SC(volatile void *addr, int32_t value) {
  int dest;

  __asm__ __volatile__("strex %0, %2, [%1]" :
          "=&r" (dest) : "r" (addr), "r" (value) : "memory");
  return dest;
}

/**
 * atomic Compare And Swap
 * @param addr Address
 * @param expected Expected value in *addr
 * @param store Value to be stored, if (*addr == expected).
 * @return 0  ok, 1 failure.
 */
static inline int atomic_CAS(volatile void *addr, int32_t expected,
        int32_t store) {
  int ret;

  do {
    if (unlikely(atomic_LL(addr) != expected))
      return 1;
  } while (unlikely((ret = atomic_SC(addr, store))));
  return ret;

}

Другими словами, он переводит ldrex / strex в хорошо известное условие Linked-Load и Store Conditional, а также реализует семантику сравнения и замены.

Если ваш код работает нормально только с помощью сравнения и замены, вы можете реализовать его для cortex-m0 следующим образом:

static inline int atomic_CAS(volatile void *addr, int32_t expected,
        int32_t store) {
  int ret = 1;

   __interrupt_disable();
   if (*(volatile uint32_t *)addr) == expected) {
      *addr = store;
      ret = 0;
   }
   __interrupt_enable();
   return ret;
}

Это наиболее часто используемый шаблон, потому что он есть у некоторых архитектур (на ум приходит x86). Реализация эмуляции шаблона LL / SC с помощью CAS кажется мне ужасной. Особенно, когда SC более чем на несколько инструкций отделен от LL, но хотя и очень распространен, ARM не рекомендует его специально в случае Cortex-M3, потому что при любые прерывания приведут к сбою strex, если вы запустите если вы слишком долго переходите между ldrex / strex, ваш код будет тратить много времени на повторение цикла strex. Это злоупотребляет шаблоном, а не использует его.

Что касается вашего дополнительного вопроса, в случае cortex-m3 возвращение strex в регистр, потому что семантика уже была определена высокоуровневыми архитектурами (strex / ldrex существует в многоядерных ветвях, которые были реализованы до того, как armv7-m был определяется, и после него контроллеры кеша фактически проверяют адреса ldrex / strex, т.е. strex дает сбой только тогда, когда кеш не может доказать, что точка адреса не была изменена).

Если бы я размышлял, я бы сказал, что это было потому, что в ранние годы этот тип атомной системы разрабатывался в библиотеках: вы возвращали бы успех / неудачу в функциях, реализованных в ассемблере, и это должно было бы уважать ABI и большинство из них (все, что я знаю) использует регистр или стек, а не флаги для возврата значений. Это также может быть связано с тем, что компиляторы лучше используют раскраску регистров, чем сжимают флаги в случае, если их использует какая-то другая инструкция, т.е. рассматривают сложную операцию, которая генерирует флаги, и в середине у вас есть последовательность ldrex / strex и операция для этого нужны флаги: компилятору все равно придется перемещать флаги в регистр.

0 голосов
/ 14 марта 2014

STREX / LDREX предназначены для многоядерных процессоров, имеющих доступ к общим элементам в памяти, которая распределяется между ядрами. ARM сделал необычно плохую работу, документировав это, вы должны прочитать между строк в документах amba / axi и arm и trm, чтобы понять это.

Как это работает, если у вас есть ядро, которое поддерживает STREX / LDREX, и если у вас есть контроллер памяти, который поддерживает эксклюзивный доступ, тогда, если контроллер памяти видит пару исключительных операций, и между ними нет доступа к другому ядру, тогда вы верните EX_OKAY, а не OKAY. Документы-инструкции сообщают разработчикам микросхем, является ли это однопроцессором (не реализующим многоядерную функцию), тогда вам не нужно поддерживать exokay, просто верните okay, что с точки зрения программного обеспечения нарушает пару LDREX / STREX для обращений, которые затрагивают эту логику (программное обеспечение вращается в бесконечном цикле, так как он никогда не вернет успех), кэш L1 поддерживает его, хотя, кажется, он работает.

Для однопроцессорных систем и для случаев, когда вы не обращаетесь к памяти, совместно используемой ядрами, используйте SWP.

-m0 не поддерживает ни ldrex / strex, ни swp, но что именно вы получаете? Они просто получают доступ, на который вы не влияете. чтобы не дать вам надавить на себя, просто отключите прерывания на время, как мы делали атомный доступ со времен темных веков. если вам нужна защита от вас и от периферийного устройства, если у вас есть периферийное устройство, которое может создавать помехи, хорошо, что обойти это невозможно, и даже обмен может не помочь.

Так что просто отключите прерывания вокруг критической секции.

...