Некоторые особенности асинхронного ввода-вывода

октября 12 2009 by admin in Обязательный материал

При асинхронном вводе-выводе следует учитывать множество деталей. Во-первых, для обслуживания поставленных в очередь запросов ввода-вывода не применяется стратегия «первым пришел — первым обслужен». Скажем, если поток содержит следующий код, драйвер устройства скорее всего сначала выполнит запись в файл, а затем чтение из файла:
OVERLAPPED о1 = { 0 };
OVERLAPPED о2 = { 0 };
BYTE bBuffer[100];
ReadFile (hfile, bBuffer, 100, NULL, &o1);
WriteFile(hfile, bBuffer, 100, NULL, &o2);
Драйвер устройства обычно исполняет запросы не в порядке их поступления, если это помогает увеличить производительность. Так, чтобы уменьшить время перемещения и позиционирования головок, драйвер файловой системы может просматривать список запросов ввода-вывода в поисках запроса, связанного с данными, расположенными рядом с текущим физическим расположением головок диска.
Следующий вопрос — надлежащая обработка ошибок. Большинство функций Windows возвращают FALSE в случае сбоя и ненулевое значение при успешном завершении. Но ReadFile и WriteFile ведут себя иначе. Разобраться в этом поможет следующий пример.
Получив запрос асинхронного ввода-вывода, драйвер устройства может предпочесть не ставить его в очередь, а обработать синхронно. Это может произойти, когда вы считываете данные из файла, и система проверяет, нет ли этих данных в кэше. Если данные доступны, драйвер устройства не ставит запрос в очередь, а система копирует данные из кэша в ваш буфер, и операция ввода-вывода, таким образом, завершается.
ReadFile и WriteFile возвращают ненулевые значения, если запрошенный ввод-вывод выполнился синхронно, если же асинхронно или возникает ошибка — возвращают FALSE. В последнем случае, чтобы определить, что именно произошло, вызовите GetLastEtror. Если она возвращает ERROR_IO_PENDING,
значит, запрос ввода-вывода успешно помещен в память и будет завершен позднее.
Если GetLastError возвращает отличное от ERROR_IO_PENDING значение, запрос не поставлен в очередь драйвера устройства. Вот самые распространенные коды ошибок, возвращаемые GetLastError, когда запрос ввода-вывода не может быть поставлен в очередь драйвера устройства:
ERRORJNVALIDUSERBUFFER или ERRORNOTENOUGH MEMORY
Каждый драйвер устройства поддерживает расположенный в неперемеща-емом пуле список фиксированной длины, в котором содержатся ожидающие выполнения запросы. Если этот список заполнен, система не может поставить в очередь ваш запрос и ReadFile или WriteFile возвращают FALSE, a функция GetLastError возвращает один из этих, двух кодов ошибки (в зависимости от драйвера). ERRORNOTENOUGHQUOTA Некоторые устройства требуют, чтобы ваш буфер данных находился на закрепленной странице, чтобы данные не могли откачиваться из ОЗУ при ожидании ввода-вывода. Это требование всегда имеет место при установленном флаге FILE_FLAG_NO_BUFFERING. Но система ограничивает объем закрепленной памяти, выделяемый одному процессу. При невозможности разместить ваш буфер в неперемещаемой памяти ReadFile и WriteFile возвращают FALSE, a GetLastError — ERROR_NOT_ ENOUGH_QUOTA. Увеличить KBo'iy для процесса позволяет функция SetPro-cessWorkingSetSize.
Как обрабатывать эти ошибки? Обычно они возникают из-за того, что остается некоторое количество невыполненных запросов, так что следует дождаться завершения выполняющихся запросов и повторно обратиться к Read-File или WriteFile.
Третье. Буфер данных и структура OVERLAPPED, указываемые при выдаче запроса асинхронного ввода-вывода, не должны перемещаться или уничтожаться, пока не выполнится запрос ввода-вывода. Когда запрос ввода вывода ставится в очередь драйвера устройства, драйверу передается адрес буфера данных и адрес структуры OVERLAPPED. Только адрес — не блок памяти! Причина очевидна: копирование памяти дороже и занимает больше процессорного времени.
Когда драйвер устройства готов обработать стоящий в очереди запрос, он обменивается данными с буфером, па который указывает параметр pvBuffer, и осуществляет доступ к элементам структуры OVERLAPPED, на которую указывает параметрpOverlapped. В частности, драйвер устройства обновляет элемент Internal, занося в него код ошибки ввода-вывода и элемент IntemalHigh, указывая число переданных байтов.
Недопустимо перемещать или уничтожать эти буферы, пока выполняется запрос ввода-вывода, — это может повредить память. Кроме того, для каждого запроса ввода-вывода надо выделять и инициализировать отдельную структуру OVERLAPPED.
Предыдущее замечание очень важно, и самые распространенные ошибки, допускаемые разработчиками при реализации асинхронного ввода-вывода, связаны именно с невыполнением этих условий. Вот как делать не надо:
VOID ReadData(HANDLE hfile) {
OVERLAPPED о = { 0 >;
BYTE b[100];
ReadFile(hfile, b, 100, NULL, &o); >
Выглядит довольно безобидно, и обращение к ReadFile производится корректно. Маленькое «но»: эта функция возвращает управление после занесения в очередь запроса асинхронного ввода-вывода. При возврате из функции из стека потока удаляется буфер и структура OVERLAPPED, а драйвер устройства об этом не знает. У драйвера по-прежнему два адреса, указывающие на стек потока, и когда ввод-вывод завершается, драйвер изменяет содержимое памяти в стеке потока, портя все, что хранится в соответствующих ячейках в этот момент времени. Найти эту ошибку очень трудно, так как изменение памяти происходит асинхронно. Иногда драйвер устройства осуществляет ввод-вывод синхронно, и тогда ошибки не возникает. Когда-то ввод-вывод может завершиться как раз после возврата из функции, а когда-то — через несколько часов, и кто знает, для чего в это время используется стек?