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

Вашими устами да мед пить :)

Не буду оригинален: слив засчитан. Вы меня разочаровали.

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


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

Не буду оригинален ...

 

Ваш пример в прошлом посте будет работать, пока в очереди всегда будет меньше чем 255 не отправленных элементов.

 

Предположим, что в очереди уже есть 255 еще не отправленных элементов (набивали очередь перед отправкой), т.е. put = 0x00FF, а в это время get = 0x0000

Мы собираемся класть туда 256-й элемент, т.е. указатель изменит сразу 9 бит из 16, изменятся оба байта в указателе, put должен стать 0x0100, но по-байтно

 

После изменения младшего байта put будет 0x0000 и тут возникло прерывание, мы видим, что [get == put], логично предполагаем, что буфер пуст, останавливаем передачу.

Выходим из прерывания.

Тут указатель put сразу же стал 0x0100 (записали старшее слово), мы наивно набиваем очередь дальше, думая, что передача продолжается.

 

На практике в подавляющем числе проектов такая ситуация практически никогда не возникает, но она существует.

Этот пример больше для того, чтобы продемонстрировать опасность доступа в прерываниях к не атомарно изменяемым данным (указателям в частности).

 

Именно по этой причине для реализации длинных аппаратных таймеров (например, 16-битный на 8-битном камне) используются теневые регистры,

а обновление финального регистра таймера производится аппаратно только при записи лишь одного из байтов теневого регистра.

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


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

...

Ну наконец-то, я смог вытащить из вас пример:)

Да, вы правы, указанный кольцевой буфер работает лишь при условии атомарности изменений указателей на засовывание/выборку. То есть, на 8-битниках индекс до 255. Для 32-битниов ограничения нет.

Но это требование гораздо слабее того, что вы изначально утверждали:

Обращение к обоим указателям должно быть атомарным.

К каждому из указателей - да, атомарность нужна. К обоим - нет.

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


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

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

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


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

Известен способ правильного чтения длинных счетчиков в малоразрядных процессорах - читаем старший байт, затем младший, затем опять старший

Здесь это не поможет - здесь как раз чтение происходит в прерывании, и два чтения подряд дадут одинаковое значение.

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


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

Но это требование гораздо слабее того, что вы изначально утверждали:

Это требование относилось конкретно к вашему примеру.

 

К каждому из указателей - да,

Если указатель длиннее разрядности проца, то да. Иначе - нет.

 

атомарность нужна. К обоим - нет.

Нужна, если производится расчет свободного/занятого места вычитанием одного указателя из другого.

Нужно защитить (сделать атомарным): вычитывание значений указателей, анализ их разницы и действие, выполняемое при этом.

 

Вот пример:

 

Пусть put = 10, get = 9, т.е. в очереди остался всего один еще не отправленный элемент.

Перед отправкой каждого элемента производится расчет свободного места в очереди посредством сравнения двух указателей и выполнения соотв. действий.

Предположим, произошло прерывание в отправляющей задаче сразу после копирования put и get в некие временные переменные, но проанализировать и принять решение еще не успели.

 

В прерывании смотрим наличие данных в буфере, сравнивая put и get, тут они не равны,

берем данные из буфера, изменяем указатель get (= 10), отправляем данные, выходим из прерывания

по некоторой причине управление долго не передавалось отправляющей задаче (работали другие более приоритетные задачи) и вновь возникло прерывание от передатчика,

в этом прерывании смотрим, что get == put и прекращаем передачу (прерываний от передатчика не будет, пока заново их не запустим) выходим из прерывания.

Наконец-то попадаем в отправляющую задачу.

 

В данный момент w = 10, get = 9. Т. е. буфер как бы непустой, смело докладываем новый элемент.

А так, как буфер был непустой, то запуск передачи не делаем (не формируем соотв. прерывание от передатчика).

В итоге кладем в буфер данные, надеясь, что они отправляются, а это не так.

По-хорошему, решение о запуске прерываний нужно делать повторно вычитывая put и get.

Но кто даст гарантию, что прерывание не возникнет после сравнения put/get но перед принятием решения и запуске прерываний от передатчика?

 

Т. е. проблема тут не в самих указателях, а в действиях, предпринимаемых для запуска/останова аппаратного передатчика.

Фактически, нужно делать атомарным (для прерывания от передатчика): чтение указателей, сравнение, анализ результата и управление передатчиком.

 

 

 

 

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


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

Так запретите прерывание чтения, когда модифицируете счетчик записи. Проблема, не стоящая ломаного яйца.

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


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

Это требование относилось конкретно к вашему примеру.

Это вы не со мной спорили. Я же специально сделал цитату со ссылкой - ткните, посмотрите контекст. Причём на тот момент восьмибитность ещё не упоминалась, это вы её уже потом за уши притянули, что само по себе доставляет: в разделе ARM, в теме про LDREX/STREX (!).

 

В данный момент w = 10, get = 9. Т. е. буфер как бы непустой, смело докладываем новый элемент.

А так, как буфер был непустой, то запуск передачи не делаем (не формируем соотв. прерывание от передатчика).

В итоге кладем в буфер данные, надеясь, что они отправляются, а это не так.

А при чём здесь буфер? Буфер здесь не при делах, его дело - быть буфером. Принять элемент, и выдать его потом. В порядке очереди. И описанный буфер это делает, чётко, без сбоев.

