Ищу статьи по проблемам блокировки общей памяти - PullRequest
3 голосов
/ 18 апреля 2009

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

В среде Linux есть два процесса, которые подключают несколько сегменты разделяемой памяти. Первый процесс периодически загружает новый набор файлов для совместного использования и записывает идентификатор общей памяти (shmid) в местоположение в «главном» сегменте разделяемой памяти. Второй процесс постоянно читает это "главное" местоположение и использует shmid для прикрепления другие общие сегменты.

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

Поиск в интернете ничего не привел к чему-то определенному в этом площадь.

Я сильно подозреваю, что это небезопасно или нормально, и что бы я на самом деле Например, некоторые ссылки на статьи, подробно описывающие проблемы.

Ответы [ 13 ]

12 голосов
/ 18 апреля 2009

Это законно - так как в ОС вам это не помешает.

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

Там не было бы "искалеченных битов на проводе". Они будут выходить как единицами или нулями. Но нечего сказать, что все ваши биты будут записаны до того, как другой процесс попытается их прочитать. И нет НИКАКИХ гарантий относительно того, как быстро они будут написаны, и как быстро они будут прочитаны.

Вы всегда должны предполагать, что нет абсолютно НИКАКОЙ связи между действиями 2 процессов (или потоков в этом отношении).

Аппаратная блокировка шины уровня не происходит, если вы не понимаете это правильно. Может быть сложнее, чем ожидалось, заставить ваш компилятор / библиотеку / os / cpu сделать это правильно. Примитивы синхронизации написаны, чтобы убедиться, что это происходит правильно.

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


@ unknown - Вопрос несколько изменился с момента опубликования моего ответа. Однако описанное вами поведение явно зависит от платформы (аппаратного обеспечения, операционной системы, библиотеки и компилятора).

Не давая конкретных инструкций компилятору, вы на самом деле не гарантированно записали 32 бита за один кадр. Представьте себе ситуацию, когда 32-битное слово не выровнено по границе слова. Этот невыровненный доступ допустим для x86, а в случае x68 доступ превращается в серию выровненных обращений процессором.

Между этими операциями может произойти прерывание. Если переключение контекста происходит в середине, некоторые биты записаны, некоторые нет. Взрыв, ты мертв.

Также давайте подумаем о 16-битном или 64-битном процессоре. Оба они по-прежнему популярны и не обязательно работают так, как вы думаете.

Итак, на самом деле вы можете столкнуться с ситуацией, когда «какой-то другой процессор выбирает значение слова размером 1/2, записанное в». Вы пишете свой код так, как будто ожидается, что этот тип вещей произойдет, если вы не используете синхронизацию.

Теперь есть способы предварительно записать ваши записи, чтобы убедиться, что вы записали целое слово. Эти методы подпадают под категорию синхронизации, и создание примитивов синхронизации - это то, что лучше всего оставить разработчикам библиотек, компиляторов, ОС и аппаратного обеспечения. Особенно, если вы заинтересованы в переносимости (которой вы должны быть, даже если вы никогда не переносите свой код)

10 голосов
/ 22 апреля 2009

Проблема на самом деле хуже, чем обсуждали некоторые люди. Zifre прав в том, что в современных процессорах x86 записи в память являются атомарными, но это быстро перестает иметь место - записи в память являются атомарными только для одного ядра - другие ядра могут не видеть записи в том же порядке.

Другими словами, если вы делаете

a = 1;
b = 2;

в ЦП 2 вы можете увидеть местоположение b, измененное до того, как местоположение 'a' будет изменено. Кроме того, если вы записываете значение, которое больше, чем собственный размер слова (32 бита на процессоре x32), записи не являются атомарными - поэтому старшие 32 бита 64-битной записи попадут на шину в другое время по сравнению с низким 32 бита записи. Это может очень усложнить ситуацию.

Используйте барьер памяти, и все будет в порядке.

7 голосов
/ 18 апреля 2009

Тебе нужно где-нибудь запереть. Если не на уровне кода, то на аппаратном кеше памяти и на шине.

Вы, вероятно, в порядке на процессоре Intel после PentiumPro. Из того, что я только что прочитал, Intel сделала свои более поздние процессоры практически игнорирующими префикс LOCK для машинного кода. Вместо этого протоколы когерентности кэша обеспечивают согласованность данных между всеми процессорами. Поэтому, если код записывает данные, которые не пересекают границу строки кэша, он будет работать. Порядок памяти записывает, что перекрестные строки кэша не гарантированы, поэтому многословные записи рискованны.

Если вы используете что-то отличное от x86 или x86_64, то вы не в порядке . Многие не-Intel процессоры (и, возможно, Intel Itanium) повышают производительность, используя явные машинные команды когерентности кэша, и если вы не используете их (через пользовательский код ASM, встроенные функции компилятора или библиотеки), то запись в память через кэш не гарантируется когда-либо становятся видимыми для другого процессора или появляются в каком-либо определенном порядке.

То есть, если что-то работает в вашей системе Core2, это не значит, что ваш код верен. Если вы хотите проверить переносимость, попробуйте свой код и на других архитектурах SMP, таких как PPC (более старый MacPro или блейд Cell), Itanium, IBM Power или ARM. Alpha был отличным процессором для выявления плохого SMP-кода, но я сомневаюсь, что вы найдете его.

3 голосов
/ 25 апреля 2009

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

В этой статье IBM представлен отличный обзор ваших возможностей.

