LINUX.ORG.RU

Есть огнепоклонники OpenBSD?

 , ,


3

4

Что-то у меня мозоли от попыток задействовать IPC-семафоры:

  • shared posix mutexes - нету (из всех «бздей» есть только у FreeBSD и то с выкрутасами);
  • POSIX.1 семафоры отключены еще в v5.5 (Revert to return EPERM for sem_init(3) pshared until it works properly). Причем в NetBSD и DragonFly эти семафоры больные (вплоть до сore dump).
  • SysV семафоры похоже глючат.

На самом деле сложно понять что именно глючит (может у меня руки кривые) без допиливания теста. Но тот-же код работает на Linux, FreeBSD, NetBSD, DragonFly, Darwin и Solaris (OpenIndiana).

На этом фоне были замечены глюки ядра OpenBSD. Например, после заполнения /tmp (tmpfs), после последующей очистки и до перезагрузки semget() стабильно выдавала «no space left on device», а ipcs выдавала встроенный help вместо обработки опций.

На OpenBSD еще есть futex-ы (aka benaphores) портированные из Linux. Соответственно, можно попробовать их в IPC-блокировках. Но я уже сомневаюсь - стоит ли тратить время на это «унылое говно»? (уж извините).


Вскрытие стюардессы показало, что причина уже не в SysV-семафорах (они работают), а в Buffer/Page Cache.

Технически происходит следующее:

  1. файл mmap-ится в память несколькими процессами в режиме read-only.
  2. один из процессов обновляет фрагмент файла, например через pwrite() или writev() и затем инвалидирует mmap и/или кэш CPU.
  3. все процессы видят изменения в своих mmap-регионах.

Упомянутая инвалидация mmap не требуется во всех актуальных операционках. Только MIPS требует инвалидировать кэш CPU. Но на всякий случай попробовал и с msync().

Так вот, проблема в том, что на OpenBSD третий пункт выполняется не всегда. Даже если явно пнуть ядро посредством msync(MS_INVALIDATE).

При этом, если mmap-ить файл в read-write режиме и менять данные не через запись файл, а непосредственно в памяти то всё работает.

Короче, диагноз ясен. Напишу в bugs@openbsd.org, может-быть поправят.


Добавлено 2019-11-14: На сабмит в bugs@openbsd.org был получен ответ «это не баг, а фича». Лично для меня открытие, что оказывается в OpenBSD «by design» либо mmap, либо write(fd).

https://marc.info/?l=openbsd-bugs&m=157373953304874

https://github.com/leo-yuriev/libmdbx/issues/67

Deleted

Ответ на: комментарий от SZT

Атомарными операциями через общую память можно решить

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

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

Deleted ()

я так понимаю у тебя процессы а не потоки, и потому обычные мьютексы/семафоры ты задействовать не можешь, верно?

если коммуникация не требует суперкритичной точности и задержки пробуждения процесса, то POSIX сигналы вполне сойдут за способ коммуникации

anonymous ()
Ответ на: комментарий от anonymous

я так понимаю у тебя процессы а не потоки, и потому обычные мьютексы/семафоры ты задействовать не можешь, верно?

если коммуникация не требует суперкритичной точности и задержки пробуждения процесса, то POSIX сигналы вполне сойдут за способ коммуникации

Мисье похоже знает толк в «стюардессах». Но я предпочту закопать или поправить ядро, вместо прилепливания очереди ожидания с пробуждением по сигналу (из говна и палок).

Deleted ()
Ответ на: комментарий от DELIRIUM

У меня другая задача = довести проект до кондиции, чтобы приморозить и переключиться на следующую версию (mithrildb).

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

Проверка на OpenBSD (и наборе прочих платформ) позволяет довести до логического завершения и проверить почти все платформенно-зависимые места. Т.е. так намного меньше вероятность пропустить какой-нибудь зависящий от платформы баг, или оставить в коде слабые места, которые потом могут потребовать более-менее серьезной доработки с погружением в контекст.