А описанная вами проблема ортогональна кольцевому буферу. Решать можно кучей способов. Например, разрешая прерывания без всяких проверок - раз мы положили элемент в буфер, то безусловно включаем прерывание, потому что в буфере гарантированно имеются данные. Или, как предложил ViKo - запрещая прерывания передатчика в момент засовывания элемента в буфер.

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


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

Вот пример:

 

Пусть put = 10, get = 9, т.е. в очереди остался всего один еще не отправленный элемент.

Конкретный пример кольцевого буфера на Си.

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

Сначала проверяется возможность чтения, потом читается, и только после этого - переписывается указатель.

Кстати, лично у меня использование глобальных переменных в функции в прямом виде - приводит к их использованию в виде локальных переменных. В результате любое изменение сразу записывается в память (это грёбаное ускорение от GCC). Чтобы этого не происходило - нужно глобальные перемененные переписывать локальные принудительно, и уже с ними работать. Этот глюк появился в GCC с переходом от 4,2 до 4,8 версии.

Кроме всего прочего, частое переписывание указателей (по чайной ложке) - это банальная нагрузка на память.

 

Буфер всегда должен иметь хотя-бы один пустой элемент, это есть защита от глюка.

Одинаковые указатели - признак пустого буфера.

 

 

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


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

Решать можно кучей способов. Например, разрешая прерывания без всяких проверок - раз мы положили элемент в буфер, то безусловно включаем прерывание,

Именно! А речь ведь была про то, что запреты/разрешения прерываний тут не нужны.

Я же пытаюсь донести, что не все тут так просто.

 

Я делаю так.

В передающей задаче при передаче данных вот что делается.

В методе отправке элемента данных: если буфер пустой, просто взводится флажок прерывания от передатчика о том, что он пустой (регистр передатчика пуст), т. е. принудительно запускаем передачу.

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

Поэтому перед запуском передачи следует просто временно запретить прерывание от передатчика (только от него).

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

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

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

Никакие другие прерывания запрещать, конечно же, не нужно.

В особых случаях можно принудительно заново проинициализировать весь передатчик. Особенно, если допускается на ходу менять скорость обмена.

 

В прерывании передатчика использую еще один флажок прерывания - передача окончена, это когда опустел и аппаратный буфер передатчика и опустел выходной сдвиговый регистра передатчика (речь про USART).

Тут у меня к задачам семафорится факт того, что все передачи завершены.

Это очень удобно для построения протокола в стиле запрос-ответ, особенно по каналу RS-485, где нужно управлять направлением работы внешнего трансивера.

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

 

По аналогии работаю с приемником, но чуть иначе: запрещать прерывания от приемника нужно лишь перед вызовом flush(), после сразу же разрешить.

По аналогии с передатчиком нужно принудительно очищать весь аппаратный буфер приемника.

Если этого не делать, то указатель приема может отличаться от указателя передачи по выходу из flush. Это при условии, что flush вызывается в фоне задачи.

В принимающей задаче если используется счетный семафор RTOS, запрещать прерывания от приемника уже не нужно: по сути защиту от исчерпания буфера обеспечивает семафор.

 

Вот именно об этих критических секциях я и говорил.

 

Без них код тоже будет работать, но пока не дойдет до граничных условий, одно из которых я описал выше.

Однажды я столкнулся с подобным поведение кода, больше не хочу.

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


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

Я же пытаюсь донести, что не все тут так просто.

Не плюй в колодец, пригодится.

Сначала проверяем свободное место, потом пишем данные, потом переписываем указатель.

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

 

А для USART кольцевой буфер мало подходит, для него необходимо иметь два линейных!!! Переписывать данные в ручном режиме, дабы дма могло нормально работать.

(я охреневаю от всплывающих подробностей :twak: )

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


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

Сначала проверяем свободное место, потом пишем данные, потом переписываем указатель.

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

Я именно так и делаю, но прерывания временно запрещаю, т. к. между проверкой и изменением указателя проходит время, в которое может влезть прерывание, которое обращается к тем же указателям.

Без эти временных запретов в одном из моих старых проектов вылезала редкая и потому очень трудноуловимая ошибка (((

 

А для USART кольцевой буфер мало подходит,
У меня два кольцевых буфера - один на прием, другой на передачу.

Каждый буфер работает независимо от другого. По сути они ничего не знают друг о друге. Временно запрещают прерывания только от своих источников.

Более того, обе задачи, которые разгребают эти буферы, тоже ничего не знают друг о друге.

Приемная задача разгребает свою очередь, проверяя контрольные суммы, заголовки и т.п.

Передающая задача наоборот, все это упаковывает и отправляет на передачу.

Задачи выше работают с этими задачами на уровне логических пакетов, ни как не связанных со средой передачи.

Разумеется, реализованы механизмы таймаута как между пакетами, а так и между байтами. Контроль битых пакетов. Перезапуск разорванной и восстановленной связи.

В итоге любые сбои в канале связи никогда не вызывают опасных непредсказуемых глюков в приложении.

 

 

 

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


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

Именно!

Вы поразительный человек! Вроде как уже всё объяснено, разжёвано до мельчайших подробностей, и тут вы с репликой "Именно!" начинаете всю канитель по-новой. Я так общаться не могу, чесслово.

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


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

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

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

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

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

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

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

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

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

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