Анатомия методов синхронизации Linux Атомика ядра, спин-блокировки и мьютексы М. Тим Джонс (mtj@mtjones.com), инженер-консультант, Emulex

http://www.ibm.com/developerworks/linux/library/l-linux-synchronization.html

2 голосов
/ 27 апреля 2009

Чтение Упорядочение памяти в современных микропроцессорах, Часть I и Часть II

Они дают представление о том, почему это теоретически небезопасно.

Вот потенциальная раса:

  • Процесс A (на ядре процессора A) выполняет запись в новую область общей памяти
  • Процесс A помещает этот идентификатор совместно используемой памяти в общую 32-разрядную переменную (которая выровнена по 32-разрядной схеме - любой компилятор будет пытаться выполнить выравнивание таким образом, если вы позволите).
  • Процесс B (на ядре процессора B) читает переменную. Предполагая 32-битный размер и 32-битное выравнивание, на практике не должно быть мусора.
  • Процесс B пытается читать из области общей памяти. Теперь нет гарантии, что он увидит записанные данные, потому что вы пропустили барьер памяти. (На практике, вероятно, в процессоре B в коде библиотеки, отображающем сегмент общей памяти, были барьеры памяти; проблема в том, что процесс A не использовал барьер памяти).

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

С последним ядром и libc вы можете поместить мьютекс pthreads в область общей памяти. (Для этого нужна свежая версия с NPTL - я использую Debian 5.0 "lenny", и она отлично работает). Простая блокировка общей переменной означает, что вам не нужно беспокоиться о проблемах с загадочным барьером памяти.

2 голосов
/ 18 апреля 2009

Я действительно считаю, что это должно быть абсолютно безопасно (но это зависит от точной реализации). Предполагая, что «главный» сегмент - это в основном массив, если shmid может быть записан атомарно (если он 32-битный, то, вероятно, все в порядке), а второй процесс просто читает, у вас все должно быть в порядке. Блокировка необходима только тогда, когда пишутся оба процесса, или записываемые значения не могут быть записаны атомарно. Вы никогда не получите испорченный (наполовину записанные значения). Конечно, могут быть некоторые странные архитектуры, которые не могут справиться с этим, но на x86 / x64 все должно быть хорошо (и, вероятно, также ARM, PowerPC и другие распространенные архитектуры).

1 голос
/ 29 мая 2009

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

Здесь - статья об этой проблеме в некоторой глубине, они обсуждают некоторые альтернативные алгоритмы планирования, которые уменьшают общую потребность в эксклюзивном доступе через шину. Что увеличивает общую пропускную способность в некоторых случаях более чем на 60%, чем наивный планировщик (если учитывать стоимость явной инструкции префикса блокировки или неявного xchg cmpx ..). Эта статья - не самая последняя работа и не очень похожа на реальный код (черт академический), но она заслуживает прочтения и рассмотрения этой проблемы.

Более поздние процессорные ABI предоставляют альтернативные операции, чем простые блокировка независимо от .

Джеффр , из FreeBSD (автор многих внутренних компонентов ядра), обсуждает монитор и mwait, 2 инструкции добавлены для SSE3, где в простом тестовом примере было выявлено улучшение на 20%. Позже он постулирует;

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

...

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

Интересно, каков будет эффект использования паузы вместо hlt.

С TBB Intel;

        ALIGN 8
        PUBLIC __TBB_machine_pause
__TBB_machine_pause:
L1:
        dw 090f3H; pause
        add ecx,-1
        jne L1
        ret
end

Art of Assembly также использует синхронизацию без использования префикса блокировки или xchg. Я давно не читал эту книгу и не буду говорить непосредственно о ее применимости в контексте SMP в режиме защиты прав пользователя, но ее стоит посмотреть.

Удачи!

1 голос
/ 22 апреля 2009

Я согласен, что это может работать - так что это может быть безопасно, но не в здравом уме. Главный вопрос заключается в том, нужен ли этот низкоуровневый общий доступ - я не эксперт по Linux, но я бы подумал использовать, например, очередь FIFO для основного сегмента разделяемой памяти, чтобы операционная система работала за вас. , Потребителю / производителю обычно все равно нужны очереди для синхронизации.

1 голос
/ 18 апреля 2009

Правовая? Я полагаю. Зависит от вашей "юрисдикции". Сейф и вменяемый? Почти наверняка нет.

Редактировать: Я обновлю это с дополнительной информацией.

Возможно, вы захотите взглянуть на эту страницу Википедии; в частности, раздел «Координация доступа к ресурсам». В частности, обсуждение в Википедии, по сути, описывает провал доверия ; неблокированный доступ к общим ресурсам может, даже для атомарных ресурсов, привести к неверному сообщению / искажению достоверности того, что действие было выполнено. По существу, в промежуток времени между проверкой, чтобы увидеть, МОЖЕТ ли он изменить ресурс, ресурс изменяется внешне, и, следовательно, доверие, присущее условной проверке, нарушается.

1 голос
/ 18 апреля 2009

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

Теперь я не знаю Linux, но подозреваю, что shmid имеет размер от 16 до 64 бит. Это означает, что, по крайней мере, возможно, что на всех платформах будет какая-то инструкция, которая могла бы записать это значение атомарно. Но вы не можете полагаться на то, что компилятор сделает это без какого-либо запроса.

Подробности реализации памяти являются одними из самых специфичных для платформы вещей!

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

...