LINUX.ORG.RU

[System V] Семафоры и Ко


0

2

Мне понадобилось как-то на неделе написать кроссплатформенный семофор (именованный, то есть для межпроцессной синхронизации) со всеми вытекающими. После некоторого анализа того, что и где имеется, решил остановиться на интерфейсе System V (более распространенный, а вояки наши любят использовать что-нибудь подревнее) для unix-систем, для Windows-систем, конечно же, использовать родной API. В итоге все получилось и работает, но у меня возникло несколько вопросов и наблюдений.

То, что API System V неуклюж и несколько перегружен - не вызывает сомнений, хотя, через денек, начинаешь привыкать. Но проблемы вытекают напрямую из этой самой System V. Во-первых, надо создавать для каждого IPC-объекта (семафор, разделяемая память, с другим еще не возился) свой файл и по нему генерить заветный key_t. Почему нельзя было использовать обычную буквенную последовательность, как это, например, сделано в Windows? Да, я знаю про POSIX-интерфейс к IPC, но и там жизнь не легче. Стандартом де-факто в мире UNIX является System V, поэтому от этого и решено было отталкиваться. Поэтому приходиться следить за созданным файлом. Это раз.

У семафора есть такая проблема, что им владеет, фактически, система, а не процессы. То есть, если вдруг процессы сегфолтнутся и семафор будет в этот момент в залоченном состоянии, то при следующем запуске приложения и обращения к семафору я опять получу уже залоченный семафор. Например в Windows семафором «владеют» процессы, и как только все процессы завершатся или «отпустят» семафор, он удаляется. Все просто и надежно. Есть ли какой-то Ъ-вэй решить этот косяк в UNIX-системах? Только, пожалуйста, без костылей типа мастер-процесса следящего за другими, или запускать один процесс всегда первым и т.д. Есть ли что-нибудь готовое на системном уровне? Только не надо говорить про SEM_UNDO - связанные с этим косяки всем известны, равно как и системно-зависимая реализация.

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

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

★★★★
Ответ на: комментарий от MuZHiK-2

>Вообще, желательно. Потому что частенько практикуется вариант терминального доступа через NFS.

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

Что вообще нужно? Примитивы межпроцессорной синхронизации или межмашинной синхронизации? В последнем случае без сокетов будет обойтись очень трудно.

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

> Что вообще нужно? Примитивы межпроцессорной синхронизации или межмашинной синхронизации?

Гибкое решение :)

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

> Не понял, что непонятно.

чем именованный от неименованного отличается
в данном контексте? ->f_op один и тот же после
открытия.

что-то я пропустил наверное, но перечитывать
ветку лень, вы тут разошлись не на шутку ;)

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

>Гибкое решение :)

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

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

>> Не понял, что непонятно.

чем именованный от неименованного отличается в данном контексте?

В данном - ничем. В более широком - тем, что процессы потребителя и производителя должен будет запускать единый процесс-родитель, и ХЗ, как это укладывается в требования ТС.

->f_op один и тот же после открытия.

Думаю, это никого не интересует. А у тебя профессиональная аберрация, да.

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

>>Ы. Это вообще-то настолько простая вещь, что описания я даже не встречал :) Вкратце: создается файл, мапится двумя процессами, и дальше они обмениваются токеном (в простейшем случай - байтом с неважно каким значением). Тот процесс, у которого токен, имеет право на доступ к файлу, другой - ждет возвращения токена. Собственно, это всё; схема обобщается и на случай нескольких файлов.

Понятно, спасибо :)

Опыт показывает, что старперы предпочитают простые вещи типа обмена Си-структрами по сокету.

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

То есть «железка» - это компьютер в сети? Там будут сокеты. А если и не будет (что практически исключено), будешь ловить Ethernet-пакеты в PACKET-сокет :)

Да, это, в общем, компьютер, но несколько урезанный в ресурсах и, видимо, возможностях из-за специфики своей работы. Там будет стоять какая-то древняя версия операционки наподобие oc2000 (только там будет возможность запускать несколько процессов, чего нету в ос2000, или это какая-то модификация этой ос2000 - пока что у нас нету ни софта, ни железок самих для тестирования), там памяти вообще почти нету, поэтому обрезана по самое «не могу». Эта штука будет получать с датчиков сырые данные, обрабатывать и выставлять на какую-то шину, с которой будут подхватывать подключенные к этой шине девайсы, дальше обрабатывать и, скорее всего, выдавать куда-то дальше, за что мы не отвечаем. То есть сети как таковой там не предусмотрено, поэтому приходится обходиться подручными средствами. Я вообще удивляюсь, как там это все работает в принципе

