Jump to content

    
Sign in to follow this  
turnon

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

Recommended Posts

Нужен счетчик количества микросекунд, прошедших с момента старта МК (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()

Edited by turnon

Share this post


Link to post
Share on other sites
Насколько мне известно, переменные длинее байта обновляются не за одну команду МК.

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

 

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

Не учел. Представь такую ситуацию: функция 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; 
}

Share this post


Link to post
Share on other sites

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

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

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

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

 

Share this post


Link to post
Share on other sites
Предложу еще один вариант, который не требует запрета прерываний и работает как с ОС, так и без нее:

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

Да

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

Да

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

Да

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

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

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

Share this post


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

 

Share this post


Link to post
Share on other sites
Предложу еще один вариант, который не требует запрета прерываний и работает как с ОС, так и без нее:

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

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

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

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

 

Share this post


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

 

Share this post


Link to post
Share on other sites
Ну это же несерьезно даже для счетчика из 8-битных чисел ;)

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

Share this post


Link to post
Share on other sites
Предложу еще один вариант, который не требует запрета прерываний и работает как с ОС, так и без нее:

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

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

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

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

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

 

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

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

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

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

 

 

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this