В компьютерных системах кроме процессора и оперативной памяти присутствует множество разнообразных устройств (device) – жесткие диски, приводы оптических дисков (CD, DVD, Blu-Ray Disk), устройства флеш-памяти, принтеры, сканеры, звуковые и видеокарты, модемы, сетевые карты и т. п.
Операционная система должна обеспечивать управление всеми этими устройствами, т. е. предоставлять способы обмена информацией между приложениями и устройствами.
Управление устройствами в Windows осуществляется подсистемой ввода вывода, включающей несколько компонентов (см. рис.4.1 в лекции 4 "Архитектура Windows"):
Далее будут рассмотрены общая схема ввода-вывода, функции и структуры данных диспетчера ввода-вывода, представленные в WRK, а также пример выполнения операции чтения.
Рассмотрим схематично принцип управления внешними устройствами, а затем перейдем к изучению соответствующих структур и функций WRK.
Для пользовательских приложений операционная система представляет устройства в виде файлов. Такое представление позволяет единообразно работать с разными устройствами, используя одинаковые функции, не задумываясь о деталях реализации доступа к устройствам.
Файл (file) – совокупность данных, имеющих имя и допускающих операции чтения-записи. Типичная последовательность работы с файлом: открытие файла, выполнение команд чтения-записи, закрытие файла.
При открытии файла создается файловый объект типа FILE_OBJECT, который связан с объектом, представляющим конкретное устройство (DEVICE_OBJECT). В объекте-устройстве содержится информация о драйвере, который управляет этим устройством. Драйвер в системе описывается объектом типа DRIVER_OBJECT. Объекты DRIVER_OBJECT создаются при загрузке в систему нового драйвера. Затем объект DRIVER_OBJECT может создать несколько объектов DEVICE_OBJECT – по количеству управляемых драйвером устройств (рис.15.1).
Как видно из рис.15.1, в объекте DRIVER_OBJECT содержится указатель на список объектов-устройств, а в каждом из этих объектов хранится ссылка на управляющий драйвер. Таким образом, имея информацию об объекте DRIVER_OBJECT, можно найти все устройства, которыми он управляет и, наоборот, по объекту DEVICE_OBJECT легко определяется драйвер устройства.
Приложение, которому необходимо произвести некоторую операцию с устройством (файлом), вызывает соответствующую WinAPI функцию (CreateFile, ReadFile, WriteFile и др.), которая, в свою очередь, обращается к функции диспетчера ввода-вывода.
Операция, которая запрашивается приложением, представляется в системе объектом типа IRP (I/O Request Packet – пакет запроса на ввод/вывод). В этом объекте хранится информация о типе операции ввода/вывода (создание, чтение, запись и т. п.), а также необходимые параметры для данной операции. Пакет IRP передается диспетчером ввода-вывода в очередь IRP потока, который запросил операцию ввода-вывода, после чего вызывается соответствующий драйвер, непосредственно выполняющий запрошенную операцию.
Драйвер в системе описывается объектом типа DRIVER_OBJECT (файл base\ntos\inc\io.h, строка 1603), имеющим следующие основные поля:
Устройства представлены объектами типа DEVICE_OBJECT, который включает следующие главные поля (файл base\ntos\inc\io.h, строка 1397):
Пакеты запроса на ввод-вывод описываются типом IRP (I/O Request Packet), состоящим из двух частей – заголовка фиксированной длины (тело IRP) и одного или нескольких блоков стека. В заголовке описывается информация, общая для запроса. Каждый блок стека содержит данные об одной операции ввода-вывода.
Заголовок включает следующие основные поля:
Структура блока стека IO_STACK_LOCATION описана в файле base\ntos\inc\io.h, строка 2303) и имеет следующие главные поля:
В следующем параграфе разобран пример операции чтения с использованием рассмотренных выше структур данных.
Для ввода-вывода используются следующие основные функции:
Рассмотрим пример чтения с устройства, используя изученные структуры данных и функцию NtReadFile (рис.15.2).
Предположим, некоторому приложению требуется прочитать данные с устройства, например, из файла на жестком диске. Предварительно приложение должно получить дескриптор объекта FILE_OBJECT, например, при помощи WinAPI функции CreateFile.
Для чтения из файла приложение вызывает WinAPI-функцию ReadFile, которая обращается к функции диспетчера ввода-вывода NtReadFile и передает ей дескриптор объекта FILE_OBJECT.
Функция NtReadFile определена в файле base\ntos\io\iomgr\read.c (строка 90) и выполняет две основные задачи – создает объект IRP (строка 517) и вызывает функцию IopSynchronousServiceTail (строка 725). При создании объекта IRP в блок стека заносится номер основной функции (Major Function), в случае операции чтения этот код равен константе IRP_MJ_READ (строка 558) и указывает на функцию чтения в массиве MajorFunction структуры DRIVER_OBJECT.
Функция IopSynchronousServiceTail определена в файле base\ntos\io\iomgr\internal.c (строка 7458). Эта функция помещает переданный ей объект IRP в очередь потока (функция IopQueueThreadIrp, строка 7468). Указатель на очередь IRP потока хранится в поле IrpList структуры ETHREAD (файл base\ntos\inc\ps.h, строка 623). Кроме этого, функция IopQueueThreadIrp вызывает соответствующий драйвер (функция IoCallDriver, строка 7494).
Драйвер выполняет определенную кодом IRP функцию и возвращает статус операции.
В лекции представлены компоненты подсистемы ввода вывода в Windows, рассмотрен принцип управления устройствами, а также реализация этого принципа на основе структур данных и функций Windows Research Kernel. Разобран пример ввода вывода для операции чтения из файла.
В следующей лекции подробно рассматривается структура основной файловой системы Windows – NTFS.