LINUX.ORG.RU

select/poll and one write buffer

 


0

1

Драйвер символьного устройства. Один буфер записи для всех пользователей. Сценарий:
UserA: open()
UserB: open()
UserA: select() //device is ready for write
UserB: write() //UserB uses write buffer
UserA: write() //write buffer is busy, waiting..

select/poll возращает готовность устройства к записи, но другой пользователь между вызовом select и write «забрал» буфер себе.
Есть варианты решения кроме создания буфера записи на каждый open?

Спасибо.



Последнее исправление: kasha (всего исправлений: 1)

семафор/спинлок. Синхронизация нужна по-любому, так как в описанном тобой случае, реализованный тобой select() врёт, так как, если select вернул дескриптор, это значит, процессу сказали: «Не ссы, write() не заблокируется», а он взял и заблокировался.

DELIRIUM ☆☆☆☆☆
()
Последнее исправление: DELIRIUM (всего исправлений: 1)

Решение для эстетов - генерация EAGAIN ежели нет буфера. Соответственно, если на write отлуп по EAGAIN, то снова селект и т.п.

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

Семафор на общий буфер записи уже есть. Какая именно синхронизация имеется ввиду? Брать семафор в селекте, а отпускать на врайт? это плохо

kasha
() автор топика
Ответ на: комментарий от io

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

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

POSIX навеял? Хотите быть абсолютно православными? Стандарт только для потоков и обычных файлов дает определенные гарантии непрерывности и м.б. атомарности операции. Формально тот же POSIX требует чтобы при использовании ограниченных буферов была бы сделана попытка добить буфер до упора и вернуть сколько не влезло. Атомарность требуется? Формально можно было бы вернуть что записано 0 - но опять это желание неблокируемости. Так может желание неблокируемости решать именно введением неблокируемости. Тогда можно иметь два дескритптора: блокирующийся для select, неблокирующийся для write.

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

Неблокируемость уже реализована.неблокируемый дескриптор возвращает EAGAIN если буфер записи занят. проблема с блокируемым дескриптором.
Select для блокируемого и неблокируемого сокета ведет себя одинаково: возвращает POLLOUT если буффер записи свободен.

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

а зачем мне этот flock? я тогда просто на open могу возрващать EBUSY, но хочется чтобы могло быть несколько писателей.

Это где так?

Сейчас если устройство открываешь в блокирующем режиме, write ждет пока буфер записи станет свободным

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

Так у тебя свой модуль ведра что ли?

на open могу возрващать EBUSY

Ты что, открываешь файл для каждой записи, а потом закрываешь? Зачем?

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

Eddy_Em ☆☆☆☆☆
()

По хорошому наверное никак не сделаеш. Есть пару мыслей

1. Если клиенский софт пишеш ты - то проблем быть не должно (возвращай еррор коды, переводи в спец режим дескриптор через ioctl вобщем придумать можно все что угодно).

2. Если юзерспейс сторонний, то можно исхитрится и понадеятся что он обробатывает врайт нормально. А именно - можно зарезервировть по 1 байту в буфере на каждый активный дескриптор (или на каждый дескриптор по которому делался селект). Далее если UserA делает write после селекта то копировать этот 1 байт и отвечать ему что 1 байт был записан (без блокировок). По идее если софт дружит со стандартами то он дальше должен опять уйти в селект и продолжить операцию записи на втором круге ...

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

Это для неблокирующей записи, а что делать для блокирующей?

Блокирующая тоже может вернуть EAGAIN.

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

И где тогда проблема? Если все и так реализовано как у меня почти было предложено? Или userspace менять нельзя?

io ★★
()

Немного разверну мысль, если клиенский софт использует селект то все будет выглядет так

1. UserA - select() - return with device is ready for write
2. UserB - write
3. UserA - write
  3.1 Driver check if buffer is full - true
  3.2 Driver check if  reserved byte is used by UserA - false
  3.2 Copy 1 byte to buffer and return 1 from write.
4. UserA - call select again

Если же пользователь не использует селект а работает просто с блокирующим API то

1. UserA - write M bytes
  3.1 Driver check if buffer is full - false
  3.2 Driver copy N bytes and return N from write
2. UserA - write M-N bytes
  3.1 Driver check if buffer is full - true
  3.2 Driver check if reserved byte is used by UserA - false
  3.2 Driver copy 1 byte and return 1 from write
3. UserA - write (M-(N+1)) bytes
  3.1 Driver check if buffer is full - true
  3.2 Driver check if reserved byte is used by UserA - true
  3.2 Driver suspend UserA for IO wait

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

Вернуть 0 можно но тогда много софта начнуть люто жрать CPU поскольку много где есть код вроде такого:

while(m>0)
{
  r = write(m);
  m -= r;
}
И если write(m); будет возвращать 0 то получим 100% CPU лоадинг в этом месте пока бефер не рассасется ...

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

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

kasha
() автор топика
Ответ на: комментарий от zaz

По хорошому наверное никак не сделаеш. Есть пару мыслей

1. Если клиенский софт пишеш ты - то проблем быть не должно (возвращай еррор коды, переводи в спец режим дескриптор через ioctl вобщем придумать можно все что угодно).

2. Если юзерспейс сторонний, то можно исхитрится и понадеятся что он обробатывает врайт нормально. А именно - можно зарезервировть по 1 байту в буфере на каждый активный дескриптор (или на каждый дескриптор по которому делался селект). Далее если UserA делает write после селекта то копировать этот 1 байт и отвечать ему что 1 байт был записан (без блокировок). По идее если софт дружит со стандартами то он дальше должен опять уйти в селект и продолжить операцию записи на втором круге ...

