Перейти к содержанию
    

Вариант реализации атомарного счетчика

Нужен счетчик количества микросекунд, прошедших с момента старта МК (STM32).

 

Насколько мне известно, переменные длинее байта обновляются не за одну команду МК. И надо чтобы в момент обновления была уверенность, что переменную в это время не читают, потому как она скорее всего будет иметь некорректное значение (1 байт обновился, второй еще и нет и т.д.)

 

Вот набросал такой счетчик, для проверки атомарности используется флаг _busy. Имеет право на жизнь такая конструкция или чего-то не учел?

 

class AtomicUint64
{
  private:
    volatile uint8_t _busy;
    volatile uint64_t _counterBackup;
    volatile uint64_t _counterValue;
  public:
    AtomicUint64(){
      _busy = 0;
      _counterBackup = 0;
      _counterValue = 0;
    }
    
    inline void inc(uint8_t value = 1){
      _counterBackup = _counterValue + value;
      _busy = 1;
      _counterValue += value;
      _busy = 0;
    }
    
    inline uint64_t get(){
      if (_busy)
        return _counterBackup;
      else    
        return _counterValue;
    }
};

extern AtomicUint64 gTick1MsecCounter;

 

Как используется. В прерывании 1 мsec вызывается gTick1MsecCounter.inc(), в остальном коде где нужно знать текущее значение счетчика - gTick1MsecCounter.get()

Изменено пользователем turnon

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Насколько мне известно, переменные длинее байта обновляются не за одну команду МК.

Нет, зависит от архитектуры.

 

Имеет право на жизнь такая конструкция или чего-то не учел?

Не учел. Представь такую ситуацию: функция get вызывается из фона, проверяется флаг busy. Так как он равен нулю, то осуществляется переход к return. Чтобы возвратить 64-битное значение нужно скопировать его из памяти в регистры R0 и R1. Копирование происходит по 32 бита. Одна часть скопировалась. Тут происходит прерывание в котором вызывается inc, которая увеличивает значение счетчика. Прерывание завершается и управление возвращается get, которая копирует оставшиеся 32 бита, потенциально изменившиеся в прерывании. В итоге получается хрень.

 

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

 

static uint64_t counter;

void inc(uint32_t value)
{
    __disable_interrupt();
    counter += value;
    __enable_interrupt();
}

uint64_t get(void)
{
    __disable_interrupt();
    uint64_t copy = counter;
    __enable_interrupt();   

    return copy; 
}

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Предложу еще один вариант, который не требует запрета прерываний и работает как с ОС, так и без нее:

1) Копируете во временную переменную счетчик (пусть он будет 64-битный, а копирование идет по 32 бита).

2) Копируете в еще одну временную переменную старшее слово счетчика

3) Сравниваете старшую часть первой переменной со второй. Если они одинаковы - отдаете первую переменную. Если они разные - считываете счетчик в первую переменную еще раз и отдаете ее.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Если они разные - считываете счетчик в первую переменную еще раз и отдаете ее.

Так тут ведь та же самая проблема будет.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Предложу еще один вариант, который не требует запрета прерываний и работает как с ОС, так и без нее:

1) Копируете во временную переменную счетчик (пусть он будет 64-битный, а копирование идет по 32 бита).

Да

2) Копируете в еще одну временную переменную старшее слово счетчика

Да

3) Сравниваете старшую часть первой переменной со второй. Если они одинаковы - отдаете первую переменную.

Да

Если они разные - считываете счетчик в первую переменную еще раз и отдаете ее.

Нет. В общем случае надо повторять в цикле с пункта 1). Просто неизвестно сколько времени проходит между обращениями и счетчик уже может прокрутиться на все младшие биты.

Собственно предложенное это классика для считывания аппаратных счетчиков, которые нельзя остановить.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Просто неизвестно сколько времени проходит между обращениями и счетчик уже может прокрутиться на все младшие биты.
Ну это же несерьезно даже для счетчика из 8-битных чисел ;)

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Предложу еще один вариант, который не требует запрета прерываний и работает как с ОС, так и без нее:

1) Копируете во временную переменную счетчик (пусть он будет 64-битный, а копирование идет по 32 бита).

2) Копируете в еще одну временную переменную старшее слово счетчика

3) Сравниваете старшую часть первой переменной со второй. Если они одинаковы - отдаете первую переменную. Если они разные - считываете счетчик в первую переменную еще раз и отдаете ее.

Чего-то не пойму принцип работы. Почему на шаге 2 именно старшее слово? Меняться начинает со старшего слова?

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Так тут ведь та же самая проблема будет.
Какая та же самая? Если старшие байты совпадают, то младший гарантированно относится к текущему значению старшего.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Ну это же несерьезно даже для счетчика из 8-битных чисел ;)

Лучше написать так, что-бы потом не думать серьезно это или несерьезно. Я не просто так написал, что это стандартный прием считывания аппаратных счетчиков. Мне, например, доводилось считывать счетчик тактов процессора в 16bit контроллере. Если речь идет о счетчике секунд, то тогда, конечно, уже можно рассуждать ну как же так он может неуспеть? А можно написать раз и навсегда, так, что бы и думать не пришлось.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Предложу еще один вариант, который не требует запрета прерываний и работает как с ОС, так и без нее:

1) Копируете во временную переменную счетчик (пусть он будет 64-битный, а копирование идет по 32 бита).

2) Копируете в еще одну временную переменную старшее слово счетчика

3) Сравниваете старшую часть первой переменной со второй.....

Чего-то не пойму принцип работы. Почему на шаге 2 именно старшее слово? Меняться начинает со старшего слова?

Это если в пункте 1 скопировать сначала старшее слово, затем младшее.

 

Более простой для понимания вариант, но не оптимальный.

1) Копируете во временную переменную счетчик.

2) Копируете в другую временную переменную счетчик.

3) Сравниваете, если совпало то возвращаете, иначе всё сначала - то есть идти на шаг 1

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...