|
Вариант реализации атомарного счетчика, Укажите на проблемы, ежели таковые есть |
|
|
|
Aug 30 2015, 09:58
|
Местный
  
Группа: Свой
Сообщений: 340
Регистрация: 17-10-14
Пользователь №: 83 207

|
Нужен счетчик количества микросекунд, прошедших с момента старта МК (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 - Aug 30 2015, 10:11
|
|
|
|
|
 |
Ответов
(1 - 9)
|
Aug 30 2015, 10:20
|
Участник

Группа: Участник
Сообщений: 55
Регистрация: 13-09-12
Пользователь №: 73 530

|
Цитата Насколько мне известно, переменные длинее байта обновляются не за одну команду МК. Нет, зависит от архитектуры. Цитата Имеет право на жизнь такая конструкция или чего-то не учел? Не учел. Представь такую ситуацию: функция 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; }
|
|
|
|
|
Aug 30 2015, 11:34
|
Участник

Группа: Участник
Сообщений: 55
Регистрация: 13-09-12
Пользователь №: 73 530

|
Цитата(Сергей Борщ @ Aug 30 2015, 14:10)  Если они разные - считываете счетчик в первую переменную еще раз и отдаете ее. Так тут ведь та же самая проблема будет.
|
|
|
|
|
Aug 30 2015, 11:47
|

Гуру
     
Группа: Свой
Сообщений: 13 372
Регистрация: 27-11-04
Из: Riga, Latvia
Пользователь №: 1 244

|
QUOTE (Сергей Борщ @ Aug 30 2015, 14:10)  Предложу еще один вариант, который не требует запрета прерываний и работает как с ОС, так и без нее: 1) Копируете во временную переменную счетчик (пусть он будет 64-битный, а копирование идет по 32 бита). Да QUOTE 2) Копируете в еще одну временную переменную старшее слово счетчика Да QUOTE 3) Сравниваете старшую часть первой переменной со второй. Если они одинаковы - отдаете первую переменную. Да QUOTE Если они разные - считываете счетчик в первую переменную еще раз и отдаете ее. Нет. В общем случае надо повторять в цикле с пункта 1). Просто неизвестно сколько времени проходит между обращениями и счетчик уже может прокрутиться на все младшие биты. Собственно предложенное это классика для считывания аппаратных счетчиков, которые нельзя остановить.
--------------------
Feci, quod potui, faciant meliora potentes
|
|
|
|
|
Aug 30 2015, 17:38
|
Местный
  
Группа: Свой
Сообщений: 340
Регистрация: 17-10-14
Пользователь №: 83 207

|
Цитата(Сергей Борщ @ Aug 30 2015, 14:10)  Предложу еще один вариант, который не требует запрета прерываний и работает как с ОС, так и без нее: 1) Копируете во временную переменную счетчик (пусть он будет 64-битный, а копирование идет по 32 бита). 2) Копируете в еще одну временную переменную старшее слово счетчика 3) Сравниваете старшую часть первой переменной со второй. Если они одинаковы - отдаете первую переменную. Если они разные - считываете счетчик в первую переменную еще раз и отдаете ее. Чего-то не пойму принцип работы. Почему на шаге 2 именно старшее слово? Меняться начинает со старшего слова?
|
|
|
|
|
Aug 30 2015, 19:48
|

Гуру
     
Группа: Свой
Сообщений: 13 372
Регистрация: 27-11-04
Из: Riga, Latvia
Пользователь №: 1 244

|
QUOTE (Сергей Борщ @ Aug 30 2015, 20:37)  Ну это же несерьезно даже для счетчика из 8-битных чисел  Лучше написать так, что-бы потом не думать серьезно это или несерьезно. Я не просто так написал, что это стандартный прием считывания аппаратных счетчиков. Мне, например, доводилось считывать счетчик тактов процессора в 16bit контроллере. Если речь идет о счетчике секунд, то тогда, конечно, уже можно рассуждать ну как же так он может неуспеть? А можно написать раз и навсегда, так, что бы и думать не пришлось.
--------------------
Feci, quod potui, faciant meliora potentes
|
|
|
|
|
Sep 13 2015, 07:55
|
Местный
  
Группа: Участник
Сообщений: 313
Регистрация: 2-07-11
Пользователь №: 66 023

|
Цитата(Сергей Борщ @ Aug 30 2015, 14:10)  Предложу еще один вариант, который не требует запрета прерываний и работает как с ОС, так и без нее: 1) Копируете во временную переменную счетчик (пусть он будет 64-битный, а копирование идет по 32 бита). 2) Копируете в еще одну временную переменную старшее слово счетчика 3) Сравниваете старшую часть первой переменной со второй..... Цитата(turnon @ Aug 30 2015, 20:38)  Чего-то не пойму принцип работы. Почему на шаге 2 именно старшее слово? Меняться начинает со старшего слова? Это если в пункте 1 скопировать сначала старшее слово, затем младшее. Более простой для понимания вариант, но не оптимальный. 1) Копируете во временную переменную счетчик. 2) Копируете в другую временную переменную счетчик. 3) Сравниваете, если совпало то возвращаете, иначе всё сначала - то есть идти на шаг 1
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|