Спасибо за предложенный вариант 2. Но у меня драйвер hdlc, я шлю датаграммы: на каждый вызов write я формирую фрейм, соответственно принимающая сторона вычитывает данные фрейм за фреймом. В каждый момент времени буфер записи либо пустой, либо там находятся данные одного фрейма которые сейчас отправляются(байт за байтом).Кроме того он достаточно сложен в реализации, проще создать отдельный буфер на каждый open, с ограничениями на количество пользователей

Можно поподробнее вариант 1? не совсем понимаю

kasha
() автор топика
Ответ на: комментарий от deep-purple

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

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

Можно поподробнее вариант 1? не совсем понимаю

Если userspace приложение пишете вы (или ваши колеги), то в документации строго написать - совмесно с poll/select использовать только не блокирующее IO и быть готовым к EAGAIN при write после успешного select. Этого должно быть достаточно для сохранения номрмального поведения при блокирующем API и переноса проблемы конкуренции для select в юзерспейс.

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

А ты уже реализовал этот чуз на блок-неблок?

В каждый момент времени буфер записи либо пустой, либо..

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

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

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

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

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

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

deep-purple ★★★★★
()
Ответ на: комментарий от kasha

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

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

В данном случае о легаси-коде речи не идет. Речь только о максимальной POSIX-совместимости при использовании в качестве механизма посылки сообщений (реально) обычного символьного устройства. Посему сей while к задаче отношения не имеет. Ожидается, что если write вернет 0, то будет сделана еще одна поытка select-а.

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

Насколько я правильно понял TS, проблема не совсем в селекте - а именно в сочитании чтоб работали оба варианта (две программы): Одна использует select, вторая построена на простом блокирующем API. Хотя возможно я не правильно понял суть проблемы ...

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

В данном случае о легаси-коде речи не идет. Речь только о максимальной POSIX-совместимости при использовании в качестве механизма посылки сообщений (реально) обычного символьного устройства. Посему сей while к задаче отношения не имеет. Ожидается, что если write вернет 0, то будет сделана еще одна поытка select-а.

А теперь представим что блокирующий write используется без select.
Вместо того чтобы подождать пока освободится буфер и записать данные, клиенту вернули 0. Клиенту вместо простого write нужно каждый раз встать в цикл пока данные не будут записаны?И 0 насколько я понимаю это результат закрытия соединения.

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

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

deep-purple ★★★★★
()
Ответ на: комментарий от io

Речь только о максимальной POSIX-совместимости при использовании в качестве механизма посылки сообщений (реально) обычного символьного устройства.

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

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

Это не у меня проблемы, а у автора топика. Автор использует символьное устройство, которое реально, как кажется, таковым не является, и пытается быть при этом максимально POSIX-совместимым и не блокироваться на блокирующей атомарной записи. В этой каше явно есть плохо совместимые элементы. Особенно последние.

Если возникает конфликт требований - таки надо бы подумать над этими требованиями, а не над их реализацией. А вот потом появляется масса решений. Если посмотреть, то в разных ОС с заявой на полную POSIX-совместимость попадаются забавные решения с аналогичными фокусами, встречал протокол передачи пакетов с четким требованием записи через write целого пакета и размером от и до. Авторы поделки просто игнорировали любой некорректный write. Никаким API это не оборачивалось.

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

Остается определиться почему бокирующийся write должен стать неблокирующимся. Да ну нафиг. Все только через ioctl с набором битов для направления движения, возможностью блокирования и т.п.

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

Это не у меня проблемы, а у автора топика.

Окей. Но причина проблем остается той же.

Все только через ioctl с набором битов для направления движения, возможностью блокирования и т.п.

«Всё через ioctl» тоже плохо. Нужно просто понять, что драйверы - это обычно (не всегда) - символьно-ориентированные файлы и строить API, исходя из этого.

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

И я, и я того же мнения. А ioctl - это просто прикол. Хотя кто мешает сделать ioctl в стиле - «хана блокировке». Типа нулевой таймаут на ожидании блокирующей записи. Развлечений может быть масса.

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

И таки датаграмма прозвучала. А потом возникнет желание нормального управления потоком? Или пока еще не важно? А что мешает использовать сетевые буфера?

Круговой буфер не обязательно тупой буфер.

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

Остается определиться почему бокирующийся write должен стать неблокирующимся. Да ну нафиг. Все только через ioctl с набором битов для направления движения, возможностью блокирования и т.п.

Так я наоборот считаю что блокирующийся write должен таким и оставаться. Проблема в том что select выдал готовность, а write не готов. Это нормальное поведение? так и должно быть?

Круговой буфер не обязательно тупой буфер.

Не понял к чему это.

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

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

Впрочем аналогичный эффект был и на ДОС ЕС ЭВМ на некоторых видах ожидания, только тот кто обращался к якобы «системному вызову» этого не замечал - возврат был только при получении ресурса - внутри стоял цикл с проверкой получения. Подобных реализаций масса.

Для того, чтобы select-write цепочка надежно работала в данном случае, требуется чтобы (как в примере выше для read) select давал гарантию выполнения последующего write-а. Но тогда только один должен проснуться по select-у на write. И данный select должен работать как резервирующий, который не дает другим писать - вот хорош ли такой подход в конкретном случае?

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

Для того, чтобы select-write цепочка надежно работала в данном случае, требуется чтобы (как в примере выше для read) select давал гарантию выполнения последующего write-а. Но тогда только один должен проснуться по select-у на write. И данный select должен работать как резервирующий, который не дает другим писать - вот хорош ли такой подход в конкретном случае?

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

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

И на неблокируемые один-другой? А разные ниточки писать в тот же не будут? Если человека тянет к конкретному решению - ну что тут сделаешь?

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

на неблокируемый тоже самое. каждая ниточка будет делать свой open

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