Deleted ()

Технически происходит следующее:

  1. файл mmap-ится в память несколькими процессами в режиме read-only.
  2. один из процессов обновляет фрагмент файла, например через pwrite() или writev() и затем инвалидирует mmap и/или кэш CPU.
  3. все процессы видят изменения в своих mmap-регионах.

Так вот, проблема в том, что на OpenBSD третий пункт выполняется не всегда. Даже если явно пнуть ядро посредством msync(MS_INVALIDATE).

Т.е. куча процессов должны читать-писать в какой-то общий файл, притом надо уметь делать это одновременно и через файловые дескрипторы, и через память(отображенный в память файл)? А зачем так делать вообще?

SZT ★★★★ ()
Последнее исправление: SZT (всего исправлений: 3)

Как вариант, можно попробовать только одним процессом mmap-нуть файл в память, а остальным процессам дать к нему доступ как к памяти через SysV SHM, хотя не факт что это будет нормально работать.

SZT ★★★★ ()

файл mmap-ится в память несколькими процессами в режиме read-only.
один из процессов обновляет фрагмент файла, например через pwrite() или writev() и затем инвалидирует mmap и/или кэш CPU.
все процессы видят изменения в своих mmap-регионах.
Упомянутая инвалидация mmap не требуется во всех актуальных операционках. Только MIPS требует инвалидировать кэш CPU. Но на всякий случай попробовал и с msync().

Я первый раз такое вижу. В каком руководстве или каком стандарте ты такое прочитал? Насколько мне известно, ни одна ось не гарантирует синхронность mmap-а и файла до момента закрытия mmap-а. Потому что запись на накопитель - это дополнительная операция, а они по отношению к файлам могут быть дорогими. Если держать в памяти рассинхронизированное значение, то возникает проблема: как быть с программами, которые сделали read/write на файл, и ожидают, что прочитают целостную инфу с диска, а не черт пойми что из буферов?

Да, я понимаю, что ты используешь /tmp и только для своих программ, но ты слишком много хочешь от ОС-и. Может быть ОС пошла у тебя на поводу, но только потому, что вас таких мног, и проще было подставить костыль под ОС, чем написать ядро правильно.

byko3y ()
Ответ на: комментарий от byko3y

Я первый раз такое вижу. В каком руководстве или каком стандарте ты такое прочитал? Насколько мне известно, ни одна ось не гарантирует синхронность mmap-а и файла до момента закрытия mmap-а. Потому что запись на накопитель - это дополнительная операция, а они по отношению к файлам могут быть дорогими. Если держать в памяти рассинхронизированное значение, то возникает проблема: как быть с программами, которые сделали read/write на файл, и ожидают, что прочитают целостную инфу с диска, а не черт пойми что из буферов?

О синхронизации при изменении через mmap и чтении через файловые операции см msync(), т.е. это стандарт. Синхронизация в обратном направлении (при изменении через файл и чтении mmap) не стандартизована, но де-факто (наверное) во всех операционках (c виртуальной памятью и mmap для файлов) кроме OpenBSD.

Повелось так достаточно давно, примерно c VMS. Причина достаточно проста - с некоторого уровня развития ядра гораздо проще и эффективнее сделать «unified page cache», а не полторы/две подсистемы. Проще говоря, сделать иначе - тяжелее, медленнее, дороже, хуже и т.п.

Собственно я так к этому привык, что никак не ожидал такого «закидона» в дизайне OpenBSD - это как «назло бабушке уши отморозить». Поэтому увидев проблему и с учетом ранее выявленных проблем с семафорами POSIX.1, решил что это баг, ибо фичей быть не может…

Да, я понимаю, что ты используешь /tmp и только для своих программ, но ты слишком много хочешь от ОС-и.

Нет, это промышленно эксплуатируемый движок БД.

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

Такой «хак» используется примерно повсеместно. Аналогично мало кто рассчитывает на не-8-битный байт или представление отрицательных не в дополнительном коде.

