
Основы ядерной архитектуры ОС Windows.pptx
- Количество слайдов: 94
Основы ядерной архитектуры ОС Windows
Применение знаний ядра ОС • Средства защиты информации – системы шифрования сетевого трафика, защита данных на диске (виртуальные диски, фильтрдрайверы, защищенные файловые системы) • Вредоносное ПО • Средства анализа в самом широком смысле (антивирусы, отладчики, системные утилиты) • Знание ядерной архитектуры важно для аналитика программного обеспечения
Применение знаний при разработке инфраструктуры анализа бинарного кода • Драйвер получения информации: список процессов, модулей и копии адресных пространств процессов • Алгоритмы в составе инфраструктуры анализа, позволяющие выявлять: переключение процессов/потоков/адресных пространств, обмен данными между адресными пространствами, механизмы межпоточной синхронизации и др.
Основные понятия • Процессы, потоки и адресные пространства в ОС Windows: взаимосвязь с процессорной архитектурой • Архитектура процессора в защищенном режиме предоставляет для использования разработчикам ОС некоторые механизмы: • Защита памяти ( «кольца защиты» ) • Сегментация • Cтраничная организация памяти • Многозадачность (task switching) • особенности использования этих процессорных механизмов во многом определяют архитектуру ОС, в особенности рассматриваемые понятия: термины процесс, поток и адресное пространство имеют смысл только в рамках конкретной ОС
Защита памяти по привилегиям ( «кольца защиты» ) 3 Передача управления User mode 2 1 0 Доступ к данным Kernel mode Важной особенностью реализации механизма колец защиты является то, что для каждого кольца защиты хранится своя копия стековых регистров (ESP), и при переключении кольца защиты аппаратура процессора осуществляет замену текущего значения ESP на сохраненную копию, соответствующую новому кольцу защиты.
Особенности организации памяти в Windows на примере 32 -разрядной адресации Вирт. адрес
ОС NT, хотя и использует селекторы, но использует их в минимальной степени. NT реализует плоскую 32 -разрядную модель памяти с размером линейного адресного пространства 4 Гб (232 байт). В NT определено 11 селекторов, из которых нас будут интересовать всего 4: Селектор Hex (bin) Назначение база предел DPL тип (RPL) 08 (001000) Code 32 (kernel) 0000 FFFF 0 (kernel) RE 10 (010000) Data 32 (kernel) 0000 FFFF 0 (kernel) RW 1 b (011011) Code 32 (user) 0000 FFFF 3 (user) RE 23 (100011) Data 32 (user) 0000 FFFF 3 (user) RW Эти 4 селектора позволяют адресовать все 4 Гб линейного адресного пространства, разница только в режиме доступа. Первые 2 селектора имеют DPL=0 и используются драйверами и системными компонентами для доступа к системному коду, данным и стеку. Вторые 2 селектора используются кодом пользовательского режима для доступа к коду, данным и стеку пользовательского режима. Эти селекторы являются константами для ОС NT. Сегментное преобразование пары селектор: смещение дает 32 -битный линейный адрес, для всех рассматриваемых селекторов совпадающее со значением смещения виртуального адреса.
• Реальная защита памяти по привилегиям, как и реализация понятия адресное пространство, осуществляется посредством механизма страничной организации памяти: • Каждый контекст памяти (адресное пространство процесса) описывается своим каталогом страниц. Физический адрес текущего каталога страниц содержится в регистре CR 3 • элемент таблицы страниц содержит бит, указывающий на возможность доступа к странице из пользовательского режима (0 - доступ только ядру, 1 - доступ пользовательскому режиму). При этом все страницы доступны из режима ядра. Таким образом, механизм защиты страниц использует всего 2 уровня привилегий - 0 (ядро) и 3 (пользователь). С помощью этого механизма адресное пространство делится на 2 части: системную и пользовательскую. • элемент таблицы страниц содержит бит, указывающий на возможность записи в соответствующую страницу памяти (0 - доступ только на чтение, 1 - чтение/запись). Чтение возможно всегда, когда разрешен доступ по привилегиям. • В некоторых режимах элемент таблицы страниц содержит бит, определяющий соответствующую страницу как данные (запрет на выполнение кода с данной страницы)
Виртуальное адресное пространство процесса Границу между пользовательским и системным диапазоном можно сдвинуть Адресное пространство в 32 разрядном режиме 0 0 Код и данные прикладных программ Модули и объекты прикладной программы user 2 Гб Код и данные ядра, память устройств 4 Гб Взаимодействие между кодом пользовательского режима и ядром часто изображают так: user kernel 3 Гб 4 Гб user kernel Модули и объекты ядра
• Каталоги страниц всех процессов организованы так, что преобразование линейного в физический адрес для системного диапазона любого адресного пространства совпадают – диапазон ядра «общий» для всех адресных пространств Адресное пространство 1 Адресное пространство N Код и данные процесса 1 Код и данные процесса N 0 … user kernel ядро 4 Гб
• Каталоги страниц позволяют создавать «совместно используемые» как внутри одного, так и между несколькими адресными пространствами области памяти путем трансляции разных виртуальных адресов в одинаковые физические. Физическая память Адресное пространство 1 Адресное пространство 2
• Разновидность такого механизма – copy-on-write – используется для реализации механизма отображения файлов в память, через который в свою очередь подгружаются в адресное пространство все исполняемые модули. exe, . dll, . sys и т. п. Изначально все страницы адресных пространств, соответствующие исполняемому модулю, отображены в общий набор страниц физической памяти При попытке записи в какую-либо из таких страниц в одном из адресных пространств это адресное пространство вначале получает свою уникальную копию этой страницы в физической памяти, и только затем в эту копию осуществляется запись Физическая память Адресное пространство 1 Физическая память Адресное пространство 2 Адресное пространство 1 модификация Адресное пространство 2 Исходная копия страницы Модиф. копия страницы
Загрузка модулей в адресное пространство • • • Исполняемые файлы в ОС Windows имеют формат PE (Portable Executable). При запуске программы создается адресное пространство процесса, причем системный диапазон адр. простр. запускаемого процесса создается простым копированием части каталога страниц запускающего процесса. Затем в пользовательский диапазон памяти отображается исполняемый модуль (обычно. exe) в соответствии с предпочтительным адресом загрузки, прописанном в PE-заголовке (задается компилятором, для MS Visual. Studio обычно 0 x 400000). Далее в соответствии с таблицей импорта модуля начинается подгрузка (также через механизм отображения в память) всех динамических библиотек, с которыми связан текущий загружаемый модуль. Каждая такая библиотека (обычно. dll) также имеет формат PE, и для нее весь процесс повторяется заново. Если предпочтительный адрес загрузки уже занят, загрузчик перебазирует модуль на свободный участок виртуальной памяти. Кроме того, компилятор может создавать неперебазируемые модули, которые обязаны грузиться строго по указанному в заголовке адресу (обычно используется для некоторых модулей ядра). Сложность перебазирования определяется необходимостью загрузчику модифицировать инструкции, осуществляющие обращения по глобальным адресам (например при доступе к глобальным переменным) – компилятор формирует адреса таких переменных исходя из назначенного им же базового адреса загрузки, при перебазировании все такие адреса должны быть модифицированы. Список адресов кода, в которые требуется внести поправки при перебазировании, хранится в секции поправок (relocation table).
• • • При завершении загрузки модуля со всеми его зависимостями управление передается на функцию – точку входа в модуль, прописанную в его PEзаголовке (это касается всех загружаемых модулей). После отображения в память содержимое файла все еще не прочитано с диска. Такой файл начинает выступать в роли файла подкачки, а реальное чтение страниц произойдет при первом обращении к соответствующему адресу виртуальной памяти. Даже после выгрузки модуля ОС по возможности сохраняет информацию о выполненном ранее отображении файла и его физические страницы, для ускорения возможной повторной загрузки модуля. Перебазирование модуля – почти катастрофическая ситуация для ОС, поскольку вносимые поправки приводят к увеличению страниц физической памяти за счет срабатывания механизма copy-on-write. Кроме того, для перебазированного модуля становится затруднительным использовать оставшиеся от предыдущей загрузки страницы памяти. В ОС начиная с Vista появился механизм Address Space Layout Randomization (ASLR), предназначенный для усложнения атак со стороны вредоносного кода, направленных на прямой доступ к коду и данным (в т. ч. стеку) системных библиотек. В соответствии с этим механизмом, модули размещаются по «случайно» выбираемым адресам. В силу указанных выше проблем с перебазированием ОС не может допустить случайное размещение модуля в разных адресных пространствах. Вместо этого «случайные» адреса загрузки всех модулей вычисляются разово для всех модулей всех адресных пространств, и в ходе текущего сеанса работы ОС не пересчитываются.
Поддержка многозадачности • Механизм аппаратной поддержки многозадачности, предоставляемый архитектурой x 86 (task switching) – в Windows не используется при штатном функционировании ОС. Единственный случай его применения – при обработке BSOD (“синий экран”), т. е. при фатальном сбое в работе ядра ОС, для обработки которого на время вывода диагностических сообщений и формирования crashdump требуется работоспособная программно-аппаратная среда. В момент штатной работы ОС все происходит в рамках одного task’а, для обработки BSOD происходит переключение в другую задачу, выхода из нее уже нет. • Собственно многозадачность в терминах ОС Windows реализуется программно и будет рассмотрена позже.
Основные понятия: подведение итогов • • Процесс – соответствует некоторому запущенному в ОС приложению. Процесс можно рассматривать как контейнер ресурсов, выделяемых приложению для работы. Одним из важнейших ресурсов является адресное пространство. Каждому процессу выделяется свое собственное, единственное, адресное пространство. Код выполняется в рамках потоков
• • • Адресное пространство – диапазон виртуальных адресов, доступный процессу. Для некоторой части этих адресов средствами ОС при поддержке аппаратуры осуществляется трансляция в физические адреса, т. е. обращение к таким адресам будет либо немедленно перенаправлено в ОЗУ или память устройств, или приведет к задействованию механизма подкачки страниц с диска в ОЗУ с последующим доступом к ним в ОЗУ. Если аппаратные механизмы контроля доступа к памяти не позволяют осуществить доступ, либо трансляция памяти для адресуемой виртуальной ячейке отсутствует, процессор генерирует исключение. Адресное пространство разбито на 2 диапазона: по младшим адресам – диапазон памяти, доступный и коду пользовательского режима (user mode = 3 кольцо защиты), и коду ядра (kernel mode = 4 к. з. ; по старшим адресам – диапазон памяти ядра (доступен только коду ядре). Диапазон памяти ядра с точки трансляции адресов – «общий» для всех адресных пространств Вся память устройств, отображаемая на адресуемую физическую память, в виртуальном адресном пространстве отображается в диапазон памяти ядра. Доступ к портам в/в коду user mode также закрыт, в результате прикладной уровень не имеет возможности прямой работы с оборудованием.
TSS – Task State Segment Процессор определяет 5 структур для организации переключения задач (multitasking): • TSS - хранит состояние процессора • Дескриптор TSS (только в GDT, в LDT быть не может) – задает расположение TSS в памяти. Используется для вызова задачи с помощью специальных call/jmp • Task-gate descriptor/шлюз задачи (может находиться в GDT, LDT или IDT). Содержит значение селектора TSS. Используется для вызова задачи с помощью специальных call/jmp или int. • Task register TR • Флаг NT в регистре флагов EFLAGS В защищенном режиме TSS и Дескриптор TSS должны быть созданы хотя бы для одной задачи; селектор, определяющий Дескриптор TSS , д. б. загружен в регистр задач TR (инструкцией LTR)
Переключение на другую задачу осуществляется в одном из 4 случаев: • выполняется инструкция JMP или CALL на дескриптор TSS в GDT. • выполняется инструкция JMP или CALL на шлюз задачи в GDT или LDT. • происходит прерывание через шлюз задачи в IDT. • выполняется инструкция IRET при установленном флаге NT (Nested Task).
Обработка прерываний и исключений Два механизма прерывания работы программы • interrupt – асинхронное событие, как правило генерируемое устройством в/в. • exception – синхронное событие, генерируемое при попытке выполнения инструкции, в случае если состояние процессора соответствует одному или нескольким предопределенным условиям. Три класса исключений: • Faults –сохраняемый cs: eip указывает на адрес вызвавшей исключение инструкции. Позволяет рестартовать инструкцию, вызвавшую исключение. Пример – деление на 0 • Traps – исключение генерируется после выполнения вызвавшей его инструкции. сохраняемый cs: eip указывает на адрес следующей инструкции. Пример – отладочные прерывания. • Aborts – инструкция, вызвавшая исключение, не всегда определена и не м. б. рестартована. Три вида дескрипторов прерываний (хранятся в IDT): • Task Gate – переключает задачу • Interrupt Gate • Trap Gate Разница между Interrupt Gate и Trap Gate – при использовании Interrupt Gate очищается флаг IF (запрет маскируемых прерываний). При использовании Trap Gate флаг IF остается неизменным.
DPL задает ограничение на уровень привилегий кода, который пытается вызвать прерывание (код 3 -го кольца защиты не сможет вызвать прерывание с DPL=0, т. е. 0 кольца защиты). селектор в Interrupt и Trap Gate определяет, на каком кольце защиты будет работать обработчик прерывания. В ОС Windows все обработчики прерываний работают в 0 кольце защиты. Если при входе/выходе в обработчик прерывания меняется кольцо защиты работающего кода – меняется и стек (значение вершины стека берется из текущего TSS из поля, соответствующего новому номеру кольца защиты). В противном случае стек не меняется. Пример: любым системным отладчиком в пошаговом режиме пройти код, осуществляющий обнуление ESP, в двух случаях: в пользовательском режиме и в режиме ядра.
Вызов прерывания: без переключения колец защиты (стек не меняется) Рост адресов Свободная часть стека Error code EIP CS EFLAGS Занятая часть стека ESP после вызова прерывания ESP перед вызовом прерывания Вызов прерывания: переключение 3 ->0 кольцо защиты (смена стека) Свободная часть стека 3 Занятая часть стека 3 SS: ESP перед вызовом прерывания Рост адресов Свободная часть стека 0 Error code EIP CS EFLAGS ESP SS Занятая часть стека 0 ESP после вызова прерывания SS: ESP в процессе вызова прерывания после переключения кольца защиты – взяты из TSS
• • • Поток – единица исполнения в ОС Windows. Механизм вытесняющей многозадачности, реализуемый ОС, применяется именно к потокам. Программный код исполняется в потоке, изначально для процесса создается один поток, который затем может создать дополнительные. Все потоки одного процесса работают в рамках общего адресного пространства, но различаются контекстом – копией регистров, которая сохраняется перед приостановкой потока при исчерпании потоком кванта времени и подгружается при возобновлении потока при получении потоком очередного кванта времени. Контекст потока (thread context) – термин, часто используемый в ядерной документации. Состояние работы потока определяется принадлежащей ему копией процессорных регистров, в том числе отдельной копией регистра вершины стека для каждого кольца защиты. Для ядерных функций различают два вида контекстов: Arbitrary thread context – контекст случайного потока – как правило для функций, вызов которых произошел из-за возникновения асинхронного аппаратного события, прервав работу произвольного работающего потока (на рис. стрелка вверх) Non-arbitrary thread context – контекст определенного потока – как правило – контекст потока, в котором произошла передача управления в код ядра (на рис. стрелка вниз) Модули и объекты прикладной программы user kernel Модули и объекты ядра аппаратура
Система приоритетов • Windows NT имеет двухуровневую модель приоритетов: • Приоритеты высшего уровня (уровни запросов прерываний - Interrupt Re. Quest Level - IRQL) управляют аппаратными и программными прерываниями • приоритеты низшего уровня (приоритеты планирования) управляются планировщиком и управляют исполнением потоков
31 для 32 -разрядных ОС 15 для 64 -разрядных ОС Классы приоритетов в Win 32 API 0 Ядро возвращает управление коду user mode только при уровне IRQL PASSIVE_LEVEL Приоритет из этого диапазона можно назначить, только обладая полномочиями администратора
Приоритеты планирования Схема приоритетов, реализуемая ядром ОС: • При запуске потоку назначается базовый приоритет (б. п. ). • В процессе работы – у потока текущий приоритет (т. п. ) • Если базовый приоритет в динамическом диапазоне, исходно т. п. = б. п. , затем в процессе работы ОС может менять т. п. по правилу: б. п. ≤ т. п. ≤ 15 • Если базовый приоритет диапазоне realtime, то всегда т. п. = б. п. • Win 32 API скрывает эту схему: процессу назначается класс приоритета, а его потокам – относительный приоритет – значение ± 2 относительно класса приоритета потока. • Вся эта схема все равно сводится к системе приоритетов, реализуемой ядром ОС.
• • • Планировщик Windows реализует «карусельную» (round-robbin) схему переключения потоков по правилу: Если есть потоки из диапазона realtime – переключение по карусельной схеме будет только для потоков с максимальным значением realtime-приоритета. Любые потоки с более низким приоритетом никогда не исполнятся. Если потоков с realtime-приоритетом нет – карусельная схема для всех потоков с динамическим диапазоном приоритетов. Для исполнения выбираются потоки с максимальным т. п. , но у потоков с более низким т. п. он иногда инкрементируется, и в конце концов поток даже с самым низким базовым приоритетом в конце концов получит квант времени, после чего т. п. сбрасывается.
Уровни запросов прерываний (IRQL) • • • IRQL – способ управления маскированием прерываний на конкретном процессоре. По сути – это некоторое число, назначаемое конкретному процессору и обозначающее факт маскирования на нем некоторых прерываний. Конкретное значение IRQL из диапазона DIRQL (Device IRQL) также связывается с каждым обработчиком аппаратных прерываний. Если такое прерывание в данный момент не маскировано и оно доставлено процессору, ОС производит смену текущего IRQL процессора на IRQL прерывания, маскируя при этом все прерывания с IRQL ≤ нового IRQL процессора. При завершении обработчика прерывания значение IRQL процессора восстанавливается на старое. На уровне IRQL_PASSIVE_LEVEL разрешены все прерывания и работает механизм многозадачности. Код пользовательского режима работает только на PASSIVE_LEVEL (возможно за исключением функций, вызываемых механизмом APC на уровне APC_LEVEL – надо проверить) На уровне APC_LEVEL реализуется механизм асинхронного вызова процедур Asynchronous Procedure Call (APC), через который в частности ядро ОС реализует механизм обратного вызова функций (callback functions) прикладного уровня для уведомления прикладной программы ядром о возникновении некоторой ожидаемой ситуации. На этом уровне маскированы прерывания с IRQL= APC_LEVEL. Доступ к выгружаемой (paged) памяти разрешен.
• На уровне IRQL DISPATCH_LEVEL работают так называемые «отложенные процедуры» (Deferred Procedure Calls - DPC), поэтому этот уровень иногда называют DPC_LEVEL. DPC создаются обработчиками прерываний, и туда выносится весь «долгоиграющий» код обработки прерывания. Задачей обработчика прерывания является как можно быстрее зафиксировать факт возникновения прерывания и данные, необходимые для дальнейшей обработки в DPC. Вызовы DPC помещаются в очередь (своя для каждого процессора), обработка которых ведется в соответствии с собственной системой приоритетов (low-mediumhigh). Возврата к IRQL
• при работе процессора на уровне IRQL≥DISPATCH_LEVEL запрещено обращение к выгружаемой памяти – оно немедленно приведет к BSOD с ошибкой IRQL_NOT_LESS_OR_EQUAL. • На уровне IRQL>PASSIVE_LEVEL нельзя предпринимать никакие действия, которые могут привести к необходимости переключения потока (например использовать механизмы синхронизации на основе диспетчерских объектов (Dispatcher Objects) с помощью функции Wait. For(Single/Multiple)Objects с ненулевым временем ожидания) – замаскированы обслуживающие такое переключение прерывания уровня APC и DISPATCH_LEVEL • Уровни выше DISPATCH_LEVEL в основном соответствуют обработчикам аппаратных прерываний (DIRQLs). Маскирована часть прерываний, к возникновению которых могут приводить в том числе вызовы многих служебных ядерных функций, поэтому множество разрешенных к вызову служебных функций сильно ограничено. Серьезная обработка должна быть вынесена в DPC, а иногда – и в рабочие потоки с IRQL PASSIVE_LEVEL.
Пространство имен диспетчера объектов
• Имена ядерных объектов размещаются в едином пространстве имен. Подсистема Win 32 скрывает его наличие, транслируя обращения к именам объектов различных типов в обращения к конкретным директориям единого пространства имен, иногда полностью видоизменяя имя объекта. Например, обращение к файлу с именем c: foo. txt будет последовательно трансформировано следующим образом: • ? ? c: foo. txt • В директории ? ? будет найден элемент с именем С: , имеющий тип Symbolic. Link и значение DeviceHarddisk. Volume 1, в результате подстановки имя примет вид • DeviceHarddisk. Volume 1foo. txt • В директории Device будет найден элемент с типом Device, на этом разбор имени будет закончен, а соответствующему устройству будет отправлен запрос, частью которого будет оставшаяся неразобранной часть имени foo. txt – дальше с ней будет разбираться драйвер устройства.
Структура драйвера • Работа драйвера начинается с вызова функции Driver. Entry, соответствующей начальной точке входа в исполняемый модуль драйвера, которая прописана в его PE-заголовке. • Задача Driver. Entry – инициализация работы драйвера. Если она завершается успешно – функция завершается с кодом STATUS_SUCCESS (=0), после чего драйвер остается в памяти ядра. При любом другом коде завершения драйвер выгружается из памяти. • Работа драйвера после загрузки по сути определяется тем, какие callbackфункции зарегистрированы драйвером при инициализации: • Диспетчерские функции: массив точек входа Driver. Object->Major. Function[], вызываемых для обработки запросов, направляемых устройствам драйвера. • Обработчики прерываний (ISR), отложенных вызовов (DPC). • Подмена адресов функций обработки вызовов системных сервисов. • Функции, определяемые типом программного интерфейса задействованного при инициализации (например NDIS). • В ОС начиная с Vista – драйверы должны быть подписаны. Механизм проверки подписи может быть отключен при запуске ОС.
ограничения • Проблема при попытке написания кода драйвера на C++: функция main() – не первая функция с которой начинает работать код (инициализация памяти, вызов конструкторов глобальных экземпляров классов). • В коде драйвера – жесткие ограничения на библиотечные функции. Разрешенные к вызову – можно вызывать только функции ядра, некоторые типы драйверов имеют ограниченный набор функций, разрешенных к вызову, чтобы соответствовать некоторой спецификации – например NDIS. • Для всех доступных для вызова ядерных функций документация фиксирует жесткие условия, в которых они могут быть вызваны (контекст потока – случайный или нет, уровень IRQL, с которого может быть осуществлен вызов). Кроме того – ограничения на доступ к памяти (не всегда можно работать с paged-памятью) в зависимости от условий работы. • В ранних версиях ОС было ограничение (запрет) на работу с инструкциями FPU, возникающее вследствие несохранения контекста FPU при переключении user->kernel mode
Среда разработки • Пакет DDK/WDK, поддерживает все ОС начиная с Win. XP • Visual. Studio • Для интеграции WDK с Visual. Studio – сторонний продукт Visual. DDK, без него – утилита build, работающая в режиме командной строки • В пакете WDK для Windows 8 впервые официально заявлена возможность использования IDE (Integrated Development Environment) Visual. Studio, но с рядом ограничений (в частности не поддерживается разработка драйверов для XP, предположительно поддерживает только WDF/KMDF-драйверы, полнофункциональная поддержка всех типов драйверов – по прежнему официально только через build)
Многоуровневая модель сокрытия знаний о функционировании ядра ОС • WDF/KMDF – WDM – Legacy Drivers • Legacy Drivers – по сути способ написания ядерного драйвера «с нуля» , опираясь только на знания архитектуры ОС. По сути архитектура таких драйверов не претерпела изменений с момента появления ОС Windows NT 3. 51. Такой драйвер будет работать на всех современных Windows • WDM – Windows Driver Model – модель драйверов, изначально разработанная как способ реализации универсальных драйверов для кардинально различающихся линеек ОС: Win 9 x и Win. NT, путем предоставления драйверу для работы некоторого программного окружения, по сути соответствующего поведению ядра ОС линейки Windows NT. Данная архитектура стала стандартом для драйверов всех последующих ОС данной линейки • WDF/KMDF – фреймворк, программная оболочка, скрывающая реальное устройство ядра ОС для облегчения разработки драйверов, но может стать «тормозом» при разработке «продвинутых» драйверов
Интеграции WDK с Visual. Studio –Visual. DDK
Код простейшего драйвера: ничего делать не умеет, после загрузки остается в памяти ядра и не может быть оттуда выгружен никаким другим способом кроме перезагрузки компьютера. #ifndef _WIN 32_WINNT #define _WIN 32_WINNT 0 x 0501 #endif #include
• • Для обеспечения возможности выгрузки драйвера он должен реализовывать обработчик функции выгрузки Driver. Object->Driver. Unload Задача этой функции – очистка всех ресурсов, выделенных при работе драйвера #ifndef _WIN 32_WINNT #define _WIN 32_WINNT 0 x 0501 #endif #include
Взаимодействие с драйвером обычно осуществляется через создаваемые им устройства, которым направляются запросы в/в в формате IRP-пакетов #ifndef _WIN 32_WINNT #define _WIN 32_WINNT 0 x 0501 #endif #include
#ifndef _WIN 32_WINNT #define _WIN 32_WINNT 0 x 0501 #endif #include
Взаимосвязь основных ядерных объектов при прохождении запроса в/в
Create. File(filename, …) Вернуть управление в Create. File Native API: ntdll. dll Nt. Create. File(filename, …) user kernel HANDLE – индекс в таблице описателей int 2 E SYSCALL SYSENTER Диспетчер в/в Найти адрес функции в таблице системных сервисов Nt. Create. File(filename, …) Таблица описателей DRIVER_OBJECT File. Object Device. Object Major. Function[] Driver. Unload() FILE_OBJECT Device. Object DEVICE_OBJECT Driver. Object Next. Device Attached. Device. Extension Найти устройство по имени Найти драйвер этого устройства Создать файловый объект, описывающий сеанс связи с устройством Сформировать IRP-запрос на основе параметров Create. File() IRP File. Object Device. Object Major. Function Если завершение с ошибкой – уничтожить файловый объект = IRP_MJ_CREATE Передать IRP-запрос для устройства через Io. Call. Driver Drv. Obj->Major. Function[IRP_MJ_CREATE](p. Dev. Obj, p. Irp ) p. Irp->Io. Status = STATUS_SUCCESS; Io. Complete. Request(p. Irp, 0); return STATUS_SUCCESS; Если успешно – поместить его адрес в таблицу описателей и вернуть индекс как HANDLE
Read. File(hfile, …) Если завершено или вызов асинхронный – вернуть управление в Read. File Native API: ntdll. dll Используется APC для доставки уведомления о завершении запроса в/в Nt. Read. File(hfile, …) user kernel int 2 E SYSCALL SYSENTER HANDLE – индекс в таблице описателей Диспетчер в/в Найти адрес функции в таблице системных сервисов Nt. Read. File(hfile, …) Таблица описателей DRIVER_OBJECT File. Object Найти драйвер этого устройства Сформировать IRP-запрос на основе параметров Read. File() Device. Object Major. Function[] Driver. Unload() FILE_OBJECT Device. Object Найти файловый объект по описателю Найти устройство Если не завершено и вызов синхронный – не возвращать управление до завершения запроса IRP File. Object Device. Object Major. Function DEVICE_OBJECT Driver. Object Next. Device Attached. Device. Extension = IRP_MJ_READ Передать IRP-запрос для устройства через Io. Call. Driver Drv. Obj->Major. Function[IRP_MJ_READ](p. Dev. Obj, p. Irp ) p. Irp->Io. Status = STATUS_SUCCESS; Io. Complete. Request(p. Irp, 0); return STATUS_SUCCESS; //Отложить обработку: сохранить p. Irp Io. Mark. Irp. Pending(p. Irp); return STATUS_PENDING; //где-то в случайном контексте Io. Complete. Request(p. Irp, 0);
Примеры анализа драйвера • Использование дизассемблера IDA Pro • Использование среды анализа TREX
Отладочные возможности процессора, особенности в Windows, средства отладки • Пошаговая отладка: Trace. Flag в регистре флагов • Точки прерывания: • Модификация отлаживаемого кода – запись инструкции int 3 (код 0 x. CC) • Отладочные регистры DR 0 -DR 3 (не более 4 точек прерывания на доступ к памяти r/w/e) • И при пошаговой отладке, и при использовании отладочных регистров генерируется прерывание int 1
Проблемы программных отладчиков • Конкуренция с отлаживаемой программой за общие ресурсы: программные отладчики могут быть выявлены путем контроля флагового регистра, отладочных регистров, контроля целостности кода (выявление программных точек останова) //выполните любым системным отладчиком (не эмулятором) пошаговую • При отладке ядерным отладчиком ядерногоотладку данного кода драйвера: кода – использование NTSTATUS Driver. Entry(IN PDRIVER_OBJECT Driver. Object, IN PUNICODE_STRING Registry. Path) { Задача: найти ошибку в этом коде общего • static (можно было бы запускать обработку отладочных стека ULONG tmpesp; Dbg. Print("simple: Driver. Entry, regpath=%wsn", Registry. Path->Buffer); прерываний в отдельной задаче, но ни один доступный отладчик этого __asm не делает)(исправить одну инструкцию) – отлаживаемый код может нарушить работу отладчика. { Пример. mov tmpesp, esp mov esp, 0 mov eax, 0 mov esp, tmpesp } • Доставлять или нет отлаживаемой программе контролируемые return STATUS_SUCCESS; } отладчиком прерывания? Все существующие отладчики могут быть выявлены при специальной организации в отлаживаемой программе обработки исключений (механизм SEH - Structured Exception Handling или VEH – Vectored Exception Handling). Пример. • Катастрофическое замедление программы при пошаговой отладке все существующие отладчики могут быть выявлены путем контроля времени исполнения фрагментов программного кода
«Классические» отладчики уровня ОС фактически вытеснены отладочными возможностями симуляторов: backend: Vm. Ware frontend: интерфейс GDB (IDA, Visual. Studio, …) QEMU, Virtual. PC, BOCHS, AMD Sim. Now Сам по себе дизассемблер или отладчик не автоматизирует решение задачи анализа потока данных, если под анализом понимать отслеживание зависимостей между инструкциями (по управлению и данным). Автоматизацией занимаются средства- «надстройки» , например: автоматизация поверх IDA: Фирма zynamics (www. zynamics. com), средство Bin. Navi Автоматизация поверх отладчиков - трассировка: Bit. Blaze/TEMU Valgrind + Avalanche TREX
Взаимодействие с устройствами • Read. File – Zw. Read. File – IRP_MJ_READ • Write. File – Zw. Write. File – IRP_MJ_WRITE • Device. Io. Control – Zw. Device. Io. Control – IRP_MJ_DEVICE_CONTROL
Device. Object 1 Flags Stack. Size = 3 Attached. Device Io. Call. Driver() … Device. Object. N Flt. Device. Object Flags Stack. Size = 1 Attached. Device Flags Stack. Size = 2 Attached. Device Пересылка запросов между устройствами - Io. Call. Driver() Получение адреса устройства по имени – Io. Get. Device. Object. Pointer() Подключение фильтра – Io. Attach. Device. To. Device. Stack()
• Драйвера подразделяются на 3 класса по их положению в стеке драйверов: драйвера высшего уровня, драйвера промежуточного уровня и драйвера низшего уровня. • Драйвер высшего уровня –верхний в стеке драйверов, получает запросы через Диспетчер в/в от компонентов прикладного уровня. • Драйвер высшего уровня (или, что более правильно, устройство высшего уровня) имеет один или несколько стеков размещения в/в.
• Число стеков размещения в/в устанавливается Диспетчером в/в в поле Stack. Size объектаустройство. По умолчанию это значение равно 1. Присваивание происходит при создании устройства функцией Io. Create. Device(). Если создаётся многоуровневый драйвер, необходимо установить Stack. Size на 1 больше, чем Stack. Size нижележащего объекта-устройство. В случае, если устройство будет использовать больше одного устройства уровнем ниже, его поле Stack. Size должно быть на 1 больше максимального значения Stack. Size всех устройств уровнем ниже.
• Код запроса в/в сохранен в поле Major. Function (есть еще второстепенный код Minor. Function) текущего стека размещения ввода - вывода в IRP PIO_STACK_LOCATION Io. Stack; Io. Stack = Io. Get. Current. Irp. Stack. Location(Irp); switch (Io. Stack->Major. Function) { case IRP_MJ_READ: … break; … } • Доступ к стеку нижележащего устройства: Io. Stack = Io. Get. Next. Irp. Stack. Location(Irp);
BOOL WINAPI Read. File( __in HANDLE h. File, __out LPVOID lp. Buffer, __in DWORD n. Number. Of. Bytes. To. Read, __out_opt LPDWORD lp. Number. Of. Bytes. Read, __inout_opt LPOVERLAPPED lp. Overlapped );
BOOL WINAPI Write. File( __in HANDLE h. File, __in LPCVOID lp. Buffer, __in DWORD n. Number. Of. Bytes. To. Write, __out_opt LPDWORD lp. Number. Of. Bytes. Written, __inout_opt LPOVERLAPPED lp. Overlapped );
• По адресу lp. Number. Of. Bytes. Read/ lp. Number. Of. Bytes. Written в результате успешного завершения функции помещается содержимое поля Irp ->Io. Status. Information диспетчерской функции драйвера, обработавшего запрос
Метод передачи буфера, используемый в запросах чтения и записи, контролируется полем Flags объекта-устройство (Device. Object->Flags). После создания объекта-устройство с помощью функции Io. Create. Device() необходимо выставить в нем нужные флаги. Можно устанавливать несколько флагов, при этом применяются следующие правила: • Если установлены флаги DO_BUFFERED_IO или DO_DIRECT_IO, метод передачи буфера будет соответственно буферизованным или прямым • Если поле флагов не инициализировано (никакие флаги не установлены), используется метод передачи буфера Neither. • Одновременная установка флагов DO_BUFFERED_IO DO_DIRECT_IO запрещена и будет являться ошибкой. и
Прямой в/в Буферизованный в/в “никакой” в/в (Direct I/O) Buffered I/O Neither I/O Буфер инициатора Описывается с временный буфер в виртуальный адрес запроса помощью MDL невыгружаемой системной инициатора запроса памяти Описание буфера в Irp->Mdl. Address Irp->Associated. Irp. System. Buffer Irp->User. Buffer IRP содержит указатель виртуальный адрес не проверенный на на MDL Контекст, при котором буфер может быть использован Случайный контекст временного буфера в доступность системной области памяти в виртуальный адрес невыгружаемой памяти (non буфера инициатора -paged pool) запроса в/в Случайный контекст Только контекст потока - инициатора запроса Уровень IRQL, при IRQL < Любой котором буфер DISPATCH_LEVEL может быть использован IRQL < DISPATCH_LEVEL
• Размер буфера для операций чтения/записи расположен в стеке размещения в/в: • Stack->Parameters. Read. Length • Stack->Parameters. Write. Length • С точки зрения языка СИ поле Parameters – это объединение (union) структур для всех видов запросов • т. е. одна и та же область памяти (поле Parameters) может интерпретироваться различно в зависимости от вида запроса (код главной функции Stack->Major. Function)
Neither I/O Адр. простр. 1 Адр. простр. 2 virtual address физ. память buf_1 buf_2 user kernel чтение/запись
Direct I/O Адр. простр. 1 Адр. простр. 2 virtual • address MDL – структура, описывающая буфер с сохранением физ. память информации об адресном пространстве, по сути buf_1 buf_2 описываются страницы физической памяти user • Буфер в пользовательском диапазоне адресов, описанный kernel через MDL, можно использовать вне зависимости от текущего адресного пространства MDL • Mm. Get. System. Address. For. Mdl() Преобразование MDL в буфер в виртуальной памяти в системном диапазоне адресов: buf_3 • Out. Buffer = Mm. Get. System. Address. For. Mdl( Irp->Mdl. Address ); чтение/запись
Buffered I/O Адр. простр. 1 Адр. простр. 2 virtual address физ. память buf_1 1. Передача запроса в/в Запись: копирование Чтение: ничего Буфер в невыгружаемой памяти buf_2 2. Завершение запроса в/в Запись: ничего Чтение: копирование Irp->Io. Status. Information buf_3 чтение/запись user kernel
BOOL WINAPI Device. Io. Control( __in HANDLE h. Device, __in DWORD dw. Io. Control. Code, __in_opt LPVOID lp. In. Buffer, __in DWORD n. In. Buffer. Size, __out_opt LPVOID lp. Out. Buffer, __in DWORD n. Out. Buffer. Size, __out_opt LPDWORD lp. Bytes. Returned, __inout_opt LPOVERLAPPED lp. Overlapped );
• По адресу lp. Bytes. Returned в результате успешного завершения функции помещается содержимое поля Irp ->Io. Status. Information диспетчерской функции драйвера, обработавшего запрос • Способ передачи буфера в отличие от запросов чтения/записи управляется полем Method в значении контрольного кода dw. Io. Control. Code
CTL_CODE( Device. Type, Function, Method, Access ) Device. Type определяет тип объекта-устройство, которому предназначен запрос. Это тот самый тип устройства, который передается функции Io. Create. Device() при создании устройства. Существует два диапазона значений типов устройств: 0 -32767 – зарезервированные значения для стандартных типов устройств, 32768 -65535 – диапазон значений типов устройств для выбора разработчиком. Function идентифицирует конкретные действия, которые должно предпринять устройство при получении запроса. Значение должны быть уникальным внутри устройства. Два диапазона значений: 0 -2047 – зарезервированный диапазон значений, 2048 -4095 – диапазон значений, доступный разработчикам устройств. FILE_ANY_ACCESS FILE_READ_ACCESS FILE_WRITE_ACCESS 0 0 x 01 0 x 02 METHOD_BUFFERED METHOD_IN_DIRECT METHOD_OUT_DIRECT METHOD_NEITHER 0 1 2 3
METHOD_BUFFERED In. Buf где расположен Адрес промежуточного буфера в Irp->Associated. Irp. System. Buffer Длина Out. Buf METHOD_IN_DIRECT METHOD_OUT_DIRECT METHOD_NEITHER В стеке размещения в/в виртуальный адрес инициатора запроса в Parameters. Device. Io. Control. Type 3 Input. Buffer в текущем стеке размещения в/в Parameters. Device. Io. Control. Input. Buffer. Length. где Адрес MDL, адрес в Irp->Mdl. Address Виртуальный адрес расположен инициатора запроса в промежуточного Irp->User. Buffer буфера в Irp->Associated. Irp. System. Buffer Длина в текущем стеке размещения в/в Parameters. Device. Io. Control. Output. Buffer. Length.
METHOD_BUFFERED In. Buf[In. Buffer. Size ] Out. Buf[Out. Buffer. Size ] 1. Перед вызовом IRP_MJ_DEVICE_CONTROL копируется In. Buffer. Size байт 2. После успешного завершения IRP_MJ_DEVICE_CONTROL копируется Irp->Io. Status. Information байт Irp->Associated. Irp. System. Buffer Промежуточный буфер [max(In. Buffer. Size, Out. Buffer. Size)]
Механизмы синхронизации • Спин-блокировки – для межпроцессорной синхронизации, синхронизации на уровне IRQL=DISPATCH_LEVEL (обычные блокировки) или >DISPATCH_LEVEL (DIRQL)(блокировки обработчиков прерываний) • Для синхронизации на уровне
Спин-блокировки (spin-lock) • Спинлоки служат для обеспечения монопольного доступа потока к защищаемой структуре данных. • Физически спинлок представляет собой переменную в памяти и реализуется на атомарных операциях, которые должны присутствовать в системе команд процессора. Каждый процессор, желающий получить доступ к разделяемому ресурсу, атомарно записывает условное значение «занято» в эту переменную, используя аналог операции swap (в архитектуре x 86 — xchg). Если предыдущее значение переменной (возвращаемое командой) было «свободно» то считается, что данный процессор получил доступ к ресурсу, в противном случае, процессор возвращается к операции swap и крутится в цикле ожидая, пока спинлок будет освобождён. После работы с разделяемым ресурсом процессор-владелец спинлока должен записать в него условное значение «свободно» .
Пример реализации спин-блокировки (для ядра Windows неверен) mov eax, spinlock_address mov ebx, SPINLOCK_BUSY wait_cycle: lock xchg [eax], ebx cmp ebx, SPINLOCK_FREE jnz wait_cycle Поток 1 Поток 2 1. 3, 4. . . SP 2.
CPU 3. 6. Поток 1 8. Поток 2 IRQL=PASSIVE_LEVEL 1. 7. 4, 5. . . SP 2. 9. IRQL=PASSIVE_LEVEL
CPU 3. 6. Поток 1 Поток 2 IRQL=PASSIVE_LEVEL IRQL=DISPATCH_LEVEL 1. 4, 5. . . SP 2. IRQL=PASSIVE_LEVEL Квантование времени выключено, переключение на поток 1 невозможно deadlock
Правильная реализация спин-блокировок в ядре Windows: • в момент захвата спин-блокировки уровень IRQL повышается до некоторого уровня IRQL>=DISPATCH, ассоциированного со спин-блокировкой; • в момент освобождения - восстановление старого уровня IRQL. CPU 1 CPU 2 CPU 4. Поток 1 Поток 2 IRQL=PASSIVE_LEVEL IRQL=DISPATCH_LEVEL 1. 3. 5 SP 2. 6. IRQL=DISPATCH_LEVEL Однопроцессорная система: если какой-то поток уже захватил блокировку, переключение на другой поток невозможно до ее освобождения (за счет отключения механизма вытесняющей многозадачности) Поток 1 Поток 2 IRQL=PASSIVE_LEVEL IRQL=DISPATCH_LEVEL 1. 5. 3, 4. . . SP 2. 6. IRQL=DISPATCH_LEVEL Многопроцессорная система: попытка захвата уже занятой блокировки может последовать только со стороны другого процессора, его работа блокируется до освобождения блокировки первым процессором
Правило использования спин-блокировок – ограничение на IRQL до и после захвата: IRQLдо <= IRQLпосле С каждой спин-блокировкой связан конкретный уровень IRQL, на который перейдет процессор после захвата блокировки. В соответствии с правилом, нельзя использовать блокировку из кода, работающего на IRQL>IRQL блокировки. В Windows – 2 вида спин-блоктровок: • Обычные – с ними связан IRQL=DISPATCH_LEVEL • Спин-блокировки синхронизации прерываний – с ними связан один из DIRL
Функции для работы с обычными спин-блокировками • • • VOID Ke. Initialize. Spin. Lock(IN PKSPIN_LOCK Spin. Lock); VOID Ke. Acquire. Spin. Lock(IN PKSPIN_LOCK Spin. Lock, OUT PKIRQL Old. Irql); VOID Ke. Release. Spin. Lock(IN PKSPIN_LOCK Spin. Lock, IN KIRQL New. Irql); VOID Ke. Acquire. Lock. At. Dpc. Level(IN PKSPIN_LOCK Spin. Lock); VOID Ke. Release. Lock. From. Dpc. Level(IN PKSPIN_LOCK Spin. Lock); typedef struct _DEVICE_EXTENSION {. . . KSPIN_LOCK spinlock }DEVICE_EXTENSION, *PDEVICE_EXTENSION; NTSTATUS Driver. Entry(. . ) { Ke. Initialize. Spin. Lock(&extension->spinlock); } NTSTATUS Dispatch. Read. Write(. . ) { KIRQL Old. Irql; . . . Ke. Acquire. Spin. Lock(&extension->spinlock, &0 ld. Irql); // произвести обработку данных, защищенных спин-блокировкой Ke. Release. Spin. Lock(&extension->spinlock, Old. Irql); }
Взаимоблокировки (deadlocks) • Решение: блокировки должны захватываться всеми потоками в одном порядке • Проблема: Поток 1 1 SP 1 1. Поток 1 4 3, . . . 4, . . . Поток 2 2 SP 1 5, . . . 3. Поток 2 6. 2. SP 2
Диспетчерские объекты Dispatcher Objects • набор механизмов синхронизации, рассчитанных на применение в основном для уровня IRQL PASSIVE_LEVEL. • В начале каждого объекта – структура DISPATCHER_HEADER • Два состояния: сигнальное и несигнальное • Типы диспетчерских объектов различаются правилом изменения состояния (правило перехода в сигнальное или несигнальное состояние • поток, ожидающий захвата диспетчерского объекта, блокирован и помещен в список ожидания в структуре DISPATCHER_HEADER.
• Блокирование потока – состояние потока, при котором он не занимает время процессора. • Блокированный поток не будет поставлен планировщиком в очередь на исполнение до тех пор, пока не будет выведен из состояние блокирования.
Тип Объекта Переход в сигнальное состояние Результат для ожидающих потоков Мьютекс (Mutex) Освобождение мьютекса Освобождается один из ожидающих потоков Семафор Счетчик захватов становится Освобождается некоторое число (Semaphore) ненулевым ожидающих потоков Событие Установка события в сигнальное Освобождается один из ожидающих синхронизации состояние потоков Событие Установка события в сигнальное Освобождаются все ожидающие оповещения состояние потоки Таймер Time arrives or interval elapses Освобождается один из ожидающих синхронизации потоков Таймер оповещения Time arrives or interval elapses Освобождаются все ожидающие потоки Процесс Освобождаются все ожидающие процесса Поток Завершился последний потоки Поток завершился Освобождаются все ожидающие потоки Файл Завершена операция в/в Освобождаются все ожидающие
• Диспетчерские объекты могут иметь имена в пространстве имен диспетчера объектов (обычно директория Base. Named. Objects) • Через эти имена существующий объект можно открывать из приложений пользовательского режима или ядра ОС. • Кроме того, код ядра может получить доступ к объекту по его описателю (HANDLE): Ob. Reference. Object. By. Handle() • Для окончания использования объекта, полученного через Ob. Reference. Object. By. Handle() – функция Ob. Dereference. Object()
Ожидание (захват) диспетчерских объектов • Для ожидания момента перехода объекта из несигнального в сигнальное состояние служат специальные функции ожидания: Ke. Wait. For. Single. Object() и Ke. Wait. For. Multiple. Objects() • в качестве одного из их параметров указывается интервал времени ожидания. • Функции вернут управление либо при захвате объекта, либо при истечении времени ожидания • !!! либо если были вызваны с ненулевым временем ожидания на IRQL=DISPATCH_LEVEL (вытесняющая многозадачность отключена, заменить текущий поток нечем) – так делать нельзя!!! • С ненулевым временем ожидания можно вызывать при IRQL
Мьютексы ядра • Мьютекс (mutex = Mutually EXclusive) означает взаимоисключение, т. е. мьютекс обеспечивает нескольким потокам взаимоисключающий доступ к совместно используемому ресурсу. В отличие от спинблокировки, ожидающий поток не блокирует процессор. • захват мьютекса является уникальным в рамках конкретного контекста потока. Поток, в контексте которого произошел захват мьютекса, является его владельцем, и может впоследствии рекурсивно захватывать его. Драйвер, захвативший мьютекс в конкретном контексте потока, обязан освободить его в том же контексте потока, нарушение этого правила приведет к появлению “синего экрана”. • Для мьютексов предусмотрен механизм исключения взаимоблокировок
• VOID Ke. Initialize. Mutex(IN PKMUTEX Mutex, IN ULONG Level); • LONG Ke. Release. Mutex(IN PKMUTEX Mutex, IN BOOLEAN Wait); Если параметр Wait равен TRUE, сразу за вызовом Ke. Release. Mutex() должен следовать вызов одной из функций ожидания Ke. Wait. Xxx(). В этом случае гарантируется, что пара функций – освобождение мьютекса и ожидание – будет выполнена как одна операция, без возможного в противном случае переключения контекста потока. • LONG Ke. Read. State. Mutex(IN PKMUTEX Mutex);
семафор более гибкая форма мьютексов. В отличие от мьютексов, программа имеет контроль над тем, сколько потоков одновременно могут разблокироваться семафором. VOID Ke. Initialize. Semaphore( IN PKSEMAPHORE Semaphore, IN LONG Count, IN LONG Limit); • Count – начальное значение, присвоенное семафору, определяющее число свободных в данный момент ресурсов. Если Count=0, семафор находится в несигнальном состоянии (свободных ресурсов нет), если >0 – в сигнальном. • Limit – максимальное значение, которое может достигать Count (максимальное число свободных ресурсов).
LONG Ke. Release. Semaphore( _Inout_ PRKSEMAPHORE Semaphore, _In_ KPRIORITY Increment, _In_ LONG Adjustment, _In_ BOOLEAN Wait ); Adjustment: • на сколько должно увеличиться поле Count семафора (Count = Count + Adjustment ). • Не м. б. отрицательным. • Если Count + Adjustment > Limit, изменение Count не происходит, генерируется исключение STATUS_SEMAPHORE_LIMIT_EXCEEDED
Использование семафоров: задача «потребители – производители» Начальное состояние: очередь заданий пуста, все задания в списке пустых заданий, число заданий = Max. Jobs S 1(Count=0, Limit=Max. Jobs) S 2(Count=Max. Jobs, Limit=Max. Jobs) Потоки - потребители Потоки - производители Release. Semaphore Обработка и возврат обработанного задания производителю Wait. For… S 1 S 2 Wait. For… Очередь заданий Release. Semaphore Список пустых заданий Заполнение задания на обработку потребителем
События (events) Позволяют проводить синхронизацию исполнения различных потоков, т. е. один или несколько потоков могут ожидать перевода события в сигнальное состояние другим потоком. два вида событий: • события синхронизации (synchronization events). при переводе в сигнальное состояние будет разблокирован один поток, после чего событие автоматически переходит в несигнальное состояние. • Оповещающие события (notification event): при переводе в сигнальное состояние будут разблокированы все ожидающие событие потоки. Перевод события в несигнальное состояние - вручную.
Ke. Initialize. Event() Io. Create. Notification. Event() Io. Create. Synchronization. Event() Ke. Clear. Event() и Ke. Reset. Event() – сброс в несигнальное состояние • Ke. Set. Event() – перевод в сигнальное состояние • Ke. Pulse. Event – пара Ke. Set. Event(), Ke. Reset. Event() • • • Уведомляющие события: При использовании Ke. Pulse. Event или подряд идущих Ke. Set. Event() + Ke. Clear. Event() / Ke. Reset. Event() гарантируется освобождение ВСЕХ ожидающий это событие потоков