Кафедра: Программное обеспечение ЭВМ и информационные технологии
Курсовая работа
на тему: Разработка
драйвера виртуального жесткого диска
СОДЕРЖАНИЕ
СОДЕРЖАНИЕ. 1
ВВЕДЕНИЕ. 2
1. АНАЛИТИЧЕСКИЙ РАЗДЕЛ.. 4
1.1 Постановка задачи. 4
1.2 Архитектура Windows 2000. 4
1.3 Многослойная архитектура драйверов. 5
1.4 Архитектура драйверов устройств хранения. 8
1.5 Выбор файловой системы.. 9
2. КОНСТРУКТОРСКИЙ РАЗДЕЛ.. 11
2.1 Структура классового драйвера. 11
2.2 Организация внутреннего хранения данных диска. 12
2.3 Доступ к передаваемым данным. 13
2.4 Обработка запросов Plug and Play. 14
2.5 Обработка расширенных запросов. 15
2.7 Расчет геометрии диска. 16
2.6 Структура драйвера. 17
3. ТЕХНОЛОГИЧЕСКИЙ РАЗДЕЛ.. 18
3.1 Выбор и обоснование языка и среды программирования. 18
3.2 Структуры данных классового драйвера. 18
3.3 Блокировка выгрузки устройства. 19
3.4 Процедуры драйвера виртуального диска. 19
3.4.1 Инициализация
драйвера. 19
3.4.2Обработка запросов
записи/чтения. 22
3.4.3 Обработка
расширенных запросов. 24
3.4.4 Обработка
запросов Plug and Play. 26
3.4.5 Выгрузка драйвера. 28
3.5
Программа настройки
параметров виртуального диска. 29
3.6 Установка драйвера. 29
4.
ЭКСПЕРИМЕНТАЛЬНО-ИССЛЕДОВАТЕЛЬСКИЙ РАЗДЕЛ.. 31
4.1 Описание экспериментов. 31
4.2 Результаты экспериментов. 31
ЗАКЛЮЧЕНИЕ. 32
СПИСОК ИСПОЛЬЗОВАННОЙ ЛИТЕРАТУРЫ.. 33
ВВЕДЕНИЕ
В
настоящее время все усложняются программные продукты и комплексы, что приводит
к росту объема обрабатываемых данных, усложнению структуры их представления и
хранения на компьютере. Все это приводит к повышению требований к скорости
чтения и записи данных из файлов а также уменьшению времени доступа к каждому
файлу в отдельности.
Самым
распространенным устройством для хранения данных в настоящее время является
жесткий диск, который умеет хранить большие объемы данных. Время доступа к
произвольному месту на диске зависит от скорости перемещения считывающей
головки. Но быстродействие механики диска имеет предел, и время ответа для
жесткого диска на несколько порядков выше, чем для оперативной памяти. Поэтому
производительность при множественных операциях чтения и записи к различным
данным катастрофически падает.
Для
решения данной проблемы существуют следующие средства повышения
производительности. Все записываемые и считываемые данные не сразу пишутся на диск,
а сохраняются в определенной области оперативной памяти - кэше. Но размер кэша
не большой и в нем сохраняются только несколько или самых последних операций
чтения записи или самых частых, в зависимости от стратегии кэширования.
Однако
буферизация только на основе оперативной памяти в подсистеме ввода-вывода
оказывается недостаточной — разница между скоростью обмена с оперативной
памятью, куда процессы помещают данные для обработки, и скоростью работы
внешнего устройства часто становится слишком значительной, чтобы в качестве
временного буфера можно было бы использовать оперативную память — ее объема
может просто не хватить. Для таких случаев необходимо предусмотреть особые
меры, и часто в качестве буфера используется дисковый файл, называемый также спул-файлом
(от spool — шпулька, тоже буфер, только для ниток). Типичный пример применения
спулинга дает организация вывода данных на принтер. Для печатаемых документов
объем в несколько десятков мегабайт — не редкость, поэтому для их временного
хранения (а печать каждого документа занимает от нескольких минут до десятков
минут) объема оперативной памяти явно недостаточно.
Другой подход для
хранения данных в памяти – создание Ram
дисков. В систему добавляется виртуальный диск, а образ диска расположен в
оперативной памяти. Такой подход позволяет повысить быстродействие, когда
приложение использует обращения случайного чтения, случайной записи. Так
например, значительную часть всех обращений к данным в современных СУБД
составляют случайные запросы на чтение данных. Другое преимущество виртуальных
дисков – их можно использовать для бездисковых рабочих станций для
промежуточных файлов.
1. АНАЛИТИЧЕСКИЙ РАЗДЕЛ
1.1 Постановка
задачи
К
разрабатываемому драйверу виртуального диска предъявляются следующие
требования:
1.
Разрабатываемый драйвер должен добавлять в операционную
систему новый виртуальный диск
2.
Для увеличения скорости передачи, драйвер должен работать в
блочном режиме передачи данных
3.
Драйвер должен быть конфигурируемым, для драйвера можно
задать имя, которое присваивается диску и его размер
4.
Драйвер должен определять объем свободной физической памяти и
ограничивать размер создаваемого виртуального диска.
5.
Драйвер может динамически выгружаться из системы и
загружаться в нее без необходимости перезагружать компьютер
6.
Поскольку операции чтения записи, создания, переименования и
удаления файлов выполняет драйвер файловой системы, то нужно обеспечивать
считывание и запись необходимых файловой системе данных.
7.
Работа драйвера не должна влиять на работу других драйверов.
1.2 Архитектура Windows 2000
Наиболее
распространены реализации Windows 2000 для платформы Intel x86 в одно- или многопроцессорных конфигурациях, однако существуют
также версии для DEC Alpha и MIPS. Данная операционная система использует защищённый режим
центрального процессора, реализует механизмы виртуальной памяти и
многопоточности.
Исполняемый
код в Windows 2000 может исполняться в двух уровнях привилегий: пользовательском
режиме и режиме ядра. Уровень привилегий накладывает определённые
ограничения: в пользовательском режиме не могут выполняться привилегированные
инструкции процессора и не разрешено обращение к защищённым страницам в памяти.
Эти ограничения накладываются для обеспечения безопасности работы системы.
Пользовательское приложение не должно иметь возможность — в результате ошибки
или преднамеренно — вносить изменения в критические таблицы операционной
системы или в память других приложений. В частности, такие ограничения
запрещают пользовательскому приложению напрямую управлять внешними
устройствами, потому что каждое из них является разделяемым ресурсом.
В Windows 2000 обеспечение
обмена данными и управление доступом к внешнему устройству как к разделяемому
ресурсу возлагается на его драйвер. Ввод и вывод в драйверах
осуществляется пакетами — IRP (Input/Output Request Packet). Запросы на ввод/вывод, посылаемые приложениями или другими
драйверами, обрабатываются драйвером, после чего запрашивающей программе в том
же пакете посылается статус завершения операции. Общий принцип взаимодействия
проиллюстрирован на рис. 1.
Рассмотрим
как строится архитектура драйверов. Операционная система Windows® поддерживает
многослойную архитектуру драйверов. Каждое устройство обслуживается цепочкой
драйверов, называемой стеком драйверов. Каждый драйвер в стеке изолирует
аппаратно зависимые возможности от вышестоящего уровня.
На
рис. 2 показаны типы драйверов, которые могут обслуживать устройство. В
действительности, некоторые из этих типов могут отсутствовать в стеке.
Рис. 2 Многослойная архитектура драйверов
1.
Над стеком находятся приложения. Они обрабатывают запросы
пользователя или других приложений и вызывают или подсистему Win 32 или клиент драйвер
пользовательского режима.
2.
Клиент драйвер пользовательского режима обрабатывает запросы
от приложений или от подсистемы Win32. При получении запросов, которые требуют обработки в
режиме ядра, он вызывает нужный клиент драйвер режима ядра или процедуру
обработки запроса через подсистему Win32. Клиент драйвер реализуются как динамические библиотеки (DLL).
3.
Клиент драйвер режима ядра обрабатывает запросы подобно
клиент драйверу в режиме пользователя, за исключением того, что обработка идет
в режиме ядра.
4.
Классовый и мини классовый драйвер устройства предоставляет
основной набор сервисов обслуживания. Классовый драйвер предоставляет аппаратно
независимую поддержку для специфичного класса устройств. Мини классовый драйвер
обрабатывает отдельные операции для устройства из этого класса.
5.
Порт-драйвер (для некоторых устройств это драйвер
хост-контроллера или хост-адаптера) выполняет требуемые операции ввода/вывода
для порта, хаба или другого физического устройства, через которое
осуществляется подключение. От типа устройства и шины через которую оно
подключено зависит будет ли присутствовать этот драйвер в стеке. Все драйвера
устройств хранения имеют порт-драйвер. Например, SCSI порт драйвер выполняет
ввод/вывод через шину SCSI.
Мини
порт-драйвер обрабатывает специфичные операции порт-драйвера.
6.
Драйвер аппаратной шины находится в самом низу стека. Microsoft предоставляет эти
драйвера для всех шин, как часть операционной системы.
1.4 Архитектура
драйверов устройств хранения
Для
того, чтобы определить какой тип драйвера потребуется разработать, рассмотрим
более подробно структуру взаимодействие драйверов устройств хранения в цепочке
драйверов.
Классовые
и фильтр- драйвера для устройств хранения предоставляют интерфейс
взаимодействия между драйверами высокого уровня, расположенными в стеке над
ними, и порт-драйвером предоставляемым операционной системой.
Рис. 3 Архитектура драйверов устройств хранения
Запрос
ввода/вывода от приложений пользователя обрабатывается подсистемой
ввода/вывода. Подсистема формирует пакет запроса на ввод/вывод(IRP), который приходит
классовому драйверу устройства ввода вывода через один или несколько
промежуточных драйверов фильтров верхнего уровня(таких как например драйвер
файловой системы).
Классовый
драйвер ввода вывода дополняет полученный пакет IRP дополнительным SCSI блоком запроса (SRB) и посылает запрос
порт драйверу через фильтр драйверы нижнего уровня.
Порт-драйвер
полученные пакеты SRB преобразует к формату для передачи по аппаратной шине, и
посылает их адаптеру главной шины (HBA), через драйвер вводы вывода шины и один или несколько
фильтр драйверов.
Благодаря
тому, что виртуальный диск не является реальным физическим устройством, нам не
требуется производить доступ к аппаратной шине и формировать специальные пакеты
SRB.
Данные в оперативной памяти уже доступны на уровне классового драйвера;
нецелесообразно и не нужно передавать IRP пакеты на нижний уровень.
Таким
образом, требуется разработать классовый драйвер, в который будет происходить
вся обработка запросов и обращение к данным диска для нашего класса устройств
виртуального диска.
1.5 Выбор
файловой системы
Для
того, чтобы начать работу с диском, диск разбивается на разделы. Раздел — это
непрерывная часть физического диска, которую операционная система представляет
пользователю как логическое устройство (используются также названия логический
диск и логический раздел). Именно с логическими устройствами работает
пользователь, обращаясь к ним по символьным именам, используя, например,
обозначения А, В, С. В частном случае, когда все дисковое пространство
охватывается одним разделом, логическое устройство представляет физическое
устройство в целом.
Исходя
из того, что объем диска ограничен объемом имеющейся свободной оперативной
памяти, то нецелесообразно разбивать виртуальный диск на несколько разделом.
Создание одного логического раздела позволяет упростить логику устройства и
сэкономить место, которое используется для описания каждого раздела.
На
каждом логическом устройстве может создаваться только одна файловая система.
Для дисковых накопителей Windows поддерживает файловые системы FAT и NTFS.
При
выборе файловой системы виртуального диска учитывались следующие положения.
Рамдиск не предназначен для долговременного хранения данных и при отключении
питания все данные теряются. Как следствие проблема фрагментации файлов теряет
свою актуальность и остроту. Объем диска ограничен и требуется, чтобы файловая
система под файлы метаданных использовала минимум свободного места.
Отличительные
свойства NTFS[2], то что она ориентирована для поддержки больших файлов,
восстанавливаемости после сбоев и отказов программ и аппаратуры управления
дисками – все это приводит к значительному размеру метаданных. Поэтому
минимальный размер тома равен 10 Мб, а на практике использование NTFS оправдано для
логических дисков от 400 Мб.
Файловая
система FAT относится к ФС с глобальным индексом, и поэтому метаданные
состоят из метки тома, глобальной таблицы диска и коневого каталога. Все
остальное место свободное и отводится под создаваемые файлы и каталоги. Тот
факт , что при каждой операции чтения/ записи идет обращение к таблице FAT не влияет на
производительность, т.к. время доступа для оперативной памяти ничтожно мало по
сравнению с жестким диском. Также существует несколько версия FAT: 12, 16 и 32. FAT 12 можно использовать
на маленьких дисках от 1 Мб. Использование FAT 32 в основном
предназначена для томов объемом в несколько Гб, и минимальный размер тома
ограничен 512 Мб, этому она не подходит для виртуального диска.
Таким образом для
нашего виртуального диска будет использоваться файловые системы FAT
12(когда объем диска не превышает 16 Мб) и FAT
16.
2. КОНСТРУКТОРСКИЙ
РАЗДЕЛ
2.1 Структура
классового драйвера
Драйвер
реализуется как набор процедур, каждая из которых предназначена для реализации
отдельного типа обращений к драйверу со стороны диспетчера ввода/вывода.
Процедуры, которые необходимо поддерживать классовому драйверу приведены в таблице
1.
Таблица 1
Процедура
Описание
DriverEntry
Выполняется
при загрузке драйвера операционной системой. Здесь драйвер регистрирует свои
остальные точки входа и выполняет свою общую инициализацию.
Unload
Вызывается
при выгрузке драйвера. Здесь необходимо освободить все затребованные ресурсы.
AddDevice
Здесь
создаётся объект-устройство, соответствующий полученному уведомлению от
менеджера устройств, и выполняется инициализация данных, специфичных для
данного устройства.
DispatchPnP
Выполняет
обработку специфичных Plug&Play запросов , таких как инициализация
устройства, таких как инициализация устройства, остановка, удаление
устройства и обрабатывать остальные запроса
DispatchPower
Выполняет
обработку запросов по управлению питанием устройства
DispatchSystemControl
Обрабатывает
запросы от подсистемы инструментария Windows (WMI)
DispatchCreate,
DispatchClose,
DispatchRead,
DispatchWrite
Обслуживают
запросы на чтение запись данных для устройства.
2.2 Организация
внутреннего хранения данных диска
Определим,
какую структуру данных оптимально использовать для хранения данных на диске.
Самый простой способ – разместить все данные в одномерном массиве байт, и
адресовать данные с помощью смещения от начала диска. Следует учесть тот факт,
что при запросах на чтение /запись данных, в качестве параметров указывается :
смещение в байтах с которого начинается передача данных (которое и служит
индексом первого байта для чтения) и количество байт для передачи.
Естественно,
что применение более сложных структур организации данных(двумерный массив,
списки и т.д.) приведет только к дополнительным накладным расходам по
преобразованию адресации данных.
Таким
образом, виртуальный диск будет храниться в памяти как одномерный массив байт
заданного размера. Для чтения данных, информация копируется из области памяти
образа диска в буфер инициатора запроса. Для записи - копирование происходит в
обратном направлении.
Следующим
шагом выберем, каким образом мы зарезервируем память для диска. Для
резервирования памяти в режиме ядра Windows предоставляет специальные системные вызовы. Существуют
следующие типы резервируемой памяти:
·
Страничная память(Paged memory) – виртуальная память, которая может быть перемещена
системой на жесткий диск в любой момент времени. В случае, если приложение
обратиться к отсутствующей в физической памяти области своей виртуальной
памяти, то возникает исключение по отсутствию страницы в памяти. В результате
системный обработчик перехватывает это исключение и загружает в физическую
память отсутствующую страницу. Однако при работе в режиме ядра, когда уровень приоритета
равен или выше DISPATCH_LEVEL, это исключение создаст ситуацию, когда системный обработчик
не может подгрузить страницу т.к. его IRQL меньше текущего.
·
Нестраничная память(Nonpaged memory) – эта память никогда не может быть перемещена системой на
жесткий диск и всегда остается в физической оперативной памяти. В результате,
обращаться к этой памяти можно при любом уровне IRQL. Объем данной памяти
ограничен даже при наличии достаточного объема физической памяти в Windows 2000 660 Мбайтами, а в
Windows XP 1300 Мбайтами.
Поскольку
обращение к образу диска происходит при уровне привилегий PASSIVE_LEVEL или DISPATCH_LEVEL, но в особых ситуациях
этот приоритет может быть выше. Поэтому во избежание возникновения ситуации,
когда страница отсутствует в памяти, и мы ее не сможем подгрузить – виртуальный
диск будет использовать нестраничную память. Дополнительно мы увеличим
быстродействие драйвера, т.к. уже не требуется подгружать отсутствующие
страницы с жесткого диска.
2.3 Доступ к
передаваемым данным
Рассмотрим,
каким образом драйвер может получить доступ к передаваемым данным.
Пользовательский процесс, вызывая функцию API (например, WriteFile), передаёт ей
указатель на буфер, в котором размещается записываемая информация. Однако,
передаваемый виртуальный адрес действительно будет указывать на записываемую
информацию только в контексте данного процесса. Операции же ввода/вывода
в драйвере происходят в контексте произвольного процесса. Из-за смены
таблиц страничного преобразования при переключении процессов использовать
переданный виртуальный адрес в произвольном контексте совершенно
недопустимо.
Для
операций ввода/вывода архитектура операционной предусматривает два специальных
метода передачи буфера данных, принадлежащего инициатору запроса:
·
Буферизированный (buffered I/O). Драйверу в пакете запроса ввода/вывода передаётся
указатель на копию исходного буфера в невыгружаемой памяти (поле
AssociatedIrp.SystemBuffer). Подсистема ввода/вывода отвечает за точное
соответствие содержимого этого буфера передаваемым данным. Этот метод, в
основном, используется для устройств, не предающих больших объёмов данных:
манипуляторы, низкоскоростные коммуникационные линии и т. п.
·
Прямой (direct I/O). В этом случае система блокирует страницы пользовательского
буфера, чтобы они не были выгружены на диск во время передачи данных.
Расположение пользовательского буфера в физической памяти описывается
структурой MDL (Memory Descriptor List), доступной в пакете запроса ввода/вывода через поле
MdlAddress. По этой структуре необходимо настроить системную таблицу страниц на
тот же буфер в физической памяти. Это осуществляется функцией MmGetSystem,
которая возвращает виртуальный адрес буфера в системной области памяти. Данный
метод эффективен с большими объёмами данных, например, при работе с дисковыми накопителями.
Для
драйвера виртуального диска будет использоваться прямой метод, который
обеспечивает возможность обмена большими данными и высокую скорость передачи.
2.4 Обработка
запросов Plug and Play
В
процессе работы диспетчер ввода вывода может динамически управлять состоянием
устройства: запускать, останавливать и выгружать. Реализация этих функций
драйвером устройства хранения обеспечивает при обработке специфичных PnP IRP пакетов. В таблице 2
приведены описания IRP пакетов, которые должны поддерживаться.
Таблица 2
IRP_MN_Xxx
Описание
IRP_MN_START_DEVICE
Инициализация
устройства с заданными ресурсами
IRP_MN_QUERY_STOP_DEVICE
Проверка
осуществимости остановки устройства для перераспределения ресурсов
IRP_MN_STOP_DEVICE
Остановка
устройства с потенциальной возможность. перезапуска или удаления из системы
IRP_MN_CANCEL_STOP_DEVICE
Уведомляет,
что предыдущий запрос на остановку не получит дальнейшего развития
IRP_MN_QUERY_REMOVE_DEVICE
Проверка
осуществимости безопасного удаления устройства
IRP_MN_REMOVE_DEVICE
Выполнить
безопасное удаление устройства
IRP_MN_CANCEL_REMOVE_DEVICE
Уведомляет,
что предыдущий запрос на удаление не получит дальнейшего развития
IRP_MN_SURPRISE_REMOVAL
Уведомляет,
что устройство было удалено без предварительного предупреждения
Применительно
к виртуальному диску большая часть этих сообщений не влечет каких-либо
дополнительных действий, т.к. у рамдиска нет дополнительных буферов, данные с
которые должны быть записаны на диск при остановке устройства, или поддержки
функций управления электропитанием устройства. Поэтому для виртуального диска
существует внутренняя переменная, которая хранит текущее состояние устройства.
Основным
состоянием диска будет WORKING – когда диск находится в рабочем состоянии. Остальные
состояния(приведенные в таблице 3) носят информативный характер, чтобы узнать
текущий режим работы диска. При операциях доступа к устройству будет
осуществляться проверка состояния WORKING, и только при нем доступ к диску разрешен.
Таблица 3
Состояние
Значение
STOPPED
Устройство
остановлено
WORKING
Устройство
работает
PENDINGSTOP
Устройство
ожидает остановки
PENDINGREMOVE
Устройство
ожидает удаления
SURPRISEREMOVED
Устройство
удалено без предварительного предупреждения
REMOVED
Устройство
удалено
UNKNOWN
Устройство
в ошибочном состоянии
2.5 Обработка
расширенных запросов
Для
управления сами устройством диспетчер ввода вывода посылает драйверу пакет с
кодом управления вводом/выводом(IOCTL). Какие именно коды управления будет посылаться зависит от
типа устройства. О том какие коды должен обязательно поддерживать классовый
драйвер точно ничего не сказано. Поэтому приведенный далее список кодов управления
был получен в процессе отладки драйвера, когда записывались получаемые
драйвером IOCTL и выяснялась их функция.
Список
кодов управления:
·
IOCTL_DISK_GET_PARTITION_INFO – сообщить о типе, размере и природе раздела диска.
·
IOCTL_DISK_GET_MEDIA_TYPES,
IOCTL_DISK_GET_DRIVE_GEOMETRY – сообщить о
геометрии диска (количество цилиндров, дорожек, секторов)
·
IOCTL_DISK_IS_WRITABLE – проверка можно ли на диск записывать данные
·
IOCTL_DISK_SET_PARTITION_INFO
изменить тип раздела
·
IOCTL_MOUNTMGR_QUERY_POINTS – сообщить о символической ссылке для тома
·
IOCTL_MOUNTDEV_QUERY_DEVICE_NAME – сообщить об имени устройства
·
IOCTL_DISK_CHECK_VERIFY – проверить, сменился ли носитель (для съемных дисков)
2.6 Расчет
геометрии диска
При
получении расширенного запроса IOCTL_DISK_GET_DRIVE_GEOMETRY или IOCTL_DISK_GET_MEDIA_TYPES требуется инициатору запроса передать информацию о геометрии
диска. Драйвер виртуального диска заполняет стандартную структуру Windows DISK_GEOMETRY, где
указываются следующие параметры:
Cylinders – количество
цилиндров
TracksPerCylinder
количество дорожек на цилиндр
SectorsPerTrack
количество секторов на дорожку
BytesPerSector
размер сектора в байтах
MediaType – тип носителя
Тип
носителя для жестких дисков должен быть равен FixedMedia.
Размер
сектора примем равным 512 байтам, это стандарт де-факто для почти всех современных
дисковых накопителей.
Если
количество цилиндров превышает 1023(такое ограничение было введено для
совместимости со старыми версиями BIOS), то количество секторов на дорожку увеличивается вдвое и
будет равно 64.
Таким
образом максимальный размер рамдиска, для которого число цилиндров не превышает
1023 равен:
3.1 Выбор и
обоснование языка и среды программирования
Для
разработки драйвера виртуального диска применялся пакет DDK (Driver Development Kit), который включает в
себя все необходимые заголовочные файлы и библиотеки. Пакет DDK ориентирован на язык
С. Драйвер разрабатывался на Windows 2003 Server DDK, т.к. она включает в себя как средства для разработки
драйверов для Windows 2003 Server, так и последние версии средств для разработки драйверов ОС Windows 2000 и XP.
В
качестве интегрированной среды разработки применялся Microsoft Visual C++ 6. Он предоставляет
удобный интерфейс для написания программного кода на C, и позволяет
компилировать его используя библиотеки пакета DDK.
Для
отладки работы драйвера применялись программы DebugView и DriverMonitor из пакета NuMega Driver Studio 2.0, которые позволяют
просматривать отладочные сообщения драйвера.
3.2 Структуры
данных классового драйвера
Параметры
диска и его состояние хранятся в расширении устройства — структуре DEVICE_EXTENSION. Она всегда
доступная через объект-устройство (DEVICE_OBJECT, поле DeviceExtension), она реализует наиболее удобный и рекомендованный Microsoft способ хранения
таких данных. Структуру расширения определяет разработчик драйвера. Мы ее
определим так:
typedef
struct _DEVICE_EXTENSION
{
PDEVICE_OBJECT DeviceObject;
PDEVICE_OBJECT LowerDeviceObject;
DEVICE_STATE
DevState; // Текущее состояние
устройство
IO_REMOVE_LOCK
RemoveLock; // Блокировка
устройства
Поля DeviceObject и LowerDeviceObject
облегчают доступ к объектам-устройствам из различных участков драйвера. Они
потребуются в дальнейшем. Поле RemoveLock служит для блокировки удаления
устройства. DiskImage – содержит указатель на одномерный массив – образ диска.
Поле DiskGeometry хранит геометрию диска. DevState содержит текущее состояние
устройства. SymbolicLink – символическая ссылка, которая служит для обращения к
драйверу.
Поле
DiskRegInfo описывает параметры, которые расположены в реестре и используются
драйвером для создания диска:
typedef struct
_DISK_INFO
{
ULONG DiskSize; //
Размер диска в байтах
ULONG
RootDirEntries; // Количество
элементов в корне
Когда
система производит удаление нижестоящего устройства, она посылает ему PnP-запрос с функцией
IRP_MN_REMOVE_DEVICE. Если же в этот момент драйвер чем-то занят, то он должен
защитить своё устройство от преждевременной выгрузки из памяти. Для этого в
системе предусмотрен объект «блокировка выгрузки» (remove lock). Он может быть
«захвачен» и «освобождён». Внутри этого объекта имеется счётчик. Этот счётчик
устанавливается в ноль при инициализации объекта (функция IoInitializeRemoveLock).
Счётчик увеличивается на единицу при захвате объекта (IoAcquireRemoveLock)
перед обработкой запроса и уменьшается при освобождении (IoReleaseRemoveLock).
Обработчик
PnP-запроса
с функцией IRP_MN_REMOVE_DEVICE гарантированно исполняется в контексте
системного процесса. В этом обработчике перед удалением
объекта-устройства над блокировкой выполняется действие «освободить и ждать»
(IoReleaseRemoveLockAndWait). Оно уменьшает счётчик на единицу и блокирует системный
процесс, пока счётчик не станет равным нулю. Следовательно, пока драйвер
обрабатывает хотя бы один запрос к устройству, это устройство не будет удалено.
3.4 Процедуры
драйвера виртуального диска
3.4.1 Инициализация драйвера
Начнём
с процедуры, вызываемой при инициализации драйвера — DriverEntry. Она определяется следующим образом:
Тип
NTSATUS, соответствующий возвращаемому значению, определяет тип ошибки. Многие
функции драйвера возвращают значение этого типа. Если работа проходит успешно,
результат принимает значение STATUS_SUCCESS.
В
данной процедуре необходимо сообщить системе остальные процедуры обработки
пакетов IRP:
Поскольку
нам нужно сохранить параметры о пути реестра и признак того, что
инициализировалось ли само устройство, то в структуре расширения драйвера
заведем следующие поля:
Массив
MajorFunction устанавливает соответствие между кодом типа запроса IRP_MJ_NNN и диспетчерской
функцией, которая его обрабатывает. Наш драйвер обрабатывает запросы на
открытие и закрытие устройства (RamDskCreateClose), чтение и запись
(RamDskReadWrite), расширенные запросы обработки IOCTL(RamDskIOCtl), запросы Plug and Play (RamDskDispatchPnp),
функции управления питанием (RamDskDispatchPower), запросы от подсистемы
инструментария Windows (RamDskDispatchSystemControl).
Сам
объект функционального устройства, который будет отвечать за наше устройство,
создается далее, когда система вызовет процедуру RamDskAddDevice. Но для его
создания необходимо узнать путь реестра с параметрами драйвера, а также
необходим признак, что устройство добавлено. Данные параметры будем хранить в
структуре расширения драйвера. Поля и размер структуры выбираются
разработчиком, для хранения нужных ему данных. Объявим структуру расширения драйвера следующим образом:
В
процедуре DriverEntry мы создаем экземпляр структуры и инициализируем ее поля:
копируем путь реестра RegistryPath, который передается в параметрах процедуры,
и сбрасываем признак DeviceInitialized того, что устройство добавлено.
Процедура
RamDskAddDevice будет вызвана, когда будет обнаружено устройство. Ее действие
заключается в создании объекта устройства диска и создании символических ссылок
для него.
Проверим,
может быть уже создан объект устройство, а процедура RamDskAddDevice вызывается
повторно. Для этого проанализируем признак DeviceInitialized расширения
драйвера:
// Может присутствоать
только одно устройство, если повторно вызывается запрос
// AddDevice
для следующего устройства, отклоним запрос
Если
процедура RamDskAddDevice вызвана в первый раз, создаем объект устройство с
типом дисковый накопитель (FILE_DEVICE_DISK) и инициализируем расширение
устройства DEVICE_EXTENSION:
status =
IoCreateDevice(
DriverObject,
sizeof(DEVICE_EXTENSION),
&uniDeviceName,
FILE_DEVICE_DISK
,
(FILE_DEVICE_SECURE_OPEN),
FALSE,
&functionDeviceObject);
Если создание
прошло успешно, мы читаем параметры диска из реестра и устанавливаем начальное
состояние устройства остановлено (STOPPED) в расширении устройства, а также
устанавливаем флаги сигнализирующие о том, что мы используем прямой метод
передачи данных (DO_DIRECT_IO) и процедуры драйвера находятся в страничной
памяти(DO_POWER_PAGABLE):
devExt->DevState
= STOPPED; // состояние
остановлен
// Установим флаги
functionDeviceObject->Flags
|= DO_POWER_PAGABLE;
functionDeviceObject->Flags
|= DO_DIRECT_IO;
// Резервируем память
под образ диска
devExt->DiskImage
=
ExAllocatePoolWithTag
( NonPagedPool,
devExt->DiskRegInfo.DiskSize,
RAMDSK_TAG_DISK
) ;
Последней
операцией мы резервируем память в нестраничном пуле под образ диска(размер
образа devExt->DiskRegInfo.DiskSize байт).
Т.к.
выделенная память может содержать произвольные данные, необходимо записать
метаданные файловой системы и подсчитать геометрию диска, что делается вызовом
RamDskFormatDisk(
functionDeviceObject );
Создаем символическую
ссылку, которая ассоциирует с нашим драйвером указанную в параметрах букву
диска и имеет формат «\DosDevices\X:»,
где «Х» - буква диска.
// Создаем символьную
ссылку в пространстве имен Win32
status =
IoCreateSymbolicLink( &devExt->SymbolicLink, &uniDeviceName );
Если
символическая ссылка создана без ошибок, то установим в расширении устройства
флаг, что символическая ссылка создана и устройство подключается к стеку
устройств:. При выгрузке драйвера если флаг установлен будет удаляться и
символическая ссылка драйвера:
devExt->Flags
|= FLAG_LINK_CREATED;
devExt->LowerDeviceObject
= // подключение к стеку устройств
Последней
операцией устанавливается признак DeviceInitialized расширения драйвера, что
устройство добавлено.
3.4.2 Обработка запросов записи/чтения
Когда
пользовательское приложение или компонент ядра пытается открыть драйвер
виртуального диска для записи или чтения, то диспетчер ввода вывода формирует
запросы IRP с кодом IRP_MJ_CREATE. При закрытии обрабатывается запрос IRP_MJ_CLOSE. Особых действий в
обработке этих пакетов не требуется, поэтому драйвер возвращает успешный статус
завершения. В данном драйвере оба пакета обрабатываются в одной процедуре
RamDskCreateClose:
В
качестве проверки, если мы получили пакет с другим кодом, то возвратим
ошибочный статус.
Обработка
запросов на запись или чтение , также реализуется в одной процедуре, т.к.
отличия в действиях заключаются в направлении копирования данных: из буфера в
образ диска или наоборот. Для выполнения запроса, требуется, чтобы виртуальный
диск находился в состоянии WORKING:
if
( devExt->DevState
!= WORKING )
{
// Устройство не готово
или удалено, отменить любые запросы
DBGPRINT(
DBG_COMP_READ, DBG_LEVEL_WARN, ("Device not ready\n" ) );
status =
STATUS_INVALID_DEVICE_STATE;
COMPLETE_REQUEST(
Irp, status, information );
}
Далее
требуется проверить, что переданные параметры(начальное смещение
Parameters.Read.ByteOffset и количество байт Parameters.Read.Length) не выходят
за границы нашего диска, чтобы обеспечить корректность операции чтения/записи.
Дополнительно количество байт должно быть кратно размеру сектора на диске:
Если
какой-либо из параметров не верен, то статус обработки запроса будет равен
STATUS_INVALID_PARAMETER (неверные параметры).
Драйвер
использует прямой метод передачи буфера данных, нам передается MDL список для буфера
пользователя в параметре Irp->MdlAddress, который мы отображаем в адресное
пространство ядра с помощью функции MmGetSystemAddressForMdlSafe:
Поскольку
наш драйвер обращается с виртуальным диском, при следующих запросах выполнять
каких-либо действий не требуется, просто сообщить, что запрос успешно
обработан. Это запросы:
case
IOCTL_DISK_IS_WRITABLE://проверка
можно ли на диск записывать данные
Запрос
IOCTL_DISK_GET_PARTITION_INFO, требует сообщить информацию
о разделах на диске. На рамдиске имеется один раздел. Результатом обработки запроса
будет структура PARTITION_INFORMATION
typedef struct
_PARTITION_INFORMATION
{
LARGE_INTEGER ;
//смещение, с которого начинается раздел
LARGE_INTEGER
;//размер
раздела
DWORD
;
//скрытых секторов
DWORD
;
//порядковый номер раздела
BYTE
;
//тип раздела
BOOLEAN
;
//TRUE - раздел является загрузочным
BOOLEAN
;
//распознан ли раздел
BOOLEAN
;
//TRUE – изменились параметры раздела
}
Тип
раздела для виртуального диска может быть PARTITION_FAT_12 или
PARTITION_FAT_16(он определяется при инициализации и хранится в расширении
драйвера). Остальные поля заполняются следующим образом:
Запросы IOCTL_DISK_GET_MEDIA_TYPES,
IOCTL_DISK_GET_DRIVE
_GEOMETRY нужны для поучения
информации о геометрии диска, для этого надо заполнить структуру DISK_GEOMETRY
описанную в разделе 2.7.:
typedef struct
_DISK_GEOMETRY {
LARGE_INTEGER ;
//количество цилиндров
MEDIA_TYPE
;
//тип носителя
ULONG
;
//количество дорожек на цилиндр
ULONG
;
//количество секторов на дорожку
ULONG
;
//размер сектора в байтах
} DISK_GEOMETRY,
*PDISK_GEOMETRY;
Все
остальные параметры заранее рассчитаны и определены при инициализации, и нужно
только эту структуру скопировать из расширения устройства:
Мы
пересылаем пакет драйверу, который находится ниже при помощи вызова функции
IoCallDriver. Если нижние драйвер находится в состоянии обработки
(STATUS_PENDING), то просто усыплем наш драйвер с помощью функции KeWaitForSingleObject
и просыпаемся по событию завершения операции нижним драйвером.
При остановке
устройства(IRP_MN_STOP_DEVICE), просто пересылаем запрос нижнему драйверу и
устанавливаем состояние устройства STOPPED:
case
IRP_MN_STOP_DEVICE:
{
devExt->DevState
= STOPPED;
Irp->IoStatus.Status
= STATUS_SUCCESS;
IoSkipCurrentIrpStackLocation(
Irp );
status =
IoCallDriver( devExt->LowerDeviceObject, Irp );
break;
}
При обработке запросов IRP_MN_SURPRISE_REMOVAL, IRP_MN_QUERY_ REMOVE_DEVICE,
IRP_MN_QUERY_STOP_DEVICE, производятся аналогичные действия, с установкой соответствующего статуса:
case
IRP_MN_QUERY_STOP_DEVICE:
{
devExt->DevState
= PENDINGSTOP;
Irp->IoStatus.Status
= STATUS_SUCCESS;
IoSkipCurrentIrpStackLocation(
Irp );
status =
IoCallDriver( devExt->LowerDeviceObject, Irp );
break;
}
case
IRP_MN_STOP_DEVICE:
{
devExt->DevState
= STOPPED;
Irp->IoStatus.Status
= STATUS_SUCCESS;
IoSkipCurrentIrpStackLocation(
Irp );
status =
IoCallDriver( devExt->LowerDeviceObject, Irp );
break;
}
case IRP_MN_QUERY_REMOVE_DEVICE:
{
devExt->DevState
= PENDINGREMOVE;
Irp->IoStatus.Status
= STATUS_SUCCESS;
IoSkipCurrentIrpStackLocation(
Irp );
status =
IoCallDriver( devExt->LowerDeviceObject, Irp );
break;
}
case
IRP_MN_SURPRISE_REMOVAL:
{
devExt->DevState
= SURPRISEREMOVED;
Irp->IoStatus.Status
= STATUS_SUCCESS;
IoSkipCurrentIrpStackLocation(
Irp );
status =
IoCallDriver( devExt->LowerDeviceObject, Irp );
break;
}
При
удалении устройства IRP_MN_REMOVE_DEVICE, мы вызываем функцию
RamDskRemoveDevice, которая освобождает выделенную при инициализации память,
символическую ссылку (она будет описана далее):
Выгрузка
драйвера состоит из двух частей: удаление объекта устройства и выгрузка самого
драйвера.
При
удалении объекта устройства, менеджер ввода вывода посылает IRP пакет
IRP_MN_REMOVE_DEVICE, тогда вызывается процедура RamDskRemove Device. IRP пакет передаем дальше
драйверу нижнего уровня с помощью функции IoCallDriver. Статус драйвера
устанавливается в состояние устройство удалено, чтобы новые запросы не могли
быть выполнены. С помощью функции IoReleaseRemoveLockAndWait ждем пока текущие
запросы не обработаются.
Irp->IoStatus.Status
= STATUS_SUCCESS;
IoSkipCurrentIrpStackLocation(
Irp );
status =
IoCallDriver( devExt->LowerDeviceObject, Irp );
Перед
удалением расширения объекта устройства в процедуре RamDskCleanUp освобождается
память под образ диска, удаляется символическая ссылка и сам функциональный
объект устройства. Также далее сбрасывается признак DeviceInitialized инициализации
устройства.
При выгрузке самого
драйвера вызывается процедура RamDskUnload, которая должна обязательно
присутствовать, чтобы драйвер мог выгружаться.
В
данной процедуре удаляется уже расширение драйвера.
3.5 Программа
настройки параметров виртуального диска
Чтобы
предоставить пользователю возможность настроить параметры виртуального диска
разработана программа RamDskConfig.exe. Общий вид программы изображен на рис. 4
Рис.
4 Программа настройки параметров виртуального диска
Программа позволят назначать для рамдиска букву диска и его
размер. Чтобы обеспечить корректность работы драйвера, выбор буквы диска
осуществляет из имеющихся букв, которые не назначены другим дискам в системе.
Размер
диска также задается списком предопределенных значений, но пользователь может
сам ввести требуемый размер диска в поле ввода.
Для
сохранения изменений, пользователю требуется нажать кнопку «ОК». При этом
происходит рестарт драйвера с новыми параметрами. Чтобы не сохранять изменения,
нужно нажать кнопку «Отмена».
Установка
драйвера производится с использованием установочного файла RAMDsk.inf, поставляемого вместе
с драйвером. Для установки фильтра необходимо выполнить следующие действия:
1.
Откройте окно Мастера оборудования (например, из вкладки
Оборудование окна Свойства системы).
2.
Выберите пункт Добавить/провести диагностику устройства
3.
В появившемся списке устройств выберите Добавление нового
устройства
4.
Далее укажите Нет, выбрать оборудование из списка
5.
Укажите тип устройства Другие устройства
6.
Нажмите на кнопку Установить с диска и вставьте дискету с
файлами драйвера-фильтра в дисковод.
7.
После этого в появившемся окне выберите путь к дискете и
нажмите ОК.
8.
Нажмите кнопку Далее для начала установки.
9.
Если в ходе установки система выдаст предупреждение о том,
что устанавливаемый драйвер не имеет цифровой подписи, нажмите кнопку Все равно
продолжить.
10.
После установки нажмите кнопку Готово для закрытия мастера
установки
11.
При необходимости настройки драйвера скопируйте файл
RamDskConfig.exe на диск.
Для
исследования временных характеристик использовалась программа HD TACH 2.70,
исследовались следующие характеристики:
·
скорость последовательного чтения
·
время доступа при случайном обращении
·
загрузка процессора при операциях записи/чтения
Эксперименты
проводились на компьютере Intel Pentium III 566 МГц, 196 Мб ОП, Windows 2000 Server, размер рамдиска 32
Мбайта.
4.2 Результаты
экспериментов
В
таблице 4 показаны результаты тестирования для рамдиска, для сравнения
приведены результаты для жесткого диска Segate Barracuda 4 7200
Таблица 4 Результаты тестирования
Характеристика
Рамдиск
Жесткий
диск
Скорость
последовательного чтения Мб/с
189,5
40,0
Время
доступа при случайном обращении, мс
0,0
15,2
Загрузка
ЦП
100%
10%
Как и
ожидалось, для виртуального диска характерен мгновенный доступ к любому сектору
на диске, в отличие от жестких дисков, время доступа которых зависит от
быстродействия механики.
Скорость
передачи данных для рамдиска также ограничивается скоростью обмена данными в
памяти (для данной платформы обмен данными с оперативной памятью равен 250
Мб/с).
Платой за такие высокие
показатели является полная загрузка процессора, т.к. он постоянно работает с
оперативной памятью.
ЗАКЛЮЧЕНИЕ
В
данной работе были исследованы вопросы, связанные с разработкой драйверов для
устройств хранения, способами хранения данных, управление работой устройства с
помощью команд PnP. Разработан классовый драйвер виртуального диска, который
полностью удовлетворяет всем указанным требованиям.
Работа
драйвера осуществляется корректно, рамдиск обеспечивает полную функциональность
обычного жесткого диска. На данном диске можно создавать, читать, записывать,
удалять файлы.
Драйвер
не влияет на работу других устройств, и не приводит к ощутимым задержкам с
работе системы.
Параметры
диска изменяются динамически и без необходимости перезагрузки системы.
Также драйвер в ходе тестирования
показал хорошие результаты производительности, которые ограничены
характеристиками оперативной памяти компьютера.