Дескрипторы устройств ввода вывода ос windows

Стандартные устройства и консольный ввод/вывод

Стандартные устройства и консольный ввод/вывод

Стандартные устройства и консольный ввод/вывод

Как и в UNIX, в Windows предусмотрены три стандартных устройства, предназначенные, соответственно, для ввода данных (input), вывода данных (output) и вывода сообщений об ошибках (error). В UNIX для этих устройств используются известные системе значения дескрипторов файлов (0, 1 и 2), однако в Windows доступ к стандартным устройствам осуществляется с помощью дескрипторов типа HANDLE, для получения которых предоставляется специальная функция.

HANDLE GetStdHandle(DWORD nStdHandle)

Возвращаемое значение: в случае успешного выполнения — действительный дескриптор, иначе — значение INVALID_HANDLE_VALUE.

Параметры

Параметр nStdHandle должен принимать одно из следующих значений:

• STD_INPUT_HANDLE

• STD_OUTPUT_HANDLE

• STD_ERROR_HANDLE

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

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

BOOL SetStdHandle(DWORD nStdHandle, HANDLE hHandle)

Возвращаемое значение: в случае успешного выполнения — TRUE, иначе — FALSE. 

Параметры

Допустимые значения параметра nStdHandle функции SetStdHandle являются теми же, что и в случае функции GetStdHandle. Параметр hHandle указывает открытый файл, который назначается в качестве стандартного устройства.

Одним из методов перенаправления стандартного ввода/вывода является последовательный вызов функций SetStdHandle и GetStdHandle. Полученный в результате этого дескриптор используется в последующих операциях ввода/ вывода.

Для указания путей доступа к консольному вводу (клавиатуре) и консольному выводу предусмотрены два зарезервированных имени: «CONIN$» и «CONOUT$». Роль стандартных устройств ввода, вывода и вывода ошибок первоначально отводится консоли. Однако консоль можно использовать даже после того, как операции ввода/вывода, требующие применения стандартных устройств, будут перенаправлены; для этого требуется лишь открыть дескрипторы для файлов «CONIN$» и «CONOUT$», вызвав функцию CreateFile. 

В UNIX стандартный ввод/вывод может быть перенаправлен одним из трех способов (см. [40], стр. 61—64).

Первый метод является косвенным и основывается на том, что функция dup возвращает дескриптор файла с наименьшим доступным номером. Предположим, вы хотите переназначить стандартный ввод (файловый дескриптор 0) открытому файлу, описанному как fd_redirect. Тогда можно записать следующий код:

close (STDIN_FILENO);

dup (fd_redirect);

Во втором методе используется функция dup2, а третий метод предполагает вызов замысловатой перегруженной функции fcntl с использованием в качестве параметра значения F_DUPFD.

Операции консольного ввода/вывода могут выполняться с помощью функций ReadFile и WriteFile, но проще использовать предназначенные специально для этого функции консоли ReadConsole и WriteConsole. Основное преимущество этих функций заключается в том, что они манипулируют не байтами, а обобщенными символами (TCHAR), и, кроме того, обрабатывают символы в соответствии с текущими режимами консоли, которые устанавливаются функцией SetConsoleMode. 

BOOL SetConsoleMode(HANDLE hConsoleHandle, DWORD fdevMode)

Возвращаемое значение: тогда, и только тогда, когда функция завершается успешно — TRUE, иначе — FALSE. 

Параметры

hConsoleHandle — дескриптор буфера ввода консоли или буфера дисплея, который должен быть создан с правами доступа GENERIC_WRITE, даже если устройство предназначено только для ввода информации.

Параметр fdevMode задает способ обработки символов. В имени каждого из его флагов содержится компонент, указывающий, к чему относится данный флаг— к вводу (input) или выводу (output). Ниже перечислены пять обычно используемых флагов, причем все они устанавливаются по умолчанию.

• ENABLE_LINE_INPUT — возврат из функции чтения (ReadConsole) происходит только после считывания символа возврата каретки.

• ENABLE_ECHO_INPUT — эхо-отображение вводимых символов на экране.

• ENABLE_PROCESSED_INPUT — установка этого флага приводит к обработке системой управляющих символов возврата на одну позицию, возврата каретки и перехода на новую строку.

• ENABLE_PROCESSED_OUTPUT — установка этого флага приводит к обработке системой управляющих символов возврата на одну позицию, табуляции, подачи звукового сигнала, возврата каретки и перехода на новую строку.

• ENABLE_WRAP_AT_EOL_OUTPUT — переход на следующую строку экрана как при обычном выводе символов, так и при их эхо-отображении в процессе ввода.

В случае неудачного завершения функции SetConsoleMode текущий режим остается неизменным, и функция возвращает значение FALSE. Как обычно, для получения номера ошибки следует воспользоваться функцией GetLastError.

Функции ReadConsole и WriteConsole аналогичны функциям ReadFile и WriteFile. 

BOOL ReadConsole(HANDLE hConsoleInput, LPVOID lpBuffer, DWORD cchToRead, LPDWORD lpcchRead, LPVOID lpReserved)

Возвращаемое значение: тогда, и только тогда, когда функция завершается успешно — TRUE, иначе — FALSE.

Параметры у этой функции почти те же, что и у функции ReadFile. Значения обоих параметров, связанных с количеством подлежащих считыванию (cchToRead) и фактически считанных (lpcchRead) символов, выражаются в терминах обобщенных символов, а не байтов, а значение параметра lpReserved должно быть равным NULL. Как и во всех остальных подобных случаях, никогда не используйте для собственных нужд зарезервированные поля, аналогичные lpReserved, которые встречаются в некоторых функциях. Параметры функции WriteConsole имеют тот же смысл и не нуждаются в дополнительных пояснениях. В очередном примере будет проиллюстрировано применение функций Read-Console и WriteConsole, и, кроме того, будет показано, как использовать возможности управления режимом консоли.

Любому процессу в каждый момент времени может быть назначена только одна консоль. Приложениям того типа, с которым мы имели дело до сих пор, консоль передается обычно на стадии инициализации. Однако в целом ряде других случаев, например, в случае серверных или GUI-приложений, у вас может возникнуть необходимость в получении отдельной консоли, на которую можно было бы выводить информацию о состоянии программы или отладочную информацию. Для этих целей можно воспользоваться двумя простыми функциями, не имеющими параметров. 

BOOL FreeConsole(VOID)

BOOL AllocConsole(VOID) 

Функция FreeConsole отключает процесс от его консоли, тогда как функция AllocConsole создает новую консоль, ассоциированную с дескрипторами стандартного ввода информации, стандартного вывода информации и стандартного вывода сообщений об ошибках, принадлежащими данному процессу. Если консоль у процесса уже имеется, функция AllocConsole завершится с ошибкой; чтобы избежать этого, следует предварительно вызывать функцию FreeConsole.

Примечание

GUI-приложения Windows не имеют консоли по умолчанию и должны получить ее, прежде чем смогут воспользоваться функциями WriteConsole или printf для вывода на консоль. Процессы на стороне сервера также могут не иметь консоли. О том, как создать процесс без консоли, рассказано в главе 6. 

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

Исторически сложилось так, что ОС Windows ориентирована на использование терминалов или консолей в меньшей степени, чем UNIX, и не полностью воспроизводит функциональные средства UNIX, поддерживающие работу с терминалами. В книге [40] одна из глав посвящена рассмотрению обеспечиваемых UNIX возможностей терминального ввода/вывода (глава 11), а другая — псевдотерминалам (глава 19).

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

Читайте также

4.4. Ввод и вывод

4.4. Ввод и вывод
Все операции Linux по вводу/выводу осуществляются посредством дескрипторов файлов. Данный раздел знакомит с дескрипторами файлов, описывает, как их получать и освобождать, и объясняет, как выполнять с их помощью

Ввод и вывод

