Периферийные устройства обычно имеют "на борту" регистры и диапазоны адресов памяти, при помощи которых реализуется интерфейс устройства с системой. Но добраться до них не так просто: процессор ведь физически использует другие механизмы для обращения к своим "родным" портам ввода-вывода и оперативной памяти. Для того, чтобы обратится к памяти и портам устройства, находящегося на локальной шине, процессор должен выполнить отображение (mapping) адресного пространства процессора и той шины, где находится наше устройство. В результате этой операции к участку памяти, физически находящийся в устройтсве, можно обращаться, как к участку оперативной памяти процессора. При таком обращении процессор переадресует запрос локальной шине. Но тут следует вспомнить об особенностях архитектуры Windows (да и практически любой современной ОС): ведь система поддерживает механизм виртуальной памяти! Пользовательские приложения теперь работают в своем адресном пространстве, а система, в том числе и драйвера, — в своем. Куда же будет отображена память устройства?
Ответ прост. Можно отобразить диапазон адресов устройства как на адресное пространство системы, так и на адресное пространство пользовательского процесса. Соответственно различаться будет и способ доступа к памяти устройства из приложения пользователя: в первом случае буфер с данными для записи или чтения будет передаваться драйверу из приложения, а в драйвере эти данные будут пересылаться устройству. Во втором случае приложение будет писать и читать данные в выделенный ему участок памяти, который находится в адресном пространстве процесса. Какой механизм выбрать — дело разработчика драйвера.
Объекты, представляющие адресное пространство периферийных устройств, представлены классами KPeripherialAdress, KIoRange, KMemoryRange, KIoregister, KMemoryRegister. KPeripherialAdress является базовым классом для большинства остальных классов управления диапазонами памяти и портов ввода-вывода. Сам класс KperipherialAdress в основном, не используется. Используются, в основном, следующие его подклассы:
• KIoRange — диапазон адресов ввода-вывода. Данный класс отображает диапазон адресов портов В/В из адресного пространства какой-либо из шин в адресное пространство процессора. При использовании класса KIoRange можно читать и записывать в порты 8, 16, и 32-битные значения.
• KIoRegister является альтернативным путем доступа к портам ввода-вывода. В виде экземпляра KIoRegister может быть пердставлен отдельный порт-ввода вывода в диапазоне адресов. Фактически, KIoRange — это несколько экземпляров класса KIoRegister, объединенных в массив. Создать экземпляр KioRegister можно, используя как стандартный конструктор, так и используя оператор [] класса KIoRange, например:
KIoRange m_range;
…
KIoRegister m_reg = m_range[6];
…
Применение KIoRegister упрощет процесс программирования и улучшает читабельность программы.
• KMemoryRange используется для отображения диапазона адресов памяти из адресного пространства шины в адресное пространство процессора (адресное пространство системы). После того, как память будет отображена, драйвер должен использовать методы доступа к памяти, позволяющие оперировать 8, 16 и 32– битными значениями.
• KMemoryRegister аналогичен KIoRegister, за исключением того, что в данном случае он представляет из себя отдельную ячейку памяти в адресном пространстве устройства.
• KMemoryToProcessMap используется для отображения диапазона адресов памяти шины в адресное пространство пользовательского процесса. Это может оказаться очень удобным: пользователь может напрямую общаться с памятью устроства в программе, как с обычным буфером. Впрочем, такое отображение следует применять с большой осторожностью: возможна ситуация, когда пользователь запустит несколько экземпляров программы, и все они начнут работать с памятью устройства одновременно. Вряд ли стоит объяснять, к чему это может привести.
Стоит отметить, что немалая часть устройств могут общаються со своей памятью только словами. Длина слова зависит от устройства, и может колебаться в широких пределах. Обычно для PCI-устройств — 32 бит.
В документации настоятельно рекомендуется использовать только эти классы для управления оборудованием. Это связано с возможной переносимостью драйвера на другие платформы. При использовании этих классов, которые, в свою очередь, используют функции DDK для доступа к оборудованию, процесс портирования пройдет безболезненно, т.к. для доступа к устройству будет использован HAL. Если же программист будет пытаться управлять устройствами самостоятельно, то драйвер придется переписывать при переносе на другую платформу.
Есть еще одна причина, по которой стоит использовать эти классы: ведь с ними разрабатывать драйвер намного проще!