Re: fprintf && write

From
Valentin Nechayev ()
To
Boris Rudakov ()
Date
2003-06-05T09:59:08Z
Area
CARBON.COPY
 * Forwarded from area 'RU.UNIX.PROG'
From: Valentin Nechayev <netch@segfault.kiev.ua>


>>> Boris Rudakov wrote:

 >>> 1. Получить засыпание при вызове fprintf(...) где-то в ядре на
 >>> write(..) на очень большое время, что увы неприемлимо. 2.
 >>> Использовать alarm(time), write()/fprintf(), alarm(0); - я получу 3
 >>> системных вызова.
 LW>> а попробуй так:
 LW>> 1. при открытии дескриптора переводи его в non-blocking mode.
BR> О !!!
BR> А можно это место немножечко подробнее ?

В чём подробнее? Как вызывать или что происходит?
Как вызывать - метода два: установить O_NONBLOCK во флагах ещё при open(),
или установить позднее через fcntl(,F_SETFL). Кое в чём они принципиально
разные - некоторые устройства особым образом реагируют на O_NONBLOCK
при открытии (физические компорты, например, выключают ожидание CD)
и не реагируют так потом (O_NONBLOCK на открытом компорте не вызывает
отмену ожидания CD, если порт уже открыт, а вызывает только невпадание в
спячку на read/write).
Что происходит - происходит следующее: у *последовательных* устройств
и прочих файловых объектов (пайпы, сокеты...) операции чтения и записи
не блокируют процесс или отдельную ветку, а при отсутствии данных (на чтении)
или возможности принять данные (для записи) немедленно завершаются с передачей
особого кода, означающего "сейчас не могу, ждите". А чтобы ждать не крутясь
постоянно - методы опроса готовности. select, poll - аналоги
WaitForMultipleObjects(), с тем, что можно задавать не только объект, по
которому чего-то ждём, но и чего ждём. /dev/poll, epoll, kqueue - их
оптимизированные для случая многих объектов аналоги, напоминающие completion
ports.

Базовое отличие от винды, пожалуй - что этот режим можно менять после открытия.
Может, после Win2000 сделали такую смену, а до того надо было или заказывать
overlapped I/O с постоянным nonblocking тем самым, или блокироваться,
но переключить на ходу было нельзя.

Для устройств прямого доступа подход, описанный выше, неприменим.
Там используется (где есть;)) AIO, которое опять же можно проверять,
а можно попросить сделать нотификацию (сигналом, в kqueue и так далее).

BR> Я как-то в этой эхе уже спрашивал о юниксоидной методологии асинхронного IO, но
BR> ответы были хотя вполне внятные, но я таки не все сказанное догнал :):):):)

BR> Типовая (наитиповейшая, я бы сказал) задачка такова:
BR> Есть нитка, которая развлекается асинхронным чтением (можно и записью), и
BR> попутно ждет команды идти на в связи с шатдауном.
BR> "Вражеский" :):):) код, к примеру, выглядит так (сорри за размер, но тут все
BR> важно, т.к. мне интересны юниксоидные аналоги всех приведенных ниже
BR> приседаний):

OK, сделаем перевод для случая AIO. Ниже будет замечание, когда оно нужно
а когда нет.;)

BR> ...
BR> HANDLE hStopEvent = CreateEvent(0, FALSE, FALSE, 0); // Команда идти на

См. ниже.

BR> OVERLAPPED Overlap;  // Управление async IO, внутренний ивент инициализирован
BR> ...

BR> Читаем:

BR> ..... (HANDLE hFile, LPBYTE Buf, WORD Len)
BR> {
BR>   DWORD W;
BR>   while (Len) {
BR>     ...Тут чистим Overlap в зависимсти от типа файла, но всегда -
BR>     ResetEvent(Overlap.hEvent);
BR>     BOOL Ok = ReadFile(hFile, Buf, Len, &W, &Overlap);

aio_read() с предварительной подготовкой данных в передаваемом aiocb.
Важно содержимое aio_sigevent. В исходном AIO, оповещение о завершении
могло делаться только через отдачу сигнала. Аналог отдаче overlap'а в цикле
ожидания - есть разве что в системах с kqueue.

BR>     if (W) { // Ok may be FALSE, but W may be != 0 если pending IO, хех :)
BR>       Len -= W;
BR>       Buf += W;
BR>     }
BR>     while (!Ok) {
BR>       switch (GetLastError()) { // Аналог errno

Если аналог, то errno надо было перед началом цикла и после любого
успешного вызова обнулить. ;))

BR>         case ERROR_IO_PENDING: break; // Асинхронное безобразие in progress...

BR>         case ERROR_BROKEN_PIPE:         // Эти три взбрыка означают одно и
BR>         case ERROR_NO_DATA:             // то же но для разных типов файлов;
BR>         case ERROR_NETNAME_DELETED:     // лучше проверять все
BR>                                         // универсальности кода для :)
BR>           // Connection lost = TRUE; Все плохо...
BR>           return FALSE;
BR>         default:
BR>           // Прочий взбрык, пишем ошибку куда надо и
BR>           CancelIo(hFile); // Побашкелопатана !

aio_cancel()

BR>           return FALSE;
BR>       }
BR>       // *Самое интересное* - ждем одно из двух событий
BR>       HANDLE hWait[] = {hStopEvent, Overlap.hEvent};
BR>       switch (WaitForMultipleObjects(2, hWait, FALSE, INFINITE)) {

Вместо hStopEvent - notification pipe для этой ветки, тут почти однозначно.
При наличии kqueue - ждать в select() или poll() возможности чтения из
notification pipe или из kqueue. Без - в обработчике сигнала писать в
notification pipe, или если дана гарантия что select() не рестартует -
достаточно факта вызова обработки сигнала. Хорошей синхронизации без поллинга
между condvar и сигналами или kqueue скорее всего не будет.

BR>         case 0: // Нам велели идти на - yes my lord...
BR>           CancelIo(hFile); // Побашкелопатана !
BR>           return FALSE;
BR>         case 1: // В файле чего-то зашевелилось...
BR>           Ok = GetOverlappedResult(hFile, &Overlap, &W, TRUE);

aio_error(), aio_return()

BR>           if (W) { // Снова Ok may be FALSE, но W != 0 из-за pending IO
BR>             Len -= W;
BR>             Buf += W;
BR>           }
BR>           ...Снова чистим Overlap, но всегда -
BR>           ResetEvent(Overlap.hEvent);
BR>           break; // Прогоним внутренний цикл чтобы посмотреть ошибку...
BR>         default:
BR>          // Сбой ожидания - кто-то грохнул hStopEvent или Overlap.hEvent
BR>          CancelIo(hFile); // Побашкелопатана !
BR>          return FALSE;
BR>       }
BR>     }
BR>   }
BR>   return TRUE; // Все, Len bytes из hFile успешно высосаны
BR> }

BR> Реинкарнации этого кода могут варьироваться, но суть всегда одна и та же:
BR> * Инициируем операцию IO, имеем в виду что она может пролететь сразу же вся
BR> (данные упихались в буфера ядра и асинхронность не потребовалась) - выходим

С AIO такой общий метод тоже будет работать, но заказывать его ради
объектов последовательного доступа дороговато, IMO. В этом случае простой
цикл вокруг select/poll/kqueue/... дешевле.

BR> * Операция прошла не успешно - смотрим чего стряслось. Если это
BR> ERROR_IO_PENDING - пошло асинхронное IO и надо им озаботиться

Угу, аналогично.

BR> * Делаем GetOverlappedResult чтобы узнать статус асинхронного IO, он тоже может
BR> сказать FALSE но снова выставить ERROR_IO_PENDING - ядро продолжает асинхронную
BR> операцию
BR> * Чем заняться пока идет IO - глубоко пофигу: когда операция завершится ядро
BR> просигналит Overlapped.hEvent и последующий GetOverlappedResult скажет TRUE.

Аналогично, или получим сигнал, или сможем читать из kqueue.

BR> Наш код сидит в WaitForMultipleObjects и интересуетя еще и командой прекратить
BR> операцию
BR> Надо заметить, что буфера (по крайней мере у НТи) достаточно велики и обычно
BR> такой код ограничивается одноим только ReadFile, в pending проваливаясь
BR> достаточно редко. Как управлять буферами дисковых файлов я не помню, буфера
BR> пайпов задаются при их создании, буфера же сокетов по дефолту 4k и управляются
BR> обычным sockopt.

Я видел общесистемную настройку для readahead, AFAIR.

BR> Я привел этот здоровый кусок "вражеского" кода чтобы поспрошать как такое
BR> делается в юниксе.
BR> 1. Как перевести хендл файла в асинхронный режим ?

Файла - никак. Только вызывать AIO. Это место удобнее, чем в винде,
потому что не требует переключения;))
Можно на него поставить O_NONBLOCK, но скорее всего это проигнорируется.
Систему, где O_NONBLOCK вызовет асинхронное чтение, я не представляю себе -
операция на объекте прямого доступа требует указания места чтения,
которое через API конструкции "ждём пока что-то не случится не имея возможности
узнать точнее что случилось" не получится.
Фигня тут в том, что AIO есть не везде. В Linux, например, его так и не
включили, ссылаясь на то что "у нас ветки есть" - несмотря на то, что
файловый I/O для веток не перестаёт быть блокирующим и непрерываемым.

BR> 2. select - хорошо, но я встречал мало кода где он используется, эээээ...,
BR> скажем так - аккуратно используется :) В смысле что используется строго при
BR> необходимости тогда и только тогда, когда ясно что асинхроная операция требует
BR> внимания потому как чего-то уже прокачалось.

Напиши правильно.;))

BR> 3. Как можно ждать нескольких РАЗНОРОДНЫХ СОБЫТИЙ ???? Сколько я ни читал про
BR> юникс API я как-то не нашел ничего, равного по мощи и универсильности
BR> виндозному WaitForMultipleObjects.
BR> В данном примере я упихал туда ивент
BR> отвечающий за команду завершения (некая нить супервизор просто сделает
BR> SetEvent(hStopEvent) и данный код прекратит IO поймав это событие) и ивент из
BR> Overlapped (его взведет ядро когда асинхронное чтение закончится). А вообще я
BR> могу засунуть туда хэндл любого объекта ядра - нити или процесса (их
BR> просигналят когда нить / процесс завершатся) и ты ды и ты пы.

BR> Как такие вещи правильно делать в юниксе ?

WaitForMultipleObjects() - тоже кривое. Оно говорит _где_ чего ждать,
но не говорит _что_ ждать;)) Например, если по сокету мы можем ждать и
готовности к чтению, и готовности к записи, то на это придётся порождать
какие-то промежуточные объекты. Для Unix это всегда было больнее, чем
объединение ожидания чтения с диска и с сети, поэтому и подходы другие.

А так, аналог WaitForMultipleObjects() надо делать на уровне приложения,
а не ядра, и первейшим средством будут notification pipes. Вместо поднятия
флага в event'е - запись байтика в пайп - "ветка XXX, обрати внимание,
тебе что-то пришло". Да, это криво. Впрочем, так же криво, как completion
ports на разные операции на сокете.;))

 LW>> 2. В подобных твоему участках кода делай _сначала_ write(), а уж
 LW>> потом, если write() вернул -1/EAGAIN, то select:
BR> Т.е. write(...) => -1, errno == EAGAIN - это то же самое что
BR> WriteFile(...) => FALSE, GetLastError() == ERROR_IO_PENDING ?

Именно.

 LW>> while() {
 LW>>   if(write()) {
 LW>>     select();
BR> Если write() > 0 то зачем select ?
BR> Подождать пока пройдет следующая порция IO ?

Да.

 LW>>     ...
 LW>>   }
 LW>> }
BR> А как мне подождать не только IO complette, а еще и чего-то внешнего, типа
BR> hStopEvent в моем примере ?

См. выше.


-netch-
--- ifmail v.2.15dev5
 * Origin: Dark side of coredump (2:5020/400)