MuZHiK-2 ★★★★
() автор топика
Ответ на: комментарий от pathfinder

> купите латексный фаллос

«Доктор, откуда у вас такие картинки?» (с)

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

>>Что вообще нужно? Примитивы межпроцессорной синхронизации или межмашинной синхронизации? В последнем случае без сокетов будет обойтись очень трудно.

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

MuZHiK-2 ★★★★
() автор топика
Ответ на: комментарий от tailgunner

> В более широком - тем,

а. я чего-то решил, что вы там производительность
сравнивали.

Думаю, это никого не интересует


ну и ладно.

А у тебя профессиональная аберрация, да.


не, просто кругозор узкий ;)

idle ★★★★★
()
Ответ на: комментарий от MuZHiK-2

>Эта штука будет получать с датчиков сырые данные, обрабатывать и выставлять на какую-то шину, с которой будут подхватывать подключенные к этой шине девайсы

Я правильно понял? Надо принять сырые данные с датчиков, обработать их и отправить дальше по «какой-то шине»?

pathfinder ★★★★
()
Ответ на: комментарий от MuZHiK-2

>Нужно максимально гибкое и универсальное решение, с минимумом затрат на перенос между платформами.

Чем-то всегда приходится жертвовать. Либо производительностью, либо гибкостью, либо переносимостью. Это при учете, что это ещё должно работать непонятно на чем, непонятно как. Я не верю в то, что тебе удастся решить эту задачу в обобщенном виде.

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

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

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

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

>>Я правильно понял? Надо принять сырые данные с датчиков, обработать их и отправить дальше по «какой-то шине»?

Да. Только часть обработки делаем мы, а часть - другое НИИ. То есть как минимум 2 процесса будут обмениваться данными и затем «выплевывать» дальше.

MuZHiK-2 ★★★★
() автор топика
Ответ на: комментарий от MuZHiK-2

>Только часть обработки делаем мы, а часть - другое НИИ

А насколько реально организовать это все в виде библиотеки? То есть на самом деле процесс будет один и не будет никакой межпроцессорной синхронизации и разделяемой памяти. Просто кто-то будет использовать библиотеку другого.

pathfinder ★★★★
()

Проблему с segfault с семафорах можно ражить прибавлением к имени семафора pid'а процесса, что его создает.

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

Ещё можно через semctl(..., GETPID) получать PID процесса, последнего изменявшего этот семафор.

Eshkin_kot ★★
()

> Семафоры и Ко

я вот только не понял, при чем здесь Капитан очевидность? Он же один, ему не нужны семафоры.

dilmah ★★★★★
()

Не знаю, нужны ли вам вобще семафоры. Если у вас задача просто передавать данные от одного процесса другому, может хватит и шареной памяти. К тому, что IPC объекты принадлежат системе нужно привыкнуть, и это может даже удобно, что есть память, которая переживает сегфолты. Для key_t необязательно генерить файл, то есть можно создать один кусок шареной памяти (через файл или с заранее заданым key_t) и там указывать key_t других объектов.

Ещё есть сигналы, в том числе alarm().

И не совсем понятно, какое поведение системы вам нужно, если один процесс «упал», то как должны вести себя остальные процессы?

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

>>А насколько реально организовать это все в виде библиотеки?

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

MuZHiK-2 ★★★★
() автор топика
Ответ на: комментарий от Pavval

>>Проблему с segfault с семафорах можно ражить прибавлением к имени семафора pid'а процесса, что его создает.

Немного не понял, как это спасет от дедлоков при сегфолтах, можешь пояснить идею?

MuZHiK-2 ★★★★
() автор топика
Ответ на: комментарий от mky

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

А как синхронизировать тогда доступ к шареной памяти?

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

При таком подходе нет гарантий, что такой ключ уже не занят.

И не совсем понятно, какое поведение системы вам нужно, если один процесс «упал», то как должны вести себя остальные процессы?

В идеале - «отморозиться», то есть при падении семафор должен разлочиться. Но сойдет и такой вариант: прибить или завершить все процессы, и запустить их заново.

MuZHiK-2 ★★★★
() автор топика

>MuZHiK-2

Статус: ** (корректор)
семофор

quadruple_facepalm.png

fool_anon
()
Ответ на: комментарий от MuZHiK-2

В идеале - «отморозиться», то есть при падении семафор должен разлочиться. Но сойдет и такой вариант: прибить или завершить все процессы, и запустить их заново.

И как хочешь сделать перезапуск без специального мастер процесса?

mikki
()
Ответ на: комментарий от MuZHiK-2

А как синхронизировать тогда доступ к шареной памяти?

flock на объект в разделяемой памяти? Или глобальный флаг в той же памяти (в качестве него можно вообще pid процесса использовать - тогда другой процесс, проверив наличие процесса с указанным pid'ом сможет узнать, не упал ли блокирующий процесс)?

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от MuZHiK-2

>>Проблему с segfault с семафорах можно ражить прибавлением к имени семафора pid'а процесса, что его создает.

Немного не понял, как это спасет от дедлоков при сегфолтах, можешь пояснить идею?


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

Или вообще выбирать имя семафора случайно и передавать второму процессу через какой-то файл.

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

>>И как хочешь сделать перезапуск без специального мастер процесса?

Это хоть руками или скриптом.

MuZHiK-2 ★★★★
() автор топика
Ответ на: комментарий от Eddy_Em

>>flock на объект в разделяемой памяти?

Чем плох flock - уже обсудили.

Или глобальный флаг в той же памяти (в качестве него можно вообще pid процесса использовать - тогда другой процесс, проверив наличие процесса с указанным pid'ом сможет узнать, не упал ли блокирующий процесс)?

А как синхронизировать доступ к глобальному флагу в памяти? :)

MuZHiK-2 ★★★★
() автор топика
Ответ на: комментарий от Eddy_Em

>>Кстати, Стивенса-то от корки до корки прочитали?

Собственно, прочитал там главы про SysV IPC. Посмотрел, как делали в Qt. Сделал нечто подобное с костылем как в Qt. Надеюсь, edge cases не проявятся.

MuZHiK-2 ★★★★
() автор топика
Ответ на: комментарий от Pavval

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

В том-то и проблема, что я не знаю заранее, какой процесс стартует первым. Тогда для открытия семафора второму процессу надо будет знать pid запустившегося первым.

MuZHiK-2 ★★★★
() автор топика
Ответ на: комментарий от MuZHiK-2

А как синхронизировать доступ к глобальному флагу в памяти? :)

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

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от MuZHiK-2

Так можно в разделяемой памяти создать список pid'ов всех запущенных процессов в хронологическом порядке. Очередной процесс просматривает список и удаляет из него все несуществующие процессы. Если оказывается, что он - единственный, он считает себя первичным и открывает свой семафор.

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

>>Вы еще спросите, как к мьютексам доступ синхронизировать. Считается, что операции работы с ними атомарны, так что все надеются, что никаких коллизий не произойдет :)

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

MuZHiK-2 ★★★★
() автор топика
Ответ на: комментарий от MuZHiK-2

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

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

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от MuZHiK-2

Ключ можно хранить в файле конфигурации и т.д., просто в случае железяки всё должно быть документированно, и, если возник объект и занял key_t, значит что-то не так, ИМХО.

Относительно разлочивания семафора неочевидно. То есть один процесс залочил, писал в память данные и упал. Данные получились неполные/неправильные. Разлочивание семафора может привести к обработке этих данных.

Относительно шареной памяти, можно сделать что=то наподобие структуры, для каждого процесса своё атомарное поле. Каждый процесс правит (инкрементирует на 1) своё поле. Если все поля одинаковые, значит данные обработаны.

mky ★★★★★
()
Ответ на: комментарий от MuZHiK-2

>В том-то и проблема, что я не знаю заранее, какой процесс стартует первым. Тогда для открытия семафора второму процессу надо будет знать pid запустившегося первым.

Процесс, создавший семафор, пишет его тупо в определенный текстовый файл (перезаписывая его). Второй его читает.

Или семафор может создать любой из двух процессов? Тогда в тот же файл надо писать и pid процесса, создавшего семафор. И тогда надо будет проверять, живой ли процесс по этому пиду.

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

> С увеличением количества ядер/процессоров ситуация с коллизиями действительно все больше и больше ухудшается.

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

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

А что делать-то? Неужели есть средства, гарантированно избавляющие от коллизий, например, на 1024-процессорном компьютере с несколькими сотнями одновременно запущенных идентичных процессов, пытающихся читать/писать один и тот же файл?

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

> А что делать-то?

Если не можешь предложить решение, нужно молчать.

Неужели есть средства, гарантированно избавляющие от коллизий, например, на 1024-процессорном компьютере с несколькими сотнями одновременно запущенных идентичных процессов, пытающихся читать/писать один и тот же файл?

Есть. Это средство называется «мозги программиста».

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

> Что, писать на «ядерном» уровне реализацию нового типа мьютексов или семафоров?

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

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

Не, для моих задач подходят мьютексы, flock и объекты в разделяемой памяти. А вот для «сферической задачи в вакууме», про которую я уже говорил, они уже не подойдут, т.к. есть (хоть и исчезающе малая) вероятность, что два процесса или потока попытаются одновременно выполнить блокировку.

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от MuZHiK-2

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

На счёт синхронизации передачи данных через общую память. Можно использовать схему двух очередей так, чтобы в каждую очередь писал только один процесс. Соответственно, с точки зрения каждого процесса, одна из очередей используется для входных данных (ro), другая для выходных (rw).

Синхронизация доступа через специальные токены: под каждый элемент очереди отведён один атомарный токен (в зависимости от архитектуры процессора, 2, 4 или 8 байт). Допустим, токен 0x0 означает свободный элемент, начальное значение для всех токинов и выставлять его может только читающий процесс. -1 занятый элемент, выставлять может только пишущий процесс.

В общих моментах, получите вариацию loсking-free синхронизации.

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

>Не, для моих задач подходят мьютексы

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

Но получит ее только один процесс. В конце концов, если система многопроцессорная, задача сводится к использованию соответствующих команд процессора, которые _гарантируют_ получение блокировки только одним процессом. В полностью синхронной цифровой системе вероятность получения блокировки одновременно двумя процессами можно сделать равной нулю. Реальные системы конечно же не являются полностью синхронными и для передачи информации из одного синхронного узла в другой применяются синхронизаторы (например цепочки последовательно включенных триггеров). При этом действительно существует очень малая ненулевая вероятность попадания последнего триггера в цепочке в метастабильное состояние, что ведет к непредсказуемому поведению системы. Однако эта вероятность может быть сделана сколь угодно малой и действительно делается такой, чтобы во всех произведенных устройствах за весь их жизненный цикл отказ происходил, например, в среднем 1 раз. Так что на твоей гипотетической 1024 процессорной системе существующие мьютексы будут безошибочно работать. Если это не так, то у тебя даже ядро не стартанет на этой говномашине, поскольку ядро внутри себя активно использует блокировки и полагает, что они всегда работают правильно.

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

>>Ключ можно хранить в файле конфигурации и т.д., просто в случае железяки всё должно быть документированно, и, если возник объект и занял key_t, значит что-то не так, ИМХО.

Да, в данном случае это верно.

Относительно разлочивания семафора неочевидно. То есть один процесс залочил, писал в память данные и упал. Данные получились неполные/неправильные. Разлочивание семафора может привести к обработке этих данных.

На самом деле, там если хоть один процесс свалится, но встанет вся система обработки и передачи данных :) Просто хотелось бы перезапускать только упавший процесс, а не всю цепочку.

Относительно шареной памяти, можно сделать что=то наподобие структуры, для каждого процесса своё атомарное поле. Каждый процесс правит (инкрементирует на 1) своё поле. Если все поля одинаковые, значит данные обработаны.

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

MuZHiK-2 ★★★★
() автор топика
Ответ на: комментарий от Pavval

>>Или семафор может создать любой из двух процессов? Тогда в тот же файл надо писать и pid процесса, создавшего семафор. И тогда надо будет проверять, живой ли процесс по этому пиду.

Именно этот вариант развития событий и может быть.

MuZHiK-2 ★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.