Ввод и вывод
Также мне кажется очень важным, чтобы мои результаты подпитывались соответствующим «вводом». Написание программного кода – творческая работа. Обычно мои творческие способности в наибольшей степени проявляются тогда, когда я сталкиваюсь с творческим

5.2. Ввод и вывод литер

5.2. Ввод и вывод литер
Наименьшей единицей данных, которая может участвовать в операциях ввода-вывода, является литера. Мы уже знаем, что литеры интерпретируются как небольшие целые числа в соответствии с кодом ASCII. В Прологе имеется несколько встроенных предикатов для

Глава 12. Ввод—вывод

Глава 12. Ввод—вывод

Почти в каждом приложении приходится читать или записывать файлы или выполнять другие операции ввода—вывода. Qt обеспечивает великолепную поддержку ввода—вывода при помощи QIODevice — мощной абстракции «устройств», способных читать и записывать

2.2.9. Перекрытый ввод-вывод

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

10.1.7. Простой ввод/вывод

10.1.7. Простой ввод/вывод
Вы уже знакомы с некоторыми методами ввода/вывода из модуля Kernel; мы вызывали их без указания вызывающего объекта. К ним относятся функции gets и puts, а также print, printf и p (последний вызывает метод объекта inspect, чтобы распечатать его в понятном для нас

2.1.4. Стандартный ввод-вывод

2.1.4. Стандартный ввод-вывод
В стандартной библиотеке языка С определены готовые потоки ввода и вывода (stdin и stdout соответственно). Они используются функциями scanf(), printf() и целым рядом других библиотечных функций. Согласно идеологии UNIX, стандартные потоки можно

Ввод и вывод

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

Ввод/вывод

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

5.7. Файловый ввод–вывод

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

11.3. Устройства, для которых есть стандартные драйверы

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

Ввод и вывод

Ввод и вывод
Два класса библиотеки KERNEL обеспечивают основные средства ввода и вывода: FILE и STD_FILES.Среди операций, определенных для объекта f типа FILE, есть следующие:create f.make («name») — Связывает f с файлом по имени name.f.open_write — Открытие f для записиf.open_read — Открытие f для

Аннотация: Подсистема ввода-вывода. Принцип управления устройствами. Структуры данных для ввода-вывода. Пример ввода-вывода.

Подсистема ввода-вывода

В компьютерных системах кроме процессора и оперативной памяти присутствует множество разнообразных устройств (device) – жесткие диски, приводы оптических дисков (CD, DVD, Blu-Ray Disk), устройства флеш-памяти, принтеры, сканеры, звуковые и видеокарты, модемы, сетевые карты и т. п.

Операционная система должна обеспечивать управление всеми этими устройствами, т. е. предоставлять способы обмена информацией между приложениями и устройствами.

Управление устройствами в Windows осуществляется подсистемой ввода вывода, включающей несколько компонентов (см. рис.4.1 в лекции 4 «Архитектура Windows«):

  • диспетчер ввода-вывода (I/O manager – Input/Output manager) – основной компонент; обеспечивает интерфейс между приложениями и устройствами;
  • диспетчер PnP (Plug and Play manager) – компонент, реализующий принцип Plug and Play («подключи и работай») – автоматическое распознавание и конфигурацию подключаемых к системе устройств;
  • диспетчер электропитания (power manager) – обеспечивает поддержку различных режимов энергопотребления системы и устройств;
  • драйверы устройств – программы, реализующие операции ввода-вывода для конкретного устройства; драйверы больше других компонентов системы «знают» о специфике своего устройства;
  • HAL (Hardware Abstraction Layer) – уровень абстрагирования от аппаратных средств; скрывает от других компонентов особенности реализации конкретных процессоров, системных плат и контроллеров прерываний;
  • реестр (registry) – используется как база данных для параметров устройств и драйверов.

Далее будут рассмотрены общая схема ввода-вывода, функции и структуры данных диспетчера ввода-вывода, представленные в WRK, а также пример выполнения операции чтения.

Принцип управления устройствами

Рассмотрим схематично принцип управления внешними устройствами, а затем перейдем к изучению соответствующих структур и функций WRK.

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

Файл (file) – совокупность данных, имеющих имя и допускающих операции чтения-записи. Типичная последовательность работы с файлом: открытие файла, выполнение команд чтения-записи, закрытие файла.

При открытии файла создается файловый объект типа FILE_OBJECT, который связан с объектом, представляющим конкретное устройство (DEVICE_OBJECT). В объекте-устройстве содержится информация о драйвере, который управляет этим устройством. Драйвер в системе описывается объектом типа DRIVER_OBJECT. Объекты DRIVER_OBJECT создаются при загрузке в систему нового драйвера. Затем объект DRIVER_OBJECT может создать несколько объектов DEVICE_OBJECT – по количеству управляемых драйвером устройств (рис.15.1).

Объекты для управления вводом-выводом

Рис.
15.1.
Объекты для управления вводом-выводом

Как видно из рис.15.1, в объекте DRIVER_OBJECT содержится указатель на список объектов-устройств, а в каждом из этих объектов хранится ссылка на управляющий драйвер. Таким образом, имея информацию об объекте DRIVER_OBJECT, можно найти все устройства, которыми он управляет и, наоборот, по объекту DEVICE_OBJECT легко определяется драйвер устройства.

Приложение, которому необходимо произвести некоторую операцию с устройством (файлом), вызывает соответствующую WinAPI функцию (CreateFile, ReadFile, WriteFile и др.), которая, в свою очередь, обращается к функции диспетчера ввода-вывода.

Операция, которая запрашивается приложением, представляется в системе объектом типа IRP (I/O Request Packet – пакет запроса на ввод/вывод). В этом объекте хранится информация о типе операции ввода/вывода (создание, чтение, запись и т. п.), а также необходимые параметры для данной операции. Пакет IRP передается диспетчером ввода-вывода в очередь IRP потока, который запросил операцию ввода-вывода, после чего вызывается соответствующий драйвер, непосредственно выполняющий запрошенную операцию.

Структуры данных для ввода-вывода

Драйвер в системе описывается объектом типа DRIVER_OBJECT (файл basentosincio.h, строка 1603), имеющим следующие основные поля:

  • Type – поле, определяющее тип структуры подсистемы ввода-вывода. Значения этого поля могут быть следующими – IO_TYPE_DRIVER, IO_TYPE_FILE, IO_TYPE_DEVICE, IO_TYPE_IRP и др. (см. файл basentosincio.h, строка 25);
  • Size – размер объекта в байтах;
  • DeviceObject – ссылка на первый объект DEVICE_OBJECT в списке устройств, управляемых данным драйвером (см. рис.15.1). Следующие устройства в списке можно определять по полю NextDevice объекта DEVICE_OBJECT;
  • Flags – флаги, определяющие тип драйвера (см. файл basentosincio.h, строка 1530);
  • DriverName – имя драйвера в системе;
  • HardwareDatabase – путь в реестре к информации о драйвере;
  • DriverStart, DriverSize, DriverSection – информация о расположении драйвера в памяти;
  • DriverInit – адрес процедуры DriverEntry (точка входа в драйвер), отвечающей за инициализацию драйвера;
  • DriverUnload – адрес процедуры выгрузки драйвера;
  • MajorFunction – массив адресов процедур, каждая из которых отвечает за определенную операцию с устройством. Максимальное количество таких процедур равно константе IRP_MJ_MAXIMUM_FUNCTION+ 1 = 2 8 (файл basentosincio.h, строка 80), которая определяет также количество кодов IRP (см. далее).

Устройства представлены объектами типа DEVICE_OBJECT, который включает следующие главные поля (файл basentosincio.h, строка 1397):

  • Type, Size – совпадают по назначению с полями типа DRIVER_OBJECT;
  • ReferenceCount – счетчик количества открытых дескрипторов для устройства. Позволяет отслеживать, используется кем-либо устройство или нет;
  • DriverObject – ссылка на драйвер, который управляет устройством;
  • NextDevice – указатель на следующее устройство в списке устройств для данного драйвера;
  • Flags, Characteristics – поля, уточняющие характеристики устройства;
  • DeviceType – тип устройства; возможные типы перечислены в файле publicsdkincdevioctl.h (строка 26);
  • SecurityDescriptor – дескриптор безопасности, сопоставленный с устройством (см. лекцию 9 «Безопасность в Windows»).

Пакеты запроса на ввод-вывод описываются типом IRP (I/O Request Packet), состоящим из двух частей – заголовка фиксированной длины (тело IRP) и одного или нескольких блоков стека. В заголовке описывается информация, общая для запроса. Каждый блок стека содержит данные об одной операции ввода-вывода.

Заголовок включает следующие основные поля:

  • Type, Size – поля, по назначению аналогичные соответствующим полям типов DRIVER_OBJECT и DEVICE_OBJECT;
  • IoStatus – статус операции при завершении;
  • RequestorMode – режим, в котором работает поток, инициировавший операцию ввода-вывода, – пользовательский или режим ядра;
  • StackCount – количество блоков стека;
  • Tail.Overlay.Thread – указатель на структуру ETHREAD потока, запросившего операцию ввода-вывода;
  • Tail.Overlay.CurrentStackLocation – указатель на блок стека (IRP Stack Location), который описывается структурой IO_STACK_LOCATION.

Структура блока стека IO_STACK_LOCATION описана в файле basentosincio.h, строка 2303) и имеет следующие главные поля:

  • MajorFunction – номер основной функции, определяющий запрошенную операцию ввода-вывода и совпадающий с номером функции драйвера в массиве MajorFunction (структура DRIVER_OBJECT, см. выше), которую нужно вызвать для выполнения запрошенной операции. Как уже отмечалось, всего кодов 28 (IRP_MJ_MAXIMUM_FUNCTION + 1), они описаны в файле basentosincio.h (строки 51–79);
  • DeviceObject – указатель на структуру DEVICE_OBJECT, определяющую устройство для данной операции ввода-вывода;
  • FileObject – указатель на структуру FILE_OBJECT (файл basentosincio.h, строка 1763), которая ассоциирована со структурой DEVICE_OBJECT.

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

