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)