Deleted ()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от SZT

Т.е. куча процессов должны читать-писать в какой-то общий файл, притом надо уметь делать это одновременно и через файловые дескрипторы, и через память(отображенный в память файл)? А зачем так делать вообще?

Потому-что «гладиолус» ;)

Это встраиваемая БД, которая отображает все данные в память (поэтому её достаточно сложно обогнать).

Если отобразить данные в режиме read-write, то процесс-клиент может легко повредить данные из-за собственного бага. Поэтому кроме «быстрого, но не безопасного» режима работы предлагает безопасный = данные ЬД отображаются read-only, а обновляются через файловые операции.

Всё это работает если в ядре ОС реализован unified page case, что просто выгоднее, удобнее, легче и быстрее. Внезапно OpenBSD примерно единственная ОС где нашлись причины идти другим путем.

В моём случае проблема лечиться очень просто - пришлось заблокировать безопасный режим, оставив для OpenBSD только read-write mmaping.

Deleted ()
Ответ на: комментарий от Deleted

О синхронизации при изменении через mmap и чтении через файловые операции см msync(), т.е. это стандарт. Синхронизация в обратном направлении (при изменении через файл и чтении mmap) не стандартизована, но де-факто (наверное) во всех операционках (c виртуальной памятью и mmap для файлов) кроме OpenBSD.

Смотрел-смотрел, смотрел смотрел доки - нигде вообще не описывается поведение mmap с MAP_SHARED после изменения исходного файла. Можно считать это undefined behaviour, но по факту мы видим, что многие разрабы сделали поведение предсказуемым. То есть, теперь это называется «недокументированная функция». И это причина, почему целая гора софта для винды не работает под Wine - они так же пользуются кучей недокументированных функций. Это будет работать, но это плохо.
И да, msync здесь не в тему, потому что выполняет обратную функцию.

Причина достаточно проста - с некоторого уровня развития ядра гораздо проще и эффективнее сделать «unified page cache», а не полторы/две подсистемы. Проще говоря, сделать иначе - тяжелее, медленнее, дороже, хуже и т.п.

Первый unified cache был в солярке, и сделан он был именно по причине mmap. Почему-то до этого двадвать лет разрабы не видели, что, оказывается, без него было «тяжелее, медленнее, дороже, хуже». Наверное, потому что этого не было. Было просто неудобно пользоваться mmap. Только в 4.4BSD появился unified cache, и не попал в OpenBSD.

Такой «хак» используется примерно повсеместно. Аналогично мало кто рассчитывает на не-8-битный байт или представление отрицательных не в дополнительном коде.

А как тебе такой закидон:
https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createf...

A mapped file and a file that is accessed by using the input and output (I/O) functions (ReadFile and WriteFile) are not necessarily coherent.

Всеми говоришь? Уже предвкушаю, как такой софт отваливается на WSL.

byko3y ()
Ответ на: комментарий от byko3y

Для завершения дискуссии я перефразирую:

  • системы с VM и без unified page cache существуют, но их примерно одна штука (OpenBSD).
  • это не создает проблем, просто если ОС чего-то не умеет, то это недоступно пользователя (который сам выбирает ОС).
  • у меня всё хорошо (libmdbx «выжимает» из всех поддерживаемых платформ всё что они умеют и при этом работает корректно).

А как тебе такой закидон: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createf

В NT изначально общий кэш страниц (от архитектора VMS). Если в новых версиях масдая вдруг что-то отберут, то пользователи что-то потеряют как в случае с OpenBSD. Но мне до этого примерно нет дела, ибо я не могу влиять на ситуацию.

Всеми говоришь? Уже предвкушаю, как такой софт отваливается на WSL.

На первой WSL отваливается масса всего, ибо там просто пародия на Linux. На второй WSL соответственно не отваливается почти ничего, ибо от масдая там только HyperV.

Deleted ()
Ответ на: комментарий от Deleted

Если отобразить данные в режиме read-write, то процесс-клиент может легко повредить данные из-за собственного бага. Поэтому кроме «быстрого, но не безопасного» режима работы предлагает безопасный = данные ЬД отображаются read-only, а обновляются через файловые операции.

Ну вообще-то можно отобразить файл два раза - один раз read-only, второй раз read-write и написать соотв. функций, которые будут эмулировать файловые операции pwrite() и writev(), работая при этом с read-write частью.

SZT ★★★★ ()
Ответ на: комментарий от SZT

Ну вообще-то можно отобразить файл два раза - один раз read-only, второй раз read-write и написать соотв. функций, которые будут эмулировать файловые операции pwrite() и writev(), работая при этом с read-write частью.

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

На всякий - для взаимодействия с БД пользователю не нужны файловые операции, ему предоставляется совсем другое API.

Deleted ()
Ответ на: комментарий от Deleted

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

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

На всякий - для взаимодействия с БД пользователю не нужны файловые операции, ему предоставляется совсем другое API.

Функции для воркераундов каких-то багов на Эльбрусе, интересно... А у них открытый багтрекер есть вообще, чтоб об этом узнавать не из чьих-то исходников?

SZT ★★★★ ()
Ответ на: комментарий от SZT

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

Если делать отдельный процесс, то это уже не встраиваемая БД (без собственных тредов и процессов). Т.е. имея выделенный процесс можно и нужно делать больше - и в результате получится а-ля Tarantool.

В остальных случаях, без отдельного процесса, проще посредством mprotect() включать/выключать запись. Но такой способ в среднем медленнее чем файловые операции = многократный сброс кеша PTE, плюс msync() для записи на диск (со сканированием PTE всего региона). А делать это только ради OpenBSD я пока не хочу.

Функции для воркераундов каких-то багов на Эльбрусе, интересно…

Это очень специфичные баги, которые были поправлены год-полтора назад. В исходниках это пока осталось (до конца кода выкину, после перепроверки).

А у них открытый багтрекер есть вообще, чтоб об этом узнавать не из чьих-то исходников?

Совсем открытого снаружи вроде-бы нет (не следил в этом году).

Deleted ()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от Deleted

В остальных случаях, без отдельного процесса, проще посредством mprotect() включать/выключать запись. Но такой способ в среднем медленнее чем файловые операции = многократный сброс кеша PTE, плюс msync() для записи на диск (со сканированием PTE всего региона). А делать это только ради OpenBSD я пока не хочу.

write в лине дает полновесный цикл переключений контекста, пускай и один.

mmap позволяет отображать нужный тебе кусок файла - я не измерял производительность, но, по идее, это пара циклов переключений контекста - на mmap и на munmap. Чтение из mmap без DMA точно так же будет давать тебе переключения контекста, если чо, так что я бы так не беспокоился про них до тех пор, пока не начинаешь волноваться про отдельную тысячу циклов процессора.

msync нужен на OpenBSD не больше, чем он нужен на любой другой системе.

byko3y ()
Ответ на: комментарий от byko3y

write в лине дает полновесный цикл переключений контекста, пускай и один.

mmap позволяет отображать нужный тебе кусок файла - я не измерял производительность, но, по идее, это пара циклов переключений контекста - на mmap и на munmap.

Это очевидные вещи, т.е. это всё полноценные системные вызовы без vDSO. Но что вы хотели этим сказать?

При желании наградить меня ценными советами относительно libmdbx лучше сначала погуглить и немного вникнуть в тему.

Чтение из mmap без DMA точно так же будет давать тебе переключения контекста, если чо, так что я бы так не беспокоился про них до тех пор, пока не начинаешь волноваться про отдельную тысячу циклов процессора.

Причем тут DMA, если данные нужны в user-mode из файловой системы, которая может cifs/nfs не говоря про ftp/ssh под fuse?

При чтении могут быть page faults (конечно с переключением контекста) если данные не в памяти, а на диске. Всё.

msync нужен на OpenBSD не больше, чем он нужен на любой другой системе.

Без msync() измененные данные не обязаны попасть на диск. С MS_INVALIDATE всё сложнее, но в большинстве случаев он бесполезен (либо толком ничего не делает как в linux, либо не работает как в OpenBSD).

Deleted ()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от Deleted

Причем тут DMA, если данные нужны в user-mode из файловой системы, которая может cifs/nfs не говоря про ftp/ssh под fuse?

Разговор велся о том, что mprotect каким-то образом слишком тяжелое. Я потому и удивился, мол, как так, данные читаются с диска, но кто-то там считает каждое переключение контекста. pwrite заменяется на mmap или mprotect весьма просто, так что я не вижу проблем.

Без msync() измененные данные не обязаны попасть на диск. С MS_INVALIDATE всё сложнее, но в большинстве случаев он бесполезен (либо толком ничего не делает как в linux, либо не работает как в OpenBSD).

MS_INVALIDATE по стандарту ничего не делает для MAP_SHARED. Выбор есть только между MS_ASYNC и MS_SYNC. pwrite тоже ничего не запишет на диск сразу, нужно вызывать fsync (разве что используется O_SYNC | O_DIRECT). То есть, процедура записи через mmap или mprotect имеет минимальные отличия.

byko3y ()
Ответ на: комментарий от byko3y

Разговор велся о том, что mprotect каким-то образом слишком тяжелое. Я потому и удивился, мол, как так, данные читаются с диска, но кто-то там считает каждое переключение контекста. pwrite заменяется на mmap или mprotect весьма просто, так что я не вижу проблем.

Нет, не так.

Не mprotect() в отдельности тяжелее, а способ обновления БД с его использованием интегрально медленнее чем посредством write().

MS_INVALIDATE по стандарту ничего не делает для MAP_SHARED. Выбор есть только между MS_ASYNC и MS_SYNC. pwrite тоже ничего не запишет на диск сразу, нужно вызывать fsync (разве что используется O_SYNC | O_DIRECT). То есть, процедура записи через mmap или mprotect имеет минимальные отличия.

Нет, не так.

MS_INVALIDATE никак не связан с MAP_SHARED, но теоретически должен решать проблему OpenBSD (неконсистентности записи и последующего чтения). Проблема же в том, что в POSIX говорится только об инвалидации, а в реализациях это расползается в уйму вариантов.


«Процедура записи через mprotect» в БД потребует +PROT_WRITE, затем -PROT_WRITE, и msync() для записи на диск. Причем mprotect придется вызывать многократно (т.е. примерно по два раза для каждой обновляемой страницы), ибо обновляемые страницы крайне редко будут идти последовательно. Вызывать же mprotect(PROT_WRITE) для всего региона бессмысленно, проще сразу назначить PROT_WRITE.

Поэтому способ «mprotect» примерно удваивает кол-во требуемых системных вызовов и порождает огромное кол-во отдельных изменений в таблицах PTE.

Deleted ()
Ответ на: комментарий от Deleted

«Процедура записи через mprotect» в БД потребует +PROT_WRITE, затем -PROT_WRITE, и msync() для записи на диск. Причем mprotect придется вызывать многократно (т.е. примерно по два раза для каждой обновляемой страницы), ибо обновляемые страницы крайне редко будут идти последовательно. Вызывать же mprotect(PROT_WRITE) для всего региона бессмысленно, проще сразу назначить PROT_WRITE.
Поэтому способ «mprotect» примерно удваивает кол-во требуемых системных вызовов и порождает огромное кол-во отдельных изменений в таблицах PTE.

Да, может быть, это и хуже, чем writev, но это работает на опенбсде. Ты хочешь сделать в этом случае MS_INVALIDATE? А ты уверен, что это не приведет к большому числу повторных чтений страниц с носителя?

byko3y ()

Рыба Фугу не для слабаков. Ошибёшся на миллиметр – захаваешь токсинов. Паралич дыхательной мускулатуры, смерть от асфиксии в полном сознании. Лучше не лезь в это. Кладбища полны легкомысленными пареньками, вроде тебя.

anonymous ()
Ответ на: комментарий от byko3y

Да, может быть, это и хуже, чем writev, но это работает на опенбсде.

Есть еще много способов сделать что-то через Ж, но это не повод именно так и делать потому-чот «это работает на опенбсде».

На всякий уточню - если кому-то будет нужно, то такое можно реализовать (для OpenBSD). Пока же я не вижу целесообразности и не горю желанием.

Ты хочешь сделать в этом случае MS_INVALIDATE? А ты уверен, что это не приведет к большому числу повторных чтений страниц с носителя?

Я не хочу, а уже пару-тройку дней как всё сделал.

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

Deleted ()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от anonymous

Рыба Фугу не для слабаков. Ошибёшся на миллиметр – захаваешь токсинов. Паралич дыхательной мускулатуры, смерть от асфиксии в полном сознании. Лучше не лезь в это. Кладбища полны легкомысленными пареньками, вроде тебя.

Батхерт?

Тем не менее, скажу спасибо если укажите на баг в libmdbx.

Deleted ()
Ответ на: комментарий от Deleted

Я не хочу, а уже пару дней как всё сделал.
Если есть вопросы по работе libmdbx (в том числе на OpenBSD), то спрашивайте что-то конкретное и желательно предварительно заглянув в доку или исходники.

А, тьфу, так есть режим записи в mmap. На кой черт тогда было делать writev?

byko3y ()
Ответ на: комментарий от debug_enabled

А про выкрутасы FreeBSD можешь подробнее описать? Я тогда погляжу, что можно сделать.

Там есть некоторые проблемы с разделяемыми posix-мьютексами в разделяемой памяти. Но для libmdbx проблема сейчас не актуальна - я задействовал семафоры SysV, с ними проблем нет.


Технически posix специфицирует «Attempting to initialize an already initialized mutex results in undefined behavior». На деле же во FreeBSD меньше проблем возникает если это правило нарушить.

В целом сценарий такой:

  • некий «первый» процесс открывает/создает служебный файл, накладывает эксклюзивную advisore lock посредством fcntl(), отображает этот файл в память, инициализирует внутри пару разделяемых posix-мьютексов и понижает блокировку файла до разделяемой.
  • несколько «не-первых» процессов открывают и отображают тот-же служебный файл, но уже не могут получить эксклюзивную блокировку файл и поэтому теоретически не должны повторно инициализировать мьютексы.
  • при закрытии любой процесс пытается захватить эксклюзивную блокировку файла и при успехе разрушает мьютексы.
  • это всё работает в режиме конкурирующего роя процессов (в том числе тест организует «оркестр» таких процессов).

Разделяемые posix-мьютексы реализованы в Linux, Darwin и FreeBSD. В Linux и Darwin всё работает без нареканий, а во FreeBSD случается EINVAL при захвате разделяемых мьютектов. Однако, вероятность EINVAL существенно ниже, если мьютексты инициализировать повторно в «не-первых» процессах.

Вероятность EINVAL достаточно низкая, где-то между 1e-5 и 1e-7. Т.е. воспроизведение проблемы требует от минуты до нескольких часов. Поэтому вместо рытья вглубь libthr я пошел «в ширину» - добавил использование семафоров SysV и анонимных семафоров POSIX-1 ради поддержки солярки и других «бсдей».

Deleted ()
Ответ на: комментарий от debug_enabled

Спасибо за объяснение. А я могу повторить эти тесты с помощью…

Да.

Только лучше брать ветку devel, собрать с -DMDBX_LOCKING=2001 (или 2008 для robust mutexes) чтобы использовались мьютексы, и включить/выключить повторную инициализацию. См #1 и #2.

Должно быть достаточно зациклить make test или mdbx_test basic. А чтобы не пилить диск можно использовать скрипт.

Deleted ()