Pull to refresh

Comments 18

read_pos %= sizeof(buf);
этот кусочек тоже должен быть атомарным, правильнее делать
rpos %= sizeof(buf);
Спасибо. Всё ещё хуже. Вот что значит накидать пример навскидку. Атомарной должна быть операция считывание-инкремент-ограничение.
Я написал врезку про эту проблему. Ещё раз спасибо!
О чем статья? Мне интересно программирование, особенно микроконтроллеров. До ката не понятно, чего и на чем, и под какую ОС писать будем. Ок. Перехожу на статью. И опять изложение какого-то материала по теме абстрактного программирования. Вижу ссылку на предыдущую статью, теперь-то уж наверняка станет понятно что и для чего! Перехожу… опять кирпич. Тут уж читать текст непонятной направленности у меня терпения не хватило.
Уважаемы Дмитрий, вы чего пишете-то? Судя по тексту вы хороша разбираетесь в теме, но вот в какой теме?
На мой консервативный взгляд, в любой статье должно быть введение, где кратко выполняется постановка задачи. По традиции Хабра это введение размещается до ката. Прочитал его и становится ясно, стоит ли читать дальше.
Базовые примитивы в любой ОС, как правило, соответствуют трём перечисленным. Для чего именно они нужны и каковы основные механизмы применения — показано в примерах. Возможно, Вам пока не встретилась задача, которая требует синхронизации. Встретится — возвращайтесь.
Виноват. Не разобрался. Глупые вопросы задаю. Спасибо, что карму понизили.
Справедливо урезонили дерзость зарвавшегося юнца. (с)
Вот пример для микроконтроллера. Это туннель TCP/IP-RS485 для atmega128 под EtherNut NutOs, там мьютексы используются для синхронизации работы механизма управление полудуплексом.
Семафоры, спины… о каких процессорах идет речь? Мне подобное встречалось только в VME — циклы Read-Modify-Write, ловушки на на указанный диапазон адресов, спины — но все это было аппаратное. На какое железо рассчитана эта статья, какая архитектура?
Они могут быть реализованы не только апаратно а и вполне софтово. И реализация есть почти в любой OC.
Спасибо за труды. Написано легко, компактно и на русском, потому читаю хоть и знал. Пожелания — в тексте предполагается многопроцессорная машина, а описанные примитивы вовсю применяются на однопроцессорных системах с разделением времени. Как обстоят в этом случае дела, раскройте тему.
Желательно также не забыть, что переменные, через которые мы обмениваемся данными между нитями, должны быть помечены как volatile, иначе компилятор запросто построит нам такую оптимизацию, которая легко сломает логику работы кода.
Лишнее. Кэширование переменной не может пересекать барьер, которым является мьютекс, а если реализация вызываемой функции для компилятора непрозрачна, он не имеет права делать кэширование из-за возможности алиасинга.

Ещё совсем уж вскользь упомяну, что реализация SMP в процессорах Интел совершенно всепрощающа и гарантирует программисту, что все процессоры видят память одинаково, хотя и имеют раздельные кеши.
Это не так. Процессоры могут видеть память по-разному, если не используется синхронизация.

Правда, останутся проблемы с read_pos %= sizeof(buf); — строго говоря, эту операцию нужно делать атомарно «внутри» atomic_add. То есть должна быть полная атомарная операция считывание-инкремент-ограничение.
Можно так:
do
{
    rpos = atomic_read(&read_pos);
} while (rpos != atomic_cmpxchg(&read_pos, rpos, (rpos + 1) % sizeof(buf));
ret = buf[rpos];
Про volatile — Вы предполагаете, что логика использования переменной обязательно пересекается с вызовами примитивов синхронизации, что необязательно. Правы Вы в том, что не всегда обязательно и помечать переменные как volatile, но статья-то вводная, лучше перебдеть.

Насчёт процессоров и памяти — да, я выдал Интелу избыточный кредит, Вы правы. Вероятно, меня сбил с толку тот факт, что типичная реализация спинлока для Интела является барьером, поэтому целостность появляется автоматически при использовании любого примитива синхронизации.
Вы предполагаете, что логика использования переменной обязательно пересекается с вызовами примитивов синхронизации, что необязательно.
Можете привести пример? По мне, если без volatile не работает, это баг, который через перебдение предлагается замести под ковёр.
(Вообще признаю, что мне пришлось задуматься. Но:)

Кусок данных, который читает железка на другой стороне. С ней нельзя договориться о синхронизации. Нужно выставить данные в нужном порядке и гарантировать, что до того, как будет выставлен бит "данные готовы" они будут действительно готовы. То есть — явно запретить реордер записи в память.

(Там ещё и кеш надо явно сбрасывать, или через некешируемое окно работать.)

Но в изрядной степени я Ваше возражение приму. В целом lock — поработали — unlock при том, что lock-unlock являются fence-ами — вполне типичная картина.
Ещё один, менее очевидный и не связанный с железом вариант: ресурс с доступом по схеме RCU. Реализация rcu_dereference(p) должна обращаться к p как к volatile, чтобы компилятор не мог повторно обратиться к p (вместо lp) в следующем примере:

T *p;

rcu_read_lock();
T *lp = rcu_dereference(p);
<use lp>
<use lp again>
rcu_read_unlock();
Sign up to leave a comment.

Articles