Пример ввода-вывода

Для ввода-вывода используются следующие основные функции:

  • создание/открытие файла – IoCreateFile (файл basentosioiomgriosubs.c, строка 4795);
  • чтение из файла – NtReadFile (файл basentosioiomgrread.c, строка 90);
  • запись в файл – NtWriteFile (файл basentosioiomgrwrite.c, строка 87);
  • закрытие файла – IopDeleteFile файл basentosioiomgrobjsup.c, строка 465).

Рассмотрим пример чтения с устройства, используя изученные структуры данных и функцию NtReadFile (рис.15.2).

Последовательность операций и структуры данных при чтении с устройства

Рис.
15.2.
Последовательность операций и структуры данных при чтении с устройства

Предположим, некоторому приложению требуется прочитать данные с устройства, например, из файла на жестком диске. Предварительно приложение должно получить дескриптор объекта FILE_OBJECT, например, при помощи WinAPI функции CreateFile.

Для чтения из файла приложение вызывает WinAPI-функцию ReadFile, которая обращается к функции диспетчера ввода-вывода NtReadFile и передает ей дескриптор объекта FILE_OBJECT.

Функция NtReadFile определена в файле basentosioiomgrread.c (строка 90) и выполняет две основные задачи – создает объект IRP (строка 517) и вызывает функцию IopSynchronousServiceTail (строка 725). При создании объекта IRP в блок стека заносится номер основной функции (Major Function), в случае операции чтения этот код равен константе IRP_MJ_READ (строка 558) и указывает на функцию чтения в массиве MajorFunction структуры DRIVER_OBJECT.

Функция IopSynchronousServiceTail определена в файле basentosioiomgrinternal.c (строка 7458). Эта функция помещает переданный ей объект IRP в очередь потока (функция IopQueueThreadIrp, строка 7468). Указатель на очередь IRP потока хранится в поле IrpList структуры ETHREAD (файл basentosincps.h, строка 623). Кроме этого, функция IopQueueThreadIrp вызывает соответствующий драйвер (функция IoCallDriver, строка 7494).

Драйвер выполняет определенную кодом IRP функцию и возвращает статус операции.

Резюме

В лекции представлены компоненты подсистемы ввода вывода в Windows, рассмотрен принцип управления устройствами, а также реализация этого принципа на основе структур данных и функций Windows Research Kernel. Разобран пример ввода вывода для операции чтения из файла.

В следующей лекции подробно рассматривается структура основной файловой системы WindowsNTFS.

Контрольные вопросы

  1. Перечислите компоненты подсистемы ввода вывода в Windows.
  2. Дайте определение понятия «файл».
  3. Опишите основные структуры данных, участвующие в процессе ввода вывода.
  4. Расскажите о взаимодействии объектов FILE_OBJECT, DEVICE_OBJECT, DRIVER_OBJECT, IRP в процессе ввода вывода.
  5. Какую роль играет массив MajorFunction в структуре DRIVER_OBJECT?
  6. Приведите пример ввода вывода с описанием участвующих в нем структур данных и функций Windows Research Kernel.

5.3.1. API-функции
для
организации
ввода-вывода

Обычно, если
программист хочет открыть файл, он
использует один из стандарт-

ных вызовов библиотеки языка программирования
(например, в языке C это функция

fopen). В большинстве языков программирования предусмотрены достаточно удобные

высокоуровневые средства работы с
файлами. Однако в некоторых ситуациях
требуется

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

уровневые функции.
Например, прямое обращение к операционной
системе может по-

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

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

вается
CreateFile.
В зависимости от флагов, которые
программист передает в качестве

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

ществующий. В любом
случае вызов CreateFile() создает дескриптор
файла и возвращает

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

работы с файлом. Описание аргументов
функции приведено в таблице 5.4 [10].

HANDLE CreateFile(LPCTSTR lpFileName,

DWORD dwDesiredAccess,

DWORD dwShareMode,

LPSECURITY_ATTRIBUTES lpSecurityAttributes,

DWORD dwCreationDispostion,

DWORD dwFlagsAndAttributes,

HANDLE hTemplateFile);

132

Аргумент

lpFileName

Описание

Указатель на имя файла или устройства

Таблица 5.4

DesiredAccess

dwShareMode

lpSecurityAttributes

dwCreationDisposi-

tion

Устанавливает вид доступа к объекту.
Используются флаги GE-

NERIC_READ (чтение), GENERIC_WRITE (запись) или оба
при

помощи оператора логического сложения.
Значение 0 указывает

на необходимость проверки возможности
доступа.

Набор битовых флагов, указывающий на
режим совместного

доступа к объекту. Если значение
dwShareMode равно 0, совме-

стный доступ к объекту запрещен. Флаги
FILE_SHARE_DELETE

(совместное удаление), FILE_SHARE_READ (совместное
чте-

ние) и FILE_SHARE_WRITE (совместная запись).

Указатель на структуру SECURITY_ATTRIBUTES,
которая оп-

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

черними процессами. Если аргумент
lpSecurityAttributes имеет

значение NULL, дескриптор не будет
наследоваться.

Указывает, каким образом следует создать
(или открыть) файл.

Допускается использовать следующие
значения: CREATE_NEW

(создать новый файл; если файл существует,
функция не сраба-

тывает), CREATE_ALWAYS (создать новый файл;
если файл

существует, он перезаписывается),
OPEN_EXISTING (открыть

файл; если файл не существует, функция
не срабатывает),

OPEN_ALWAYS (открыть файл; если файл не
существует, он

создается) или TRUNCATE_EXISTING (открыть файл
и сделать

его равным нулю; если файл не существует-
не срабатывает).

dwFlagsAndAttributes Набор атрибутов и флагов,
которыми должен обладать файл.

Например, если требуется, чтобы новый
файл был скрытым, ис-

пользуйте значение FILE_ATTRIBUTE_HIDDEN. Другие
воз-

можные значения флагов перечислены в
табл. 5.5.

hTemplateFile

Содержит дескриптор с доступом
GENERIC_READ. Это деск-

риптор шаблонного файла, атрибуты
которого (включая расши-

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

Наиболее интересным аргументом функции
CreateFile() является предпоследний,

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

ловой системы.
Используя различные комбинации флагов
(табл. 5.5), можно настроить

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

лить файл сразу
же после того, как он будет закрыт, или
объявить, что файл будет ис-

пользоваться для перекрывающегося
(overlapped) доступа (перекрывающийся доступ

базовый
метод осуществления асинхронного
ввода/вывода). Помимо прочего флаги по-

зволяют управлять механизмом кэширования.

Таблица 5.5

Флаг

1

FILE_FLAG_WRITE_THROUGH

FILE_FLAG_NO_BUFFERING

1

Значение

2

Приказывает Windows осуществлять немед-

ленную запись данных на диск. При этом
воз-

можно использование КЭШа.

Приказывает системе открыть файл без
ис-

пользования кэширования или буферизации,

что дает максимальную производительность.

Окончание
табл. 5.5

2

133

FILE_FLAG_RANDOM_ACCESS

FILE_FLAG_SEQUENTIAL_SCAN

FILE_FLAG_OVERLAPPED

FILE_FLAG_DELETE_ON_CLOSE

FILE_FLAG_BACKUP_SEMANTICS

FILE_FLAG_POSIX_SEMANTICS

Оповещает систему о том, что доступ к
файлу

осуществляется случайным образом.
Система

может использовать это обстоятельство
для

оптимизации кэширования файла.

Оповещает систему о том, что доступ к
файлу

осуществляется последовательно от
начала к

концу файла. Система может использовать
это

обстоятельство для оптимизации кэширова-

ния файла.

Приказывает системе инициализировать
объ-

ект для перекрывающегося ввода/вывода.

Приказывает системе уничтожить файл
сразу

же после того, как все его дескрипторы
будут

закрыты.

Указывает на то, что файл предназначен
для

операций резервного копирования или
вос-

становления из резервной копии.
Операцион-

ная система разрешает вызывающему
процес-

су любой доступ к файлу при условии,
что

вызывающий процесс обладает привилегиями

SE_BACKUP_NAME и SE_RESTORE_NAME.

Доступ к файлу осуществляется в
соответст-

вии с правилами POSIX. При этом разрешает-

ся использовать несколько различных
файлов,

имена которых отличаются только
регистром

букв. Такие файлы разрешается создавать

только в системах, поддерживающих
подоб-

ную схему именования файлов.

FILE_FLAG_OPEN_REPARSE_POINT Подавляет поведение,
свойственное для точек

грамматического разбора (reparse points) фай-

ловой системы NTFS. Когда файл открывает-

ся, вызывающему процессу возвращается
его

дескриптор вне зависимости от того,
работо-

способен ли фильтр, контролирующий
точку

грамматического разбора, или нет. Этот
флаг

не может использоваться совместно с
флагом

CREATE_ALWAYS.

FILE_FLAG_OPEN_NO_RECALL

Информирует систему о том, что вызывающее

приложение запрашивает данные, хранящиеся

в файле, однако файл может продолжать
оста-

ваться на удаленном носителе данных.
Этот

флаг используется удаленными системами

хранения данных или совместно с системой

Hierarchical Storage Management.

Еще одной особенностью функции
CreateFile() является обслуживание сменных

носителей информации. Если носитель данных в данный момент недоступен
(гибкий

диск вытащили из дисковода), операционная
система выведет на экран диалоговое
окно

с сообщением об
ошибке. Если вы хотите избежать этого
(обычно, убедившись в отсут-

ствии гибкого диска,
приложение принимает меры самостоятельно),
необходимо обра-

титься к функции
SetErrorMode() и передать ей в качестве параметра флаг

134

SEM_FAILCRITICALERRORS. В этом случае при отсутствии
гибкого диска в дисководе

система не будет отображать на экране
каких-либо сообщений об ошибках.

Обладая
дескриптором файла, можно выполнить чтение или
запись данных при

помощи функций
ReadFile() и WriteFile() соответственно.
Эти функции работают стан-

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

на буфер, длину
буфера и указатель на переменную, в
которой будет сохранено количе-

ство прочитанных или записанных байт. Если используется перекрывающийся

ввод/вывод,
в качестве одного из аргументов
необходимо передать указатель на струк-

туру
OVERLAPPED. Для обычных файлов указатель на эту структуру всегда равен

NULL.

BOOL ReadFile( HANDLE hFile;

LPVOID lpBuffer;

// дескриптор файла

// буфер для временного хранения

// прочитанных данных

DWORD dwBytesToRead; // количество байтов, которые
должны

// быть прочитаны

LPDWORD lpdwBytesRead;// возвращает количество
прочитанных байтов

LPOVERLAPPED lpOverlapped ); // поддержка асинхронного
ввода/вывода

BOOL WriteFile( HANDLE hFile,

// дескриптор файла

CONST VOID *lpBuffer, //указывает данные, которые
должны быть

// записаны в файл

DWORD dwBytesToWrite,

LPDWORD lpdwBytesWritten,

// количество записываемых байтов

// возвращает количество записанных
байтов

LPOVERLAPPED lpOverlapped ); // задает поддержку
асинхронного

// ввода/вывода

Чтобы закрыть файл, используется функция
CloseHandle(). Эту функцию можно

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

крыть любой другой дескриптор.

BOOL CloseHandle( HANDLE hObject);

Для примера приведен
исходный код простой программы, использующей
файло-

вые операции
для отображения на экране содержимого
одного или нескольких файлов.

Вызов
CreateFile() открывает файл для чтения. После этого вызов
GetStdHandle() воз-

вращает дескриптор
стандартного вывода. Затем при помощи
функций ReadFile() и Wri-

teFile() блоками по 4 Кбайт происходит
передача содержимого файла с жесткого
диска в

стандартный поток
вывода. Передача продолжается до тех
пор, пока программа не обна-

ружит конец файла.

#include <windows.h>

void MB(char *s) // Для удобства использования
MessageBox

{

MessageBox(NULL, s, NULL, MB_OK | MB_ICONSTOP);

}

void docat(char *fname) // основная подпрограмма

{

HANDLE f=CreateFile ( fname, GENERIC_READ, 0, NULL, OPEN_EXISTING,
0,

NULL);

HANDLE out=GetStdHandle(STD_OUTPUT_HANDLE);

if (f==INVALID_HANDLE_VALUE)

135

{

}

MB(«He могу открыть файл»);

exit(1);

}

char buf[4096];

unsigned long n;

do

{

unsigned long wct;

if (!ReadFi1e(f, buf, sizeof(buf), &n, NULL))

break;

if (n)

WriteFile(out, buf, n, &wct, NULL);

}

while (n==sizeof(buf)); // Если EOF, это условие не
выполняется

CloseHandle(f);

void main(int argc, char *argv[])

{

if (argc==1)

{

// утилита cat — Ошибки фактически не
обрабатываются

// Любая ошибка вызывает аварийное
завершение программы

MB(«Usage: cat FILENAME [FILENAME ….]»);

exit(9);

}

// Обработать все указанные файлы

while (—argc) docat(*++argv);

exit(0);

}

Вот так происходит работа с файлами на
уровне Win32 API. Обычно при обраще-

нии к ReadFile() или
WriteFile() работа программного потока
приостанавливается до того

момента, когда операция чтения/записи
будет завершена.

Благодаря механизмам асинхронного ввода/вывода программа может
осуществ-

лять ввод/вывод данных и одновременно
с этим выполнять какие-либо другие
полезные

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

асинхронный
ввод/вывод
— это механизм, позволяющий программе
осуществлять обмен

данными с каким-либо
устройством ввода/вывода и одновременно
с этим выполнять ка-

кую-либо другую
полезную работу [1]. Например, программа
отдает команду на чтение

данных из файла,
а затем, не ожидая завершения чтения
данных, приступает к выполне-

нию математических
вычислений. Когда данные из файла
перемещаются в оперативную

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

ных. При этом, это
лишь один из множества возможных
сценариев использования асин-

хронного ввода/ вывода.

Если вы разрабатываете
программу, принимающую данные из
нескольких разных

источников, вам также может потребоваться асинхронный ввод/вывод. Предположим,

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

туры, так и из
последовательного порта. По мере
поступления данных, программа долж-

на немедленно
осуществлять их обработку. В этой
ситуации также чрезвычайно удобно

использовать механизмы асинхронного
ввода/ вывода.

136

5.3.2.
Механизмы
асинхронного
ввода-вывода

Асинхронный ввод/вывод можно реализовать несколькими разными способами.

Проще
всего создать новый
программный
поток
и осуществлять весь ввод/ вывод сред-

ствами этого потока. Вместо
этого можно использовать перекрывающийся
ввод/вывод

или порты завершения ввода/вывода. Еще одной технологией, с помощью которой

можно
реализовать асинхронный ввод-вывод
является технология
проецирования
фай-

лов, рассмотренная ранее.

Использование потоков.
Если вы не хотите, чтобы работа программного потока

приостановилась
для выполнения процедур ввода/вывода,
создайте второй программный

поток и поручите выполнение ввода/вывода ему. Тогда первый программный поток

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

процедуры, связанные
с вводом/выводом. Например, для реализации
терминальной про-

граммы вы можете
использовать два потока: один
читает команды, вводимые с клавиа-

туры, а второй
следит за поступлением команд через
последовательный порт. Оба пото-

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

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

ступающими
командами. При этом скорость поступления
данных через последователь-

ный порт не повлияет на скорость обработки
команд, вводимых с клавиатуры.

При реализации
подобного простого подхода обычно
возникает проблема: третий

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

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

для его работы
требуется дополнительное процессорное
время. Более сложная реализа-

ция
может предусматривать использование
событий
(рассмотрены ранее при изучении

многопоточности).
Когда один из потоков ввода принимает
символ, он переводит специ-

ально предназначенное
для этого событие в сигнальное состояние.
При этом третий по-

ток может использовать для слежения за
состоянием события функцию WaitForMultip-

leObjects(). To есть он
не будет расходовать слишком много
процессорного времени. Для

слежения за
состоянием консоли можно использовать
непосредственно дескриптор кон-

соли. Этот дескриптор
переходит в сигнальное состояние в
момент, когда в потоке ввода

консоли
появляются данные, ожидающие чтения.
Если вместо события программа сле-

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

Если терминальная программа обладает собственным циклом обработки систем-

ных сообщений (к
консольным приложениям это обычно не
относится), вы можете ис-

пользовать другой подход.
Потоки, осуществляющие ввод/вывод, могут
передавать ос-

новному окну программы сообщения, определенные пользователем. Основная

программа будет
реагировать на эти сообщения так же,
как она реагирует на любые сис-

темные сообщения.

Все перечисленные
стратегии очень просты в реализации,
однако они недостаточ-

но эффективны.
Создание потоков — относительно
ресурсоемкая операция. Кроме того,

максимальное количество потоков, одновременно работающих в системе, ограничено.

Конечно, это не
будет проблемой для простой консольной
программы, однако, если ва-

ше приложение
должно поддерживать интенсивный
асинхронный ввод/вывод, примене-

ние для этой цели большого количества
потоков может оказаться неприемлемым.

Перекрывающийся
ввод/вывод.
Значительно более эффективным способом
реали-

зации асинхронного
ввода/вывода в Windows является перекрывающийся
ввод/вывод. В

Windows эта разновидность ввода/вывода
поддерживается в отношении фактически
всех

типов файлов. В
частности, вы можете применить
перекрывающийся ввод/вывод в от-

ношении дисковых файлов, коммуникационных портов, именованных каналов (named

pipes) и сетевых
сокетов. В общем и целом, если в отношении
чего-либо можно приме-

137

нить операции ReadFile() и WriteFile(), значит,
при этом допускается использовать пере-

крывающийся ввод/вывод.

Прежде чем вы
сможете осуществлять в отношении файла
операции перекрываю-

щегося ввода/вывода,
необходимо открыть файл при помощи
функции CreateFile() с ис-

пользованием флага
FILE_FLAG_OVERLAPPED. Если этот флаг не указать,
перекры-

вающийся ввод/вывод будет невозможен. Если флаг установлен, вы не сможете

использовать файл
обычным способом. При обмене данными с
файлом будет использо-

ваться перекрывающийся ввод/вывод.

Выполняя
перекрывающийся ввод/вывод при помощи
функций ReadFile() и Write-

File(), в качестве
одного из аргументов вы передаете этим
функциям указатель на струк-

туру OVERLAPPED. Первые
два 32-битных слова этой структуры
зарезервированы сис-

темой для внутреннего использования. Вторые два слова могут содержать
64-битное

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

запись данных.
Ввод/вывод осуществляется асинхронно,
поэтому не существует гаран-

тий, что данные из
файла будут извлекаться (или записываться)
последовательно байт за

байтом. Таким
образом, при выполнении перекрывающегося
ввода/вывода не существу-

ет понятия текущей позиции. При выполнении
любой операции вы просто указываете

смещение в файле.
Если вы работаете с потоками данных
(например, последовательный

порт или сокет),
понятие смещения теряет смысл, поэтому
система игнорирует соответ-

ствующее
поле структуры OVERLAPPED. Последнее поле
структуры OVERLAPPED —

это дескриптор события. Впрочем, его
можно не указывать (присваивать NULL).

Когда происходит
обращение к функции асинхронного
ввода/вывода, система мо-

жет завершить процедуру ввода/вывода немедленно. Например, вы можете приказать

системе выполнить чтение одного байта из
последовательного устройства и записать

этот байт в
30-байтный буфер, ожидающий ввода. В этом
случае функция ввода/вывода

завершает свою
работу так, словно вы и не использовали
при этом механизмов перекры-

вающегося ввода/вывода.
Если функция вернула ненулевое значение,
значит, операция

ввода/вывода успешно
завершена, а передача данных
произошла фактически точно так

же, как если бы вы не использовали
перекрывающегося ввода/вывода.

Особенности
асинхронного ввода/вывода проявляются
в случае, если функция воз-

вращает значение
FALSE. Нулевое значение, возвращенное
функцией ввода/вывода, оз-

начает, что либо процедура ввода/вывода находится в стадии выполнения, либо про-

изошла какая-либо ошибка. Чтобы уточнить, чем, собственно, завершился вызов

функции, необходимо
вызвать функцию GetLastError(). Если этот вызов
вернул значение

ERROR_IO_PENDING, значит,
запрос на ввод/вывод либо ожидает своей
очереди, либо

находится в стадии
выполнения. Любое другое значение
означает, что произошла ошиб-

ка.

Если вы обратились
к функции ввода/вывода и получили
подтверждение того, что

запрос на ввод/вывод находится в процессе
исполнения, рано или поздно вы захотите

узнать, когда же
все-таки осуществление ввода/вывода
завершится? Если вы осуществ-

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

мента, пока не завершится операция чтения.
Если вы осуществляете запись, рано или

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

Наиболее простой способ проверить текущее состояние запроса на ввод/вывод

воспользоваться функцией
GetOverlappedResult().

BOOL GetOverlappedResult( HANDLE hFile,

// дескриптор файла или устройства

LPOVERLAPPED lpOverlapped, // поддержка асинхронного
ввода/вывода

LPDWORD lpNumberOfBytesTransferred, // количество
переданных байт

BOOL bWait );

138

// флаг ожидания

В качестве аргументов этой функции
необходимо передать дескриптор файла,
ука-

затель на структуру
OVERLAPPED, использованную при обращении к функции вво-

да/вывода, указатель
на переменную, в которую будет занесено
количество переданных

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

да/вывода или ему следует немедленно вернуть управление вызывающей программе,

чтобы сообщить ей
о текущем состоянии запроса на ввод/вывод.
Если этот флаг имеет

значение TRUE и
операция ввода/вывода все еще не
завершена, вызов будет ожидать до

тех пор, пока операция ввода/вывода завершится или произойдет ошибка. Если флаг

ожидания имеет значение FALSE, вызов
возвращает ошибку (нулевое значение).

Если вызов вернул
нулевое значение, а флаг ожидания равен
FALSE, необходимо

обратиться к GetLastError(). Если этот вызов вернул значение ER-

ROR_IO_INCOMPLETE, значит,
процедура ввода/вывода все еще не
завершена. Любое

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

ошибка.

Чтобы прервать выполнение операции ввода/вывода,
следует использовать функ-

цию CanselIo.

BOOL CancelIo( HANDLE hFile); // дескриптор файла

Эта функция
отменяет выполнение любых запросов на
ввод/вывод, инициирован-

ных для некоторого дескриптора файла текущим потоком. Вызов
CanselIo() работает

только в отношении
процедур перекрывающегося ввода/вывода.
Очевидно, что если по-

ток инициировал
традиционную процедуру ввода/вывода,
он не сможет обратиться к ка-

ким-либо другим
вызовам до тех пор, пока процедура
ввода/вывода не будет завершена.

Если вы прервали
выполнение запроса на ввод/вывод при
помощи вызова CanselIo(), со-

ответствующая операция ввода/вывода завершается с сообщением об ошибке
ER-

ROR_OPERATION_ABORTED (это
значение можно получить при помощи
вызова Get-

LastError()).

В случае если было
инициировано слишком большое количество
запросов на асин-

хронный ввод/вывод, функция
ReadFile() может завершиться возвратом значения
ER-

ROR_INVALID_USER_BUFFER или ERROR_NOTENOUGH_MEMORY.

В некоторых ситуациях для осуществления операций перекрывающегося вво-

да/вывода удобнее
использовать специально предназначенные
для этого функции Read-

FileEx() и
WriteFileEx(). Эти функции не могут использоваться для обычного вво-

да/вывода.
В качестве дополнительного
аргумента
каждый из этих вызовов принимает

указатель на функцию, которая будет вызвана в момент завершения операции вво-

да/вывода.

BOOL ReadFileEx( HANDLE hFile,

LPVOID lpBuffer,

DWORD nNumberOfBytesToRead,

LPOVERLAPPED lpOverlapped,

LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine );

BOOL WriteFileEx( HANDLE hFile,

LPCVOID lpBuffer,

DWORD nNumberOfBytesToWrite,

LPOVERLAPPED lpOverlapped,

LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine );

139

Однако для того, чтобы произошло обращение
к функции завершения, поток, обра-

тившийся к
ReadFileEx() или WriteFileEx(), должен находиться в
«настороженном» со-

стоянии. Чтобы
перейти в настороженное состояние,
необходимо использовать вызовы

WaitForSingleObject() или
SleepEx(). Находясь в настороженном состоянии,
поток не вы-

полняет никаких
действий, а просто ждет, когда в результате
завершения операции вво-

да/вывода произойдет обращение к функции
завершения.

Все эти особенности
перекрывающегося ввода/вывода несколько
обескураживают.

Перекрывающийся ввод/вывод удобен во многих ситуациях,
однако он не настолько

асинхронный, как этого хотелось бы
программистам. Чтобы реализовать
действительно

асинхронный
ввод/вывод, следует создать отдельный
программный поток, в рамках ко-

торого использовать
обычные функции ввода/вывода. В качестве
альтернативы при раз-

работке некоторых программ можно использовать порты завершения ввода/вывода

них речь пойдет чуть позже).

Если традиционная функция чтения данных в процессе осуществления операции

ввода встречает символ
EOF, она тем или иным образом оповещает
об этом вызываю-

щую программу. В
частности, традиционный вызов ReadFile()
устанавливает количество

прочитанных байт равным нулю. Если
символ EOF встречается в процессе
выполнения

перекрывающегося вызова
ReadFile(), вызов возвращает ошибку.
В этом случае значе-

ние, возвращаемое функцией
GetLastError(), будет равно ERROR_HANDLE_EOF. Сим-

вол EOF может
встретиться непосредственно при
обращении к функции ReadFile() или

позже, в процессе выполнения операции
ввода/вывода.

Порты
завершения
ввода/вывода.
Перекрывающийся ввод/вывод обладает
массой

ограничений.
Фактически при использовании перекрывающегося
ввода/вывода для об-

мена данными
с некоторым объектом
(файлом или устройством) используется отдель-

ный программный
поток. Например, если вы планируете
использовать перекрывающий-

ся ввод/вывод при разработке сетевого сервера, обслуживанием каждого из клиентов

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

случае, если число
клиентов небольшое. Однако вряд ли
удастся использовать подобный

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

одновременно
достаточно большое количество клиентов.
Не всякий компьютер сможет

обеспечить
создание и поддержку работы нескольких
тысяч программных потоков од-

новременно. Может ли один поток обслуживать одновременно нескольких клиентов?

Этого
можно достичь, если использовать порты
завершения
ввода/вывода
(I/O comple-

tion ports) [1].

Порт завершения
ввода/вывода напоминает очередь. В эту
очередь заносятся уве-

домления о том,
что та или иная процедура ввода/вывода
завершена. Любой поток мо-

жет проверить очередь и отреагировать на любое из этих уведомлений. Прежде чем

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

создать его при помощи вызова
CreateIoCompletionPort().

HANDLE CreateIoCompletionPort (HANDLE FileHandle, // дескриптор
файла

HANDLE ExistingCompletionPort, // дескриптор создаваемого

// (или открываемого) порта завершения

ULONG_PTR CompletionKey, // ключ завершения,

// вставляемый в каждый пакет

DWORD NumberOfConcurrentThreads ); //количество
подключаемых

// потоков

В качестве одного из аргументов эта функция принимает дескриптор файла, от-

крытого для перекрывающегося ввода/вывода.
Как только дескриптору файла ставится
в

соответствие порт завершения ввода/вывода,
при успешном завершении любой опера-

140

ции ввода/вывода в очередь порта
завершения будет внесено уведомление
о завершении

этой операции. Вызов CreateIoCompletionPort() можно
использовать не только для соз-

дания нового порта завершения, но также
и для назначения уже существующему
порту

другого дескриптора файла.

При обращении к
функции CreateIoCompletionPort() вы можете указать
ключ — 32-

битное значение, которое система добавляет в каждое из уведомлений об успешном

осуществлении
операций ввода/вывода, связанных с
указанным файлом. Последний ар-

гумент функции
CreateIoCompletionPort() служит для передачи этой функции макси-

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

уведомлений о завершении ввода/вывода.
Обычно значение этого аргумента равно ну-

лю.

Любой поток может
получить информацию о завершении
ввода/вывода при помо-

щи функции
GetQueuedCompletionStatus(). Эта функция возвращает количество байт,

переданных в процессе ввода/вывода, ключ завершения
(32-битное число, установлен-

ное при обращении к
CreateIoCompletion) и структуру OVERLAPPED, использованную

при инициализации процедуры ввода/вывода.

BOOL GetQueuedCompletionStatus( HANDLE CompletionPort, //дескриптор
порта

LPDWORD lpNumberOfBytes, // количество переданных
байт

PULONG_PTR lpCompletionKey, //указатель на ключ
завершения,

// (если он объявлен ранее)

LPOVERLAPPED *lpOverlapped, //указатель на Overlapped

DWORD dwMilliseconds );

// время ожидания пакета

При создании порта завершения ввода/вывода
вы можете не ставить ему в соответ-

ствие каких-либо
дескрипторов файлов. В этом случае порт
завершения может исполь-

зоваться в качестве механизма связи между несколькими потоками одного процесса.

При помощи функции
PostQueuedCompletionStatus() любой поток может поместить в

очередь
порта завершения уведомление
о завершении ввода/вывода. При этом необхо-

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

OVERLAPPED, которая будет возвращена
ожидающему потоку вызовом функции Get-

QueuedCompletionStatus().

PostQueuedCompletionStatus( HANDLE CompletionPort,

DWORD dwNumberOfBytesTransferred,

ULONG_PTR dwCompletionKey,

LPOVERLAPPED lpOverlapped );

Безусловно, во многих ситуациях вы можете обойтись и без портов завершения

ввода/вывода, однако очень часто этот
механизм оказывается весьма удобным.

Выше было указано,
что еще одной «экзотической
технологией», поддерживаемой

ОС
Windows для асинхронного ввода-вывода являются файлы, отображаемые в па-

мять.
Менеджер виртуальной памяти Windows
позволяет программе работать с файлом

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

На самом деле это
не так. Менеджер виртуальной памяти
загружает в оперативную па-

мять компьютера только
фрагменты файла (страницы). Чтобы отобразить
файл на опе-

ративную память,
необходимо его открыть при помощи
функции CreateFile() и передать

дескриптор файла CreateFileMapping(). Подробно
этот механизм рассмотрен ранее.

В итоге рассмотрения всех возможностей файлового ввода/вывода возникает во-

прос — обязаны ли
программисты всегда использовать
CreateFile() и остальные связанные

с этим функции
для того, чтобы работать с файлами в
Windows? Конечно, нет. Если вы

141

ориентируетесь на традиционные методики работы
с файлами, то можете продолжать

использование функции
fopen(), MFC-класса CFile, потоков C++ или любой другого

традиционного
метода, к которому привыкли. Однако,
если вы желаете воспользоваться

специальными возможностями
Windows (такими как, например, перекрывающийся

ввод/вывод), то придется применить системный вызов
CreateFile() и все связанные с

этим дальнейшие
действия, так как большинство библиотек
компиляторов не поддержи-

вают технологий, являющихся особенностью
ОС Windows.

142

C.5. Перенаправление ввода/вывода.

Содержание

    C.5.1. Назначение потоков.
    C.5.2. Ограничение потоков.
    C.5.3. Определение и классификация потоков.
    C.5.4. Переназначение потоков.
    C.5.5. Переназначение стандартного потока ошибок (в UNIX).
    C.5.6. Перенаправление потока ввода.
    C.5.7. Конвейерная обработка.
    C.5.8. Перенаправление вывода на принтер.
    C.5.9. История возникновения потоков в MS-DOS.
    C.5.10. Резюме.

C.5.1. Назначение потоков.

Ввод/вывод в операционных системах (как MS-DOS, так и UNIX) может быть организован двумя принципиально разными способами. Первый способ — это прямое программирование устройств ввода/вывода (дисковода, экрана, модема, клавиатуры). Он может быть организован на различных уровнях (непосредственное программирование устройств, использование сервисных средств операционной системы, смешанный подход и т.д.), но суть его при этом не меняется. Каждая программа, написанная с использованием этого способа, может работать только с этим устройством и ни с каким другим. В настоящее время используется именно этот способ. Именно с помощью этого подхода (точнее, путем «косвенного» программирования периферийных устройств через драйверы этих устройств) и реализован классический WIMP — интерфейс, о котором уже было много сказано в предыдущей главе. Он позволяет создавать красивые и довольно содержательные средства общения с пользователем (меню, окна и тому подобное), а современные технологии позволяют программе при установке автоматически настраиваться на установленное на компьютере оборудование. Но у этих систем есть недостаток: они не могут принять данные с устройств и передать данные устройствам, для работы с которыми они не созданы. Например, нельзя данные вводить с модема, если программа работает только с клавиатурой. Чтобы осуществить это, используют другой способ: ввод/вывод с использованием потоков. В этом случае каждое устройство рассматривается операционной системой как файл, куда можно поместить и откуда можно взять информацию. Так же, как информация, записанная в файл, рассматривается операционной системой как единое целое, не зависимо от способа записи его на диске, так и физическая реализация процесса ввода/вывода информации устройством никак не отражается на работе пользователя.

C.5.2. Ограничение потоков.

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

C.5.3. Определение и классификация потоков.

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

1. Стандартный поток ввода — это обычно клавиатура.

2. Стандартный поток вывода — это обычно монитор.

3. Стандартный поток вывода ошибок и диагностических сообщений (стандартный поток ошибок) — это обычно дисплей (монитор).

C.5.4. Переназначение потоков.

Использование потоков не имело бы никаких преимуществ перед прямым программированием устройств, если бы их нельзя было переназначать с одного устройства на другое , а также переназначить ввод/вывод информации устройством в файл . Для этого используются следующие конструкции (в DOS и UNIX).

> — переназначает стандартный поток вывода другому устройству (или файлу). Если этот файл существует, он очищается, и на его место записывается новая информация. Используется в DOS и UNIX. В UNIX те же действия может осуществлять конструкция 1>.
>> — То же самое, но если файл существует, то новая информация записывается в конец этого файла. Используется в DOS и UNIX. Для UNIX также можно использовать конструкцию 1>>.

C.5.5. Переназначение стандартного потока ошибок (в UNIX).

Обычными средствами DOS не может перенаправить стандартный поток ошибок. В UNIX для этого можно использовать команды 2> и 2>>, соответствующие вышеприведенным для стандартного потока вывода.

Для перенаправления в UNIX стандартного потока ошибок в стандартный поток вывода используется конструкция >$ (по умолчанию в UNIX стандартному потоку ввода присваивается номер 0, стандартному потоку вывода — 1, а стандартному потоку ошибок — 2. Конструкция в UNIX 0 >$ 2 переназначает стандартный поток ввода в стандартный поток ошибок, то есть все символы, введенные в этом сеансе с клавиатуры, тут же отображаются на экране, и их нельзя перенаправить в файл).

C.5.6. Перенаправление потока ввода.

Переадресация стандартного ввода осуществляется конструкцией < для DOS и UNIX и 0> только для UNIX.

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

program < indata.dat >> outdata.txt

В данном примере входные данные программы program читаются из файла indata.dat и записываются в файл outdata.txt.

В примере:

sort < mylist > prn

данные из файла mylist сортируются стандартной программой MS-DOS sort и результат выводится на принтер.

C.5.7. Конвейерная обработка.

Символы переадресации очень удобны, но иногда бывает необходимо организовать последовательность программ, выполняющих обработку информации, причем результат редыдущей программы является исходным для следующей . При этом промежуточные данные желательно никуда не записывать. Чтобы организовать такую обработку, используют знак ‘|’ конвейера . Команды-«цепочки» такой обработки данных просто записываются в одну строку в порядке их вызова для обработки данных, и отделяются одна от другой знаком конвейера ‘|’. Пример:

sort < mylist | more.

В этом примере данные из файла mylist сортируются программой sort и постранично выводятся на экран программой more.

В UNIX тоже возможна переадресация потока с одновременным выводом данных на экран, и даже переадресация на два разных устройства. Для переадресации стандартного вывода в файл с одновременной выдачей информации на экран используется команда tee. Например, команда cat в UNIX позволяет просматривать файл. Следующая конструкция:

cat first | tee second

копирует файл first в файл second, одновременно показывая его на экране.

C.5.8. Перенаправление вывода на принтер.

Для вывода данных на принтер используются конструкции:

в DOS > prn в UNIX lpr

Еще один пример: команда

cat first | tee second | lpr

копирует файл first в файл second, одновременно распечатывая его на принтере.

Командой lpr можно также вывести несколько файлов на печать. Подробности смотри ниже.

C.5.9. История возникновения потоков в MS-DOS.

Исторически первая версия MS-DOS основывалась на многоплатформенной операционной системе для восьмиразрядных процессоров — CP/M. От нее она унаследовала и способ работы с файлами — через контрольные блоки файлов (File Control Block, FCB). Но уже во второй версии MS-DOS стала использоваться позаимствованная из операционной системы UNIX работа с файлами через дескрипторы файлов (File Handle). Одновременно с реализацией метода дескрипторов файла в MS-DOS из UNIX перешел поточный механизм организации файлов и возможность перенаправления потоков. с тех пор работа с файлами через FCB стала анахронизмом и поддерживается в операционных системах фирмы Microsoft (вплоть до Windows 98) лишь для совместимости со старыми версиями.

Следует также отметить, что термины «FCB», «дескрипторы файлов», «потоки» употребляются применительно к интерфейсу операций ввода/вывода операционных систем, а не для файловых систем как таковых. Так, использование для работы с файлами метода дескрипторов может происходить и в операционной системе MS-DOS с файловой системой FAT, и в Windows 2000 с NTFS, и в Novell NetWare с NWFS, и в Linux с NFS!

C.5.10. Резюме.

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

Напоследок приводим таблицы, в которой кратко приводятся данные, описанные в этом разделе.

Номер Название потока в UNIX Устройство
0 Стандартный поток ввода консоль, клавиатура
1 Стандартный поток вывода консоль, дисплей
2 Стандартный поток ошибок дисплей

Устройство Обозначение в DOS.
Консоль CON
1-й параллельный порт LPT1
2-й параллельный порт LPT2
Принтер (синоним LPT1) PRN
1-й последовательный порт COM1
2-й последовательный порт COM2
3-й последовательный порт COM3
4-й последовательный порт COM4
Модем (синоним COM1) AUX
Пустое устройство NUL

Символ Смысл перенаправления
> Перенаправляет стандартный вывод на другое устройство или в файл. Содержимое файла при этом теряется.
>> Перенаправляет стандартный вывод в файл. Содержимое файла не теряется. Запись идет в режиме добавления.
< Осуществляет (перенаправляет) стандартный ввод с другого устройства или из файла.
| Символ конвейерной обработки. Выходные данные работы программы, расположенной слева, передаются на вход программы, расположенной справа от знака конвейера.

Назад |
Содержание |
Вперед

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

Есть 3 файловых дескриптора: stdin — стандартный ввод, stdout — стандартный вывод и stderr — стандартный поток ошибок. В скриптах 1 означает stdout, а 2 — stderr.

Практически это означает, что для программ, которые используют стандартные входные и выходные устройства, операционная система позволяет:

  • перенаправлять stdout в файл
  • перенаправлять stderr в файл
  • перенаправлять stdout в stderr
  • перенаправлять stderr в stdout
  • перенаправлять stderr и stdout в файл
  • перенаправлять stderr и stdout в stdout
  • перенаправлять stderr и stdout в stderr
  • перенаправление stderr и stdout по конвейеру

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

А теперь примеры:

1. Перенаправление стандартного потока программы в файл с заменой содержимого файла

ping ya.ru -t > log.txt
ping ya.ru -t 1> log.txt

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

2. Перенаправление стандартного потока программы в файл с до записью содержимого лога

ping ya.ru -t >> log.txt

Тоже самое, но при прерывание пинга и начале нового, старое содержимое лога не затрется, а новое дописывается в конец

ping ya.ru -t 1>> log.txt

3. Перенаправление потока ошибок программы в фаил с заменой содержимого

ping ya.ru -t 2> log.txt

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

4. То же самое, но с до записью содержимого лога.

ping ya.ru -t 2>> log.txt

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

ping ya.ru > log.txt 2>&1

или с до записью лога

ping ya.ru >> log.txt 2>&1

В данном примере стандартный поток ошибок пересылается в стандартный поток (конструкция 2>&1) а потом стандартный поток (уже с завернутым в него потоком ошибок) посылается в лог.

6. В этом примере все наоборот, стандартный поток, пересылается в поток ошибок и уже поток ошибок перенаправляется в лог:

ping ya.ru > log.txt 1>&2

или с до записью лога

ping ya.ru >> log.txt 1>&2

7. По аналогии с Linux системами в Windows можно перенаправить весь или часть вывода программы в виртуальное устройство, а проще говоря слить в мусор.

Таким устройством является nul, и делать перенаправление в него можно используя все выше представленные комбинации. Например

ping ya.ru > nul

В Linux есть еще одна конструкция перенаправления, а именно &>/var/log/log.txt, она перенаправляет ВСЕ без исключения потоки программы в указанное место, по сути являясь более коротким и более грамотным аналогом конструкции >log.txt 1>&2. Но к сожалению в Windows это не работает.

А теперь давайте немного разберемся в прикладных различиях между работой данных методов. В нормальных приложениях все разбито на потоки, но у большинства виндовых утилит это не так, пинг например, пишет все в стандартный поток (на экран), поэтому для него конструкция вида 2> не имеет смысла. Но есть еще не виндовые утилиты, для примера возьмем curl (мой любимый).

Он разделяет 3 вида вывода, вывод полезной информации, вывод служебной информации и вывод ошибок. Если перенаправить вывод так: > или >> или 1> или 1>> то по завершению запроса отобразится служебная информация о запросе, а вся полезная информация уйдет в лог (это именно то, что уходит по конвейеру |).

А теперь сделаем заведомо ошибочный запрос, изменив протокол http на http3 не меняя вывода в лог. В итоге мы получим ошибку на экране.

Изменим вывод в лог на один из этих: 2> или 2>> ошибка ранее выводившаяся на экран, попала в лог, и на экране ничего не будет (служебной информации нет, так как запрос произведен не был).

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

И вывод был бы таким:

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

На данном скриншоте, конструкцией 2>&1 мы завернули поток ошибок в стандартный поток, а конструкцией > 5555.txt стандартный поток перенаправили в лог. Если вместо > 5555.txt использовать 2> 5555.txt, то есть перенаправить в лог стандартный поток ошибок, мы увидим весь вывод программы (и ошибки, и служебную информацию и полезный вывод) на экране. Конструкция 2>&1 имеет больший приоритет, а по ней уже все завернуто в стандартный поток.

Делать пример с заворотом стандартного потока в поток ошибок (1>&2) я не буду, ибо там все точно так же.

Надеюсь логика понятна…

Так же с помощью символа < можно прочитать входные данные для заданной команды не с клавиатуры, а из определенного (заранее подготовленного) файла. Для примера возьмем реальный и вполне полезный случай. Например, у нас есть файл log.txt и нам надо посчитать сколько в нем строк. Сделать это можно с помощью такой конструкции

find /c /v "" log.txt

но вывод будет не совсем приемлемым.

А вот если сделать так:

find /c /v "" < log.txt

то все будет именно так как надо.

Это происходит потому что в первом случае, файл обрабатывается как файл, а во втором, как поток (аналог линуксового конвейера cat log.txt |) в общем, < это виндовый аналог cat со всеми вытекающими.

Источник

Каталог оборудования

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Производители

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Функциональные группы

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Понравилась статья? Поделить с друзьями:
  • Джойстик defender scorpion rs3 драйвер windows 10
  • Дескриптор безопасности объекта в ос windows
  • Джеффри рихтер windows для профессионалов создание эффективных win32 приложений
  • Дерикс 12 скачать бесплатно для windows 10 64 bit официальный сайт
  • Деретикс 13 скачать бесплатно для windows 10 x64 с официального сайта