Последовательность создания процесса уведомление подсистемы windows

Основные понятия. Структуры данных для процессов и потоков. Создание процесса.

Аннотация: Основные понятия. Структуры данных для процессов и потоков. Создание процесса.

Основные понятия

Программа (program) – это последовательность команд, реализующая алгоритм решения задачи. Программа может быть записана на языке программирования (например, на Pascal, С++, BASIC); в этом случае она хранится на диске в виде текстового файла с расширением, соответствующим языку программирования (например, .PAS, .CPP, .VB). Также программа может быть представлена при помощи машинных команд; тогда она хранится на диске в виде двоичного исполняемого файла (executable file), чаще всего с расширением .EXE. Исполняемый файл генерируется из текста программы при компиляции.

Процесс (process) – это программа (пользовательская или системная) в ходе выполнения.

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

Если операционная система умеет запускать в одно и то же время несколько процессов, она называется многозадачной (multitasking) (пример – Windows), иначе – однозадачной (пример – MS DOS).

Процесс может содержать один или несколько потоков (thread) – объектов, которым операционная система предоставляет процессорное время. Сам по себе процесс не выполняется – выполняются его потоки. Таким образом, машинные команды, записанные в исполняемом файле, выполняются на процессоре в составе потока. Если потоков несколько, они могут выполняться одновременно.

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

Операционные системы, поддерживающие несколько процессоров, называются многопроцессорными.

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

Многопоточность – это средство распараллеливания действий внутри процесса.

Примеры.

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

Каждый процесс имеет свое собственное виртуальное адресное пространство (см. лекцию 11 «Управление памятью«), в котором независимо друг от друга работают все потоки процесса. Например, поток 1 может записывать данные в ячейку с адресом 100, а поток 2 читать данные из ячейки с адресом 101.

Замечание. Конечно, если два (или более) потоков захотят записать что-то свое в одну и ту же ячейку, возникнет неопределенность – кто раньше? Это одна из подзадач обширной проблемы синхронизации.

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

Структуры данных для процессов и потоков

В WRK за управление процессами отвечает диспетчер процессов (basentosps), а многие важные структуры данных описаны в заголовочных файлах basentosincps.h и basentosincke.h.

Процесс в Windows описывается структурой данных EPROCESS [5]. Эта структура в WRK содержится в файле basentosincps.h (строка 238). Рассмотрим некоторые её поля.

  • Pcb (Process Control Block – блок управления процессом) – представляет собой структуру KPROCESS, хранящую данные, необходимые для планирования потоков, в том числе указатель на список потоков процесса (файл basentosincke.h, строка 944).
  • CreateTime и ExitTime – время создания и завершения процесса.
  • UniqueProcessId – уникальный идентификатор процесса.
  • ActiveProcessLinks – элемент двунаправленного списка (тип LIST_ENTRY), содержащего активные процессы.
  • QuotaUsage, QuotaPeak, CommitCharge – квоты (ограничения) на используемую память.
  • ObjectTable – таблица дескрипторов процесса.
  • Token – маркер доступа.
  • ImageFileName – имя исполняемого файла.
  • ThreadListHead – двунаправленный список потоков процесса.
  • Peb (Process Environment Block – блок переменных окружения процесса) – информация об образе исполняемого файла (файл publicsdkincpebteb.h, строка 75).
  • PriorityClass – класс приоритета процесса (см. лекцию 9 «Планирование потоков»).

Структура для потока в Windows называется ETHREAD и описывается в файле basentosincps.h (строка 545). Её основные поля следующие:

  • Tcb (Thread Control Block – блок управления потоком) – поле, которое является структурой типа KTHREAD (файл basentosincke.h, строка 1069) и необходимо для планирования потоков.
  • CreateTime и ExitTime – время создания и завершения потока.
  • Cid – структура типа CLIENT_ID, включающая два поля – идентификатор процесса-владельца данного потока и идентификатор самого потока.
  • ThreadsProcess – указатель на структуру EPROCESS процесса-владельца.
  • StartAddress – адрес системной стартовой функции потока. При создании потока сначала вызывается системная стартовая функция, которая запускает пользовательскую стартовую функцию.
  • Win32StartAddress – адрес пользовательской стартовой функции.

Создание процесса

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

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

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

В Windows для создания процессов применяется одна из следующих WinAPI-функций: CreateProcess, CreateProcessAsUser, CreateProcessWithTokenW, CreateProcessWithLogonW [10]. Далее при описании будем использовать функцию CreateProcess.

Создание процессов в Windows включает 7 этапов [5; 2].

1. Проверка и преобразование параметров.

Параметры функции CreateProcess проверяются на корректность и преобразуются к внутреннему формату системы.

2. Открытие исполняемого файла.

Происходит поиск файла, который содержит запускаемую программу. Обычно это файл с расширением .EXE, но могут быть также расширения .COM, .PIF, .BAT, .CMD. Определяется тип исполняемого файла:

  • Windows приложение (.EXE) – продолжается нормальное создание процесса;
  • приложение MS-DOS или Win16 (.EXE, .COM, .PIF) – запускается образ поддержки Ntvdm.exe;
  • командный файл (.BAT, .CMD) – запускается образ поддержки Cmd.exe;
  • приложение POSIX – запускается образ поддержки Posix.exe.

3. Создание объекта «Процесс».

Формируются структуры данных EPROCESS, KPROCESS, PEB, инициализируется адресное пространство процесса. Для этого вызывается системная функция NtCreateProcess (файл basentospscreate.c, строка 826), которая затем вызывает функцию NtCreateProcessEx (тот же файл, строка 884), а та, в свою очередь, функцию PspCreateProcess (тот же файл, строка 1016).

Замечание. Начиная с Windows Vista при создании процесса вызов нескольких функций (NtCreateProcess, NtWriteVirtualMemory, NtCreateThread) заменен вызовом одной функции NtCreateUserProcess.

Рассмотрим некоторые важные действия, выполняемые функцией PspCreateProcess.

  • Если в параметрах функции PspCreateProcess указан процесс-родитель:
    • по его дескриптору определяется указатель на объект EPROCESS (функция ObReferenceObjectByHandle, строка 1076);
    • наследуется от процесса родителя маска привязки к процессорам (Affinity, строка 1092).
  • Устанавливается минимальный и максимальный размеры рабочего набора (WorkingSetMinimum = 20 МБ и WorkingSetMaximum = 45 МБ, строки 1093 и 1094, см. лекцию 11 «Управление памятью»).
  • Создается объект «Процесс» (структура EPROCESS) при помощи функции ObCreateObject (строка 1108).
  • Инициализируется двунаправленный список потоков при помощи функции InitializeListHead (строка 1134).
  • Копируется таблица дескрипторов родительского процесса (строка 1270).
  • Создается структура KPROCESS при помощи функции KeInitializeProcess (строка 1289).
  • Маркер доступа и другие данные, связанные с безопасностью (см. лекцию 13 «Безопасность», копируются из родительского процесса (функция PspInitializeProcessSecurity, строка 1302).
  • Устанавливается приоритет процесса, равный Normal; однако, если приоритет родительского процесса был Idle или Below Normal, то данный приоритет наследуется (строки 1307–1312, см. лекцию 9 «Планирование потоков»).
  • Инициализируется адресное пространство процесса (строки 1337–1476).
  • Генерируется уникальный идентификатор процесса (функция ExCreateHandle) и сохраняется в поле UniqueProcessId структуры EPROCESS (строки 1482–1488).
  • Создается блок PEB и записывается в соответствующее поле структуры EPROCESS (строки 1550–1607).
  • Созданный объект вставляется в хвост двунаправленного списка всех процессов (строки 1613–1615) и в таблицу дескрипторов (строки 1639–1644). Первая вставка обеспечивает доступ к процессу по имени, вторая – по ID.
  • Определяется время создания процесса (функция KeQuerySystemTime) и записывается в поле CreateTime структуры EPROCESS (строка 1733).

4. Создание основного потока.

Формируется структура данных ETHREAD, стек и контекст потока, генерируется идентификатор потока. Поток создается при помощи функции NtCreateThread, определенной в файле basentospscreate.c, (строка 117), которая вызывает функцию PspCreateThread (тот же файл, строка 295). При этом выполняются следующие действия:

  • создается объект ETHREAD (строка 370).
  • Заполняются поля структуры ETHREAD, связанные с процессом-владельцем, – указатель на структуру EPROCESS (ThreadsProcess) и идентификатор процесса (Cid.UniqueProcess) (строки 396 и 398).
  • Генерируется уникальный идентификатор потока (функция ExCreateHandle) и сохраняется в поле Cid.UniqueThread структуры EPROCESS (строки 400–402).
  • Заполняются стартовые адреса потока, системный (StartAddress) и пользовательский (Win32StartAddress) (строки 468-476).
  • Инициализируются поля структуры KTHREAD при помощи вызова функции KeInitThread (строки 490–498 для потока пользовательского режима и 514–522 для потока режима ядра).
  • Функция KeStartThread заполняет остальные поля структуры ETHREAD и вставляет поток в список потоков процесса (строка 564).
  • Если при вызове функции PspCreateThread установлен флаг CreateSuspended («Приостановлен») поток переводится в состояние ожидания (функция KeSuspendThread, строка 660); иначе вызывается функция KeReadyThread (строка 809), которая ставит поток в очередь готовых к выполнению потоков (см. лекцию 9 «Планирование потоков»).

5. Уведомление подсистемы Windows.

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

6. Запуск основного потока.

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

7. Инициализация процесса.

  • Проверяется, не запущен ли процесс в отладочном режиме;
  • проверяется, следует ли производить предвыборку блоков памяти (тех участков памяти, которые при прошлом запуске использовались в течение первых 10 секунд работы процесса);
  • инициализируются необходимые компоненты и структуры данных процесса, например, диспетчер кучи;
  • загружаются динамически подключаемые библиотеки (DLL – Dynamic Link Library);
  • начинается выполнение стартовой функции потока.

Резюме

В этой лекции введены понятия «процесса» и «потока». Рассмотрены структуры данных, представляющие в операционной системе процесс (EPROCESS) и поток (ETHREAD). Описан ход создания процесса с использованием структур данных и функций Windows Research Kernel.

Следующая лекция посвящена алгоритмам планирования потоков и реализации этих алгоритмов в Windows.

Контрольные вопросы

  1. Приведите определение понятий «программа», «процесс», «поток», «стек».
  2. Опишите основные поля структуры EPROCESS.
  3. Какой структурой является поле Pcb структуры EPROCESS? Опишите поля этой структуры.
  4. Опишите основные поля структуры ETHREAD.
  5. Перечислите этапы создания процесса.
  6. Опишите этапы создания объекта «процесс».
  7. Опишите этапы создания основного потока.

�������-�������

�������-�������

������ �������� (job object)�� ��� ���������, ���������� � ����������� ������ ����, �������������� ���������� ����� ��� ����������� ���������� ��� �������. ������� ����� ������� ������ � ���� �������. �� ��������� ��� ����� � �������� �������� ������ ���������, � ��� ��������, ����������� ������ ��������� � ��� ���������, ����� ������������ � ��� �� ��������. ������ �������� ����� ������������ ������� ������� ���������� ���� ���������� � ���� ���������, � ��� ����� ��� �������������. Windows-�������, ��������������� ��� �������� ��������-������� � ��������������� ���, ����������� � ������� 6-20.

���� ������ ���������� ��������� �����������, ������� ����� �������� �� �������.

� ������������ ����� �������� ��������� ������������ ����� ������������ ����������� ��������� �������.

� ����� ����� �� ������������ ����� � ���������������� ������ ������������ ������������ ���������� ������������� �������, ������������� ���������� ������� (� ������ �������������) � ���������������� ������. ��� ������ ���� ����� ����� ��������, ��� �������� ������� ���������� � ���������� �� ������, � �������� ����� ��������� � ������� ������ ����������� (���� ����� �� ����� ��������������). ������-������� ����� ��������� � ��������� ���������, � ��� ��������� ��� ������ �����������. ��� ��������� ������� �� ��������� ����� �������� ����� ������� EndOfJobTimeAction.

� �������������� ����� �� ������������ ����� ����������������� ������ ��� ������� �������� ������������ ������������ ���������� ������������� �������, ������������� ������ ��������� � �������. �� ���������� ����� ������ ������� ����������� (�� ������� ����� �� �������).

� ����� ������������ ������� ������������� ������������ ������ ��� ������� ���������, �������� � �������. ���� �������� �������� ������ � ��������, ������������ ������� ������������� ������ (� �������� Windows Server �� ���������). ������������ ������ ������������ ������� ������������ �������, ��� �������� � ��������� �������.

� �������� ������� � ����������� ������������� ����� �������� � ����������� ��� ������� �������� �������. (��������� ������ ����� �������� ���� �������� �� ����� ������������ �������� �������, �� �������� ����� ������ �� �����.)

� ����� ���������� ��� ���� ��������� ������� ���������� ����� ���������� ��� ������� �������� � �������. ������ �� ����� �������� ���� ��������� ������������ ������ (��� ��� ��� ������ ������). ��� ������� ��������� ���������� ������������. (��� ������ SetTbreadPriority ������ �� ������������, �� � ��������� �� ����������.)

� ����������� � ������������ ������� �������� ������ �� ��������� ������������� ��������� ����������� � ������������ ������� �������� ������ ��� ������� �������� �������. ������� �������� ���� ������� �����, �� � ����������� ������������ � ����������� ���������.)

� ����� �� ����������� ������, ������������ �������� ��� ������� ��������� ������������ ������ ������������ ��������� ������������, ������� ����� �������� ���� ������ ��������, ���� ����� �������.

������� ����� ��������� �� �������� � ������� ������� ����� ���������� �����-������ ������-���� ��������, ������� ����� ����� ������ ������ ����� Windows-������� GetQueuedCompletionStatus.

������� ����� ��������� ����������� �� ���������� � ���� �������� �����������, ��������� � �������. ��������, �� ������ ������� ���, ����� ��� �������� � ������� ������������ ���� � ��� �� ������ ������� ��� �� ����� ����� ������������ (���������) ������ �������� ���� ��������� �������� � ��������� �������, ����������� ���������� ������ ��������� ���������������. ����� ����, ����������� ���������� �������� ������, ���������������, ��������, ��� ��������� ��������: ����� ������ ��������� ������� ������������ ���������� ������, �� �� ������� ������������� ����� ������������ ��������� ��������� ���������� � �������������� ������ (SID).

�������, �� ������ �������� ����������� ��� ����������������� ���������� ��������� �������, �������� ��������� �������� ���������� ���������� ����, �������� ������� ������, �� �������� � ��� �������, ������������ �������� � ������� ������ ��� ����������� ��������� ������ ���������� ����������������� ���������� ������� � ������� Windows-������� SystemParametersInfo.

B Windows 2000 Datacenter Server ������� ������� Process Control Manager, ����������� �������������� ���������� ������� ��������, ������������� ��� ��� ��������� ����� � ������, � ����� ��������� ��������, ������� ������� �������� � �� ��� ���� ������� ��� �������. ��������, ��� ��� ������� ������ �� ������������ � Windows Server 2003 Datacenter Edition, �� ��������� � ������� ��� ���������� Windows 2000 Datacenter Server �� Windows Server 2003 Datacenter Edition.

�����������: �������� ������� ��������

�� ������ ������������� ����������� ������� �������� � �������� Performance (������������������). ��� ��������� ������������� ������� ����� ������������ ������� !job ��� dt nt!_ejob ��������� ����.

��������, ����������� �� ������ ������� � ��������, ��������� ������� !process ��������� ���� ��� � � Windows XP � Windows Server 2003 � ������� Process Explorer. ����� ������� ������������� ������ �������� � ����������� �� ���, ��������������� ��������� �����.

1.�������� ������� runas ��� �������� �������� ��������� ������ (Cmd.exe). ��������, �������� runas /user:�����훋���_�������������cmd. ����� ������� ���� ������, � �� ������ �������� ���� ��������� ������. Windows-������, ����������� ������� runas, ������� ������������� �������, ���������� ��� �������� (��� ����� ��������� � ������ ������ ������ �� �������).

2.��� ��������� ������ ��������� Notepad.exe.

3.���������� Process Explorer � �������� �������� �� ��, ��� �������� Cmd.exe � Notepad.exe ���������� ��� ����� �������. ��� ��� �������� �������� �� ��������� �����������.

4.������� �������� ���� ������� Cmd.exe, ���� ������� Notepad.exe, ����� ������� ���� �������. B ���� ���� �� ������� ������� J�b.

5.���������� �� ������� J�b ��� ��������� ��������� �������� � �������. B ����� ������ � �������� �� ������������ ������� ����� � � ���� ������ �������� ��� ��������.

6.������� ��������� �������� ���� � ���������� ������� (���� Win-Dbg � ������ ��������� ������� ����, ���� LiveKd, ���� �� ����������� Windows 2000), �������� �� ����� ������ ��������� �������� !process � ������� � ��� ������ ��� ��������� ������� Cmd.exe. ����� ����������� ���������� ����� ��������, ����� ������� !process ��������������_���������, � ������� ����� ������� ��������. �������, ���������� ������ �������� � ������� ������� !job. ���� �������� �������� ������ ��������� ��� ���� ������ � ���������� �������:

7.��������, ����������� ������� dt ��� ��������� �������-������� � �������� �������� �� �������������� ����:

С точки зрения
программирования каждый процесс win32/64
включает компоненты (рис. 22):

  • один или несколько
    потоков;

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

  • один или более
    сегментов кода, включая код DLL;

Рис. 22. Структура
процесса и его потоков

  • один
    или более сегментов данных, содержащих
    глобальные переменные;

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

  • память кучи
    процесса;

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

Атрибуты процесса
и потока в Windows
приведены в табл. 2.

Атрибуты процесса
и потока в Windows
Таблица 2

Процесс

Поток

Идентификатор процесса — уникальное
значение, идентифицирующее процесс
в ОС

Идентификатор потока — уникальное
значение, идентифицирующее поток,
когда он вызывает сервис.

Дескриптор защиты – описывает, кто
создал процесс, права доступа и пр.

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

Базовый приоритет – базовый приоритет
для потоков

Динамический приоритет — приоритет
выполняемого потока в данный момент.

Базовый
приоритет – нижний приоритет
динамического приоритета потока.

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

Родственность процессов по потоку –
множество процессов, где возможно
выполнение потоков.

Время выполнения – суммарное время,
затраченное на выполнение всех потоков
в процессе.

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

Счётчик ввода/вывода — переменные, в
которые заносятся сведения о количестве
и типе операций ввода/вывода, выполненных
потоками процесса.

Статус извещения (оповещения) – этот
флаг, который указывает, следует ли
потоку выполнять асинхронный вызов
процедуры.

Счётчик операций с виртуальной памятью
– это переменные, в которые заносятся
сведения о количестве и типе операций
с виртуальной памятью выполненных
потоками процесса.

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

Квоты – максимальное количество
страничной памяти и процессорного
времени доступного процессу

Маркеры режима анонимного воплощения
– это временный признак доступа.

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

Порт завершения – это канал обмена
информацией между процессами, куда
диспетчер процессов отправляет
сообщения при завершении потока.

Статус выхода –
причины завершения процесса

Статус выхода –
причины завершения потока

Все потоки процесса
совместно используют код, глобальные
переменные, строки окружения и ресурсы
процесса [3].
Каждый поток планируется независимо.
Поток включает следующие элементы:

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

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

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

Некоторые атрибуты
потока подобны атрибутам процессов.
Значениия таких атрибутов потока
извлекаются из значений атрибутов
процесса. Например, в многопроцессорной
системе родственные процессоры по
потоку – это множество процессоров, на
которых может выполняться данный поток.
Оно совпадает с множеством процессоров
родственных по процессу или является
его подмножеством. Информация, содержащаяся
в контексте потока, позволяет ОС
приостанавливать и возобновлять потоки
[9].
Основные функции управления процессами
и потоками показаны в табл.3.

Сервисы процесса
и потока в Windows
Таблица 3

Процесс

Поток

Создание процесса CreateProcess().

Создание потока CreateThread().

Открытие процесса OpenProcess().

Открытие потока OpenThread()

Информация по запросу процесса

Информация по запросу потока

Информация по наладке процесса

Информация по наладке потока

Текущий процесс
GetCurrentProcessID()

Текущий поток GetCurrentThreadID()

Прекращение процесса
ExitProcess()

Завершение потока ExitThread()

Получение контекста

Установка контекста

Приостановка Delay()

Возобновление

Извещение потоков

Проверка извещения потока

Порт регистрации завершения.

Основной функцией
для управления процессом win32 является
функция CreateProcess(). Она создаёт процесс
с одним потоком. Так как процесс требует
наличие кода, то в вызове функции
CreateProcess() необходимо указывать имя
исполняемого файла программы. Функция
имеет 10 параметров и при успешном
выполнении возвращает дескрипторы для
процесса и для первичного потока.
Дополнительные потоки можно создать
функцией CreateThread(). Для Windows
все процессы одинаковы, и она не различает
дочерние и родительские, в отличие от
Unix.

Жизненный цикл
потока в Windows
(рис 23) проходит следующие шесть состояний
[3]:

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

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

Рис. 23. Жизненный
путь потока в Windows

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

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

Переходное:
поток готов к выполнению, но не все
ресурсы необходимые ему для работы
доступны. При доступности всех ресурсов
он переходит в состояние готовности.

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

После завершения
необходимых операций освобождения
ресурсов поток удаляется из операционной
системы.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #

Описание презентации по отдельным слайдам:

  • Тема: Процессы и потоки в ОС Windows NTВнутреннее устройство процесс...

    1 слайд

    Тема: Процессы и потоки в ОС Windows NT
    Внутреннее устройство процессов.
    Внутреннее устройство потоков.
    Планирование потоков.

  • ЛитератураМ. Русинович, Д. Соломин  Внутреннее устройство Windows: Windows Se...

    2 слайд

    Литература
    М. Русинович, Д. Соломин Внутреннее устройство Windows: Windows Server 2003, Windows XP, Windows 2000. Мастер-класс. / Пер. с анг. – 4-е изд. – М.: Издательско-торговый дом «Русская редакция»; СПб.: Питер, 2005. – 992 с.
    Э. Таненбаум Современные операционные системы. 3-е изд.; СПб.: Питер, 2010. – 1120 с.

  •          Структура данных процессов и потоков

    3 слайд

    Структура данных процессов и потоков

  • Блок процесса EPROCESS

    4 слайд

    Блок процесса EPROCESS

  •         Блок процесса исполнительной системы

    5 слайд

    Блок процесса исполнительной системы

  • Поля в блоке PEB

  • Переменные ядра, связанные с производительностью

    7 слайд

    Переменные ядра, связанные с производительностью

  •     Счетчики производительности, связанные с процессами

    8 слайд

    Счетчики производительности, связанные с процессами

  •         Функции, связанные с процессами

    9 слайд

    Функции, связанные с процессами

  •         Функции, связанные с процессами

    10 слайд

    Функции, связанные с процессами

  •           Основные этапы создания процесса

    11 слайд

    Основные этапы создания процесса

  •         Выбор активируемого Windows- образа

    12 слайд

    Выбор активируемого Windows- образа

  •           Основные этапы создания процесса

    13 слайд

    Основные этапы создания процесса

  •        Этап 2: Создание объекта «процесс»

    14 слайд

    Этап 2: Создание объекта «процесс»

  •           Основные этапы создания процесса

    15 слайд

    Основные этапы создания процесса

  •            Этап 3: Создание первичного потока, его стека и контекста

    16 слайд

    Этап 3: Создание первичного потока, его стека и контекста

  •            Этап 3: Создание первичного потока, его стека и контекста

    17 слайд

    Этап 3: Создание первичного потока, его стека и контекста

  •            Этап 3: Создание первичного потока, его стека и контекста

    18 слайд

    Этап 3: Создание первичного потока, его стека и контекста

  •           Основные этапы создания процесса

    19 слайд

    Основные этапы создания процесса

  •        Этап 4: Уведомление подсистемы Windows о новом процессе

    20 слайд

    Этап 4: Уведомление подсистемы Windows о новом процессе

  •           Реакция ОС на уведомление о новом процессе

    21 слайд

    Реакция ОС на уведомление о новом процессе

  •          Реакция ОС на уведомление о новом процессе

    22 слайд

    Реакция ОС на уведомление о новом процессе

  •         Реакция ОС на уведомление о новом процессе

    23 слайд

    Реакция ОС на уведомление о новом процессе

  •           Основные этапы создания процесса

    24 слайд

    Основные этапы создания процесса

  • Тема: Процессы и потоки в ОС Windows NT2. Внутреннее устройство пото...

    25 слайд

    Тема: Процессы и потоки в ОС Windows NT
    2. Внутреннее устройство потоков.

  •            Блок потока исполнительной системы

    26 слайд

    Блок потока исполнительной системы

  • Схема блока потока ядра

    27 слайд

    Схема блока потока ядра

  •         Поля блока переменных окружения потока

    28 слайд

    Поля блока переменных окружения потока

  •            Утилиты для исследования потоков и функций

    29 слайд

    Утилиты для исследования потоков и функций

  •          Тема: Процессы и потоки в ОС Windows NT3. Планирование потоков.

    30 слайд

    Тема: Процессы и потоки в ОС Windows NT
    3. Планирование потоков.

  • Уровни приоритета потоков

    31 слайд

    Уровни приоритета потоков

  •            Взаимосвязь приоритетов в ядре и Windows API

    32 слайд

    Взаимосвязь приоритетов в ядре и Windows API

  •      Уровни прерываний и уровни приоритета

    33 слайд

    Уровни прерываний и уровни приоритета

  •        Состояния потоков в Windows XP

    34 слайд

    Состояния потоков в Windows XP

  •           Состояния потоков в Windows Server 2003

    35 слайд

    Состояния потоков в Windows Server 2003

  • База данных диспетчера ядра

    36 слайд

    База данных диспетчера ядра

  • Величины квантов2*3 = 6*10 мс = 60 мс (рабочая станция)
12*3 = 36*15 мс = 540...

    37 слайд

    Величины квантов
    2*3 = 6*10 мс = 60 мс (рабочая станция)
    12*3 = 36*15 мс = 540 мс (сервер)

  •        Самостоятельное переключение

    38 слайд

    Самостоятельное переключение

  •           Планирование потоков с вытеснением

    39 слайд

    Планирование потоков с вытеснением

  •          Планирование потоков в момент завершения кванта текущего потока

    40 слайд

    Планирование потоков в момент завершения кванта текущего потока

  • Рекомендованные приращения приоритета

    41 слайд

    Рекомендованные приращения приоритета

  •            Динамическое изменение приоритета

    42 слайд

    Динамическое изменение приоритета

    


ПРИМЕЧАНИЕ

Начальный квант по умолчанию в клиентских и серверных версиях Windows неодинаков. Подробнее о квантах см. раздел «Планирование потоков» далее в этой главе.

Этап 2D: инициализация адресного пространства процесса

   Подготовка адресного пространства нового процесса довольно сложна, поэтому разберем ее отдельно по каждой операции. Для максимального усвоения материала этого раздела вы должны иметь представление о внутреннем устройстве диспетчера памяти Windows (см. главу 7).
   
(o)
Диспетчер виртуальной памяти присваивает времени последнего усечения (last trim time) для процесса текущее время. Диспетчер рабочих наборов, выполняемый в контексте системного потока диспетчера настройки баланса (balance set manager), использует это значение, чтобы определить, когда нужно инициировать усечение рабочего набора.
   
(o)Диспетчер памяти инициализирует список рабочего набора процесса, после чего становится возможной генерация ошибок страниц.
   
(o)Раздел (созданный при открытии файла образа) проецируется на адресное пространство нового процесса, и базовый адрес раздела процесса приравнивается базовому адресу образа.
   
(o)Ha адресное пространство процесса проецируется Ntdll.dll.
   
(o)Ha адресное пространство процесса проецируются общесистемные таблицы NLS (national language support).

    

ПРИМЕЧАНИЕ

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

Этап 2E: формирование блока PEB

   

CreateProcessвыделяет страницу под PEB и инициализирует некоторые поля, описанные в таблице 6-6.

   Если в файле явно указаны значения версии Windows, эти данные замещают соответствующие начальные значения, показанные в таблице 6-6. Связь полей версии из заголовка образа с полями PEB описывается в таблице 6-7.

    
Таблица 6-7.
Windows-значения, заменяющие начальные значения полей PEB

 

Этап 2F: завершение инициализации объекта «процесс» исполнительной системы

   Перед возвратом описателя нового процесса выполняется несколько завершающих операций.
   1. Если общесистемный аудит процессов разрешен (через локальную политику безопасности или политику группы, вводимую контроллером домена), факт создания процесса отмечается в журнале безопасности.
   2. Если родительский процесс входил в задание, новый процесс тоже включается в это задание (о заданиях – в конце главы).
   3. Если в заголовке образа задан флаг IMAGE_FILE_UP_SYSTEM_ONLY (который указывает, что данную программу можно запускать только в однопроцессорной системе), для выполнения всех потоков процесса выбирается один процессор. Выбор осуществляется простым перебором доступных процессоров: при каждом запуске следующей программы такого типа выбирается следующий процессор. Благодаря этому подобные программы равномерно распределяются между процессорами.
   4. Если в образе явно указана маска привязки к процессорам (например, в поле конфигурационного заголовка), ее значение копируется в PEB и впоследствии устанавливается как маска привязки к процессорам по умолчанию.
   5.
CreateProcessпомещает блок нового процесса в конец списка активных процессов (
PsActiveProcessHead).
   6. Устанавливается время создания процесса, и вызвавшей функции (
CreateProcessв Kernel32.dll) возвращается описатель нового процесса.

Этап 3: создание первичного потока, его стека и контекста

   K началу третьего этапа объект «процесс» исполнительной системы полностью инициализирован. Однако у него еще нет ни одного потока, поэтому он не может ничего делать. Прежде чем создать поток, нужно создать стек и контекст, в котором он будет выполняться. Эта операция и является целью данного этапа. Размер стека первичного потока берется из образа – другого способа задать его размер нет.
   Далее создается первичный поток вызовом
NtCreateThread.Параметр потока – это адрес PEB (данный параметр нельзя задать при вызове
CreateProcess –только при вызове
CreateThread).Этот параметр используется кодом инициализации, выполняемым в контексте нового потока (см. этап 6). Однако поток по-прежнему ничего не делает – он создается в приостановленном состоянии и возобновляется лишь по завершении инициализации процесса (см. этап 5).
NtCreateThreadвызывает
PspCreateThread(функцию, которая используется и при создании системных потоков) и выполняет следующие операции.

    1. Увеличивается счетчик потоков в объекте «процесс».
   2. Создается и инициализируется блок потока исполнительной системы (ETHREAD).
   3. Генерируется идентификатор нового потока.
   4. B адресном пространстве пользовательского режима формируется TEB.
   5. Стартовый адрес потока пользовательского режима сохраняется в блоке ETHREAD. B случае Windows-потоков это адрес системной стартовой функции потока в Kernel32.dll
(BaseProcessStart
^Rпервого потока в процессе и
BaseThreadStartдля дополнительных потоков). Стартовый адрес, указанный пользователем, также хранится в ETHREAD, но в другом его месте; это позволяет системной стартовой функции потока вызвать пользовательскую стартовую функцию.
   6. Для подготовки блока KTHREAD вызывается
KeInitThread.Начальный и текущий базовые приоритеты потока устанавливаются равными базовому приоритету процесса; привязка к процессорам и значение кванта также устанавливаются по соответствующим параметрам процесса. Кроме того, функция определяет идеальный процессор для первичного потока. (O том, как происходит выбор идеального процессора см. раздел «Идеальный и последний процессоры» далее в этой главе.) Затем
KeInitThreadсоздает стек ядра для потока и инициализирует его аппаратно-зависимый контекст, включая фреймы ловушек и исключений. Контекст потока настраивается так, чтобы выполнение этого потока началось в режиме ядра в
KiThreadStartup.Далее
KeInitThreadустанавливает состояние потока в Initialized (инициализирован) и возвращает управление
PspCreateThread.
   7. Вызываются общесистемные процедуры, зарегистрированные на уведомление о создании потока.
   8. Маркер доступа потока настраивается как указатель на маркер доступа процесса. Затем вызывающая программа проверяется на предмет того, имеет ли она право создавать потоки. Эта проверка всегда заканчивается успешно, если поток создается в локальном процессе, но может дать отрицательный результат, если поток создается в другом процессе через функцию
CreateRemoteThreadи у создающего процесса нет привилегии отладки.
   9. Наконец, поток готов к выполнению.

Этап 4: уведомление подсистемы Windows о новом процессе

   Если заданы соответствующие правила, для нового процесса создается маркер с ограниченными правами доступа. K этому моменту все необходимые объекты исполнительной системы созданы, и Kernel32.dll посылает подсистеме Windows сообщение, чтобы она подготовилась к выполнению нового процесса и потока. Сообщение включает следующую информацию:
   
(o)описатели процесса и потока;
   
(o)флагисоздания;
   
(o)идентификатор родительского процесса;
   
(o)
флаг, который указывает, относится ли данный процесс к Windows-приложениям (что позволяет Csrss определить, показывать ли курсор запуска). Получив такое сообщение, подсистема Windows выполняет следующие операции.

    1.
CreateProcessдублирует описатели процесса и потока. Ha этом этапе счетчик числа пользователей процесса увеличивается с 1 (начального значения, установленного в момент создания процесса) до 2.
   2. Если класс приоритета процесса не указан,
CreateProcessустанавливает его в соответствии с алгоритмом, описанным ранее.
   3. Создается блок процесса Csrss.
   4. Порт исключений нового процесса настраивается как общий порт функций для подсистемы Windows, которая может таким образом получать сообщения при возникновении в процессе исключений (об обработке исключений см. главу 3).
   5. Если в данный момент процесс отлаживается (т. е. подключен к процессу отладчика), в качестве общего порта функций выбирается отладочный порт. Такой вариант позволяет Windows пересылать события отладки в новом процессе (генерируемые при создании и удалении потоков, при исключениях и т. д.) в виде сообщений подсистеме Windows, которая затем доставляет их процессу, выступающему в роли отладчика нового процесса.
   6. Создается и инициализируется блок потока Csrss.
   7.
CreateProcessвключает поток в список потоков процесса.
   8. Увеличивается счетчик процессов в данном сеансе.
   9. Уровень завершения процесса process shutdown level) устанавливается как 0x280 (это значение по умолчанию; его описание ищите в документации MSDN Library по ключевому слову
SetProcessShutdownParameters).
   10. Блок нового процесса включается в список общесистемных Windows-процессов.
   11. Создается и инициализируется структура данных fW32PROCESS), индивидуальная для каждого процесса и используемая той частью подсистемы Windows, которая работает в режиме ядра.
   12.Выводится курсор запуска в виде стрелки с песочными часами. Тем самым Windows говорит пользователю: «Я запускаю какую-то программу, но ты все равно можешь пользоваться курсором.» Если в течение двух секунд процесс не делает GUI-вызова, курсор возвращается к стандартному виду. A если за это время процесс обратился к GUI,
CreateProcessждет открытия им окна в течение пяти секунд и после этого восстанавливает исходную форму курсора.

Этап 5: запуск первичного потока

   K началу этого этапа окружение процесса уже определено, его потокам выделены ресурсы, у процесса есть поток, а подсистеме Windows известен факт существования нового процесса. Поэтому для завершения инициализации нового процесса (см. этап 6) возобновляется выполнение его первичного потока, если только не указан флаг CREATE_SUSPENDED.

Этап 6: инициализация в контексте нового процесса

   Новый поток начинает свою жизнь с выполнения стартовой процедуры потока режима ядра,
KiTbreadStartup,которая понижает уровень IRQL потока с «DPC/dispatch» до «APC», а затем вызывает системную стартовую процедуру потока,
PspUserTbreadStartup.Параметром этой процедуры является пользовательский стартовый адрес потока.
   B Windows 2000
PspUserTbreadStartupсначала разрешает расширение рабочего набора. Если создаваемый процесс является отлаживаемой программой, все его потоки (которые могли быть созданы на этапе 3) приостанавливаются. B отладочный порт процесса (порт функций подсистемы Windows, так как это Windows-процесс) посылается сообщение о создании процесса, чтобы подсистема доставила событие отладки CREATE_PROCESS_DEBUGINFO соответствующему отладчику. Далее
PspUserTbreadStartupждет пересылки подсистемой Windows ответа отладчика (через функцию
Conti
nueDebugEvent).Как только такой ответ приходит, выполнение всех потоков возобновляется.
   B Windows XP и Windows Server 2003
PspUserThreadStartupпроверяет, разрешена ли в системе предварительная выборка для приложений (application prefetching), и, если да, вызывает модуль логической предвыборки (logical prefetcher) для обработки файла команд предвыборки (prefetch instruction file) (если таковой есть), а затем выполняет предвыборку страниц, на которые процесс ссылался в течение первых десяти секунд при последнем запуске. Наконец,
PspUserThreadStartupставит APC пользовательского режима в очередь для запуска процедуры инициализации загрузчика образов
(LdrInitializeThunkиз Ntdll.dll). APC будет доставлен, когда поток попытается вернуться в пользовательский режим.
   Когда
PspUserThreadStartupвозвращает управление
KiTbreadStartup,та возвращается из режима ядра, доставляет APC и обращается к
LdrInitialize-Thunk.Последняя инициализирует загрузчик, диспетчер кучи, таблицы NLS, массив локальной памяти потока (thread-local storage, TLS) и структуры критической секции. После этого она загружает необходимые DLL и вызывает их точки входа с флагом DLL_PROCESS_ATTACH.
   Наконец, когда процедура инициализации загрузчика возвращает управление диспетчеру APC пользовательского режима, начинается выполнение образа в пользовательском режиме. Диспетчер APC вызывает стартовую функцию потока, помещенную в пользовательский стек в момент доставки APC.

Сборки, существующие в нескольких версиях

   Одна из проблем, уже давно изводившая пользователей Windows, – так называемый «DLL hell». Вы создаете этот ад, устанавливая приложение, которое заменяет одну или более базовых системных DLL, содержащих, например, стандартные элементы управления, исполняющую среду Microsoft Visual Basic или MFC Программы установки приложений делают такую замену, чтобы приложения работали корректно, но обновленные DLL могут оказаться несовместимыми с уже установленными приложениями.
   Эта проблема в Windows 2000 была отчасти решена, где модификация базовых системных DLL предотвращалась средством Windows File Protection, а приложениям разрешалось использовать собственные экземпляры этих DLL. Чтобы задействовать свой экземпляр какой-либо DLL вместо того, который находится в системном каталоге, у приложения должен быть файл
Application.exe.local(где
Application –имя исполняемого файла приложения); этот файл указывает загрузчику сначала проверить DLL-модули в каталоге приложения. Такой вид переадресации DLL позволяет избежать проблем несовместимости между приложениями и DLL, но больно бьет по принципу разделения DLL, ради которого DLL изначально и разрабатывались. Кроме того, любые DLL, загруженные из списка KnownDLLs (они постоянно проецируются в память) или, наоборот, загруженные ими, нельзя переадресовывать по такому механизму.
   Продолжая работу над решением этой проблемы, Microsoft ввела в Windows XP общие сборки (shared assemblies). Сборка (assembly) состоит из группы ресурсов, в том числе DLL и XML-файла манифеста, который описывает сборку и ее содержимое. Приложение ссылается на сборку через свой XML-манифест. Манифестом может быть файл в каталоге приложения с тем же именем, что и само приложение, но с добавленным расширением «.manifest» (например application.exe.ma-nifest), либо он может быть включен в приложение как ресурс. Манифест описывает приложение и его зависимости от сборок.
   Существует два типа сборок: закрытые (private) и общие (shared). Общие сборки отличаются тем, что они подписываются цифровой подписью; это позволяет обнаруживать их повреждение или модификацию. Помимо этого, общие сборки хранятся в каталоге Windows Winsxs, тогда как закрытые – в каталоге приложения. Таким образом, с общими сборками сопоставлен файл каталога (.cat), содержащий информацию о цифровых подписях. Общие сборки могут содержать несколько версий какой-либо DLL, чтобы приложения, зависимые от определенной версии этой DLL, всегда могли использовать именно ее.
   Обычно файлу манифеста сборки присваивается имя, которое включает имя сборки, информацию о версии, некий текст, представляющий уникальную сигнатуру, и расширение .manifest. Манифесты хранятся в каталоге WindowsWinsxsManifests, а остальные ресурсы сборки – в подкаталогах WindowsWinsxs с теми же именами, что и у соответствующих файлов манифестов, но без расширения .manifest.
   Пример общей сборки – 6-я версия DLL стандартных элементов управления Windows, comctl32.dll, которая является новинкой Windows XP. Ee файл манифеста называется WindowsWinsxsManifest x86_Microsoft.Windows.CommonControls_6595b64144ccfldf_6.0.0.0_x-ww_1382d70a.manifest. C ним сопоставлен файл каталога (с тем же именем, но с расширением .cat) и подкаталог в Winsxs, включающий comctl32.dll.
   Comctl32.dll версии 6 обеспечивает интеграцию с темами Windows XP и из-за того, что приложения, написанные без учета поддержки тем, могут неправильно выглядеть на экране при использовании этой новой DLL, она доступна только тем приложениям, которые явно ссылаются на ее общую сборку. Версия Comctl32.dll, установленная в Win-dowsSystem32, – это экземпляр версии 5.x, не поддерживающей темы. Загружая приложение, загрузчик ищет его манифест и, если таковой есть, загружает DLL-модули из указанной сборки. DLL, не включенные в сборки, на которые ссылается манифест, загружаются традиционным способом. Поэтому унаследованные приложения связываются с версией в WindowsSystem32, а новые приложения с поддержкой тем могут ссылаться на новую версию в своих манифестах.
   Чтобы увидеть, какой эффект дает манифест, указывающий системе задействовать новую библиотеку стандартных элементов управления в Windows XP, запустите User State Migration Wizard (WindowsSys
tem32UsmtMigwiz.exe) с файлом манифеста и без него.

    1. Запустите этот мастер и обратите внимание на темы Windows XP на кнопках в мастере.
   2. Откройте файл манифеста в Notepad и найдите упоминание 6-й версии библиотеки стандартных элементов управления.
   3. Переименуйте Migwiz.exe.manifest в Migwiz.exe.manifest.bak.
   4. Вновь запустите мастер и обратите внимание на кнопки без тем.
   5. Восстановите исходное имя файла манифеста.

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

Внутреннее устройство потоков

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

Структуры данных

   Ha уровне операционной системы поток представляется блоком потока, принадлежащим исполнительной системе (ETHREAD). Структура этого блока показана на рис. 6-7. Блок ETHREAD и все структуры данных, на которые он ссылается, существуют в системном адресном пространстве, кроме блока переменных окружения потока (thread environment block, TEB) – он размещается в адресном пространстве процесса. Помимо этого, процесс подсистемы Windows (Csrss) поддерживает параллельную структуру для каждого потока, созданного в Windows-процессе. Часть подсистемы Windows, работающая в режиме ядра (Win32k.sys), также поддерживает для каждого потока, вызывавшего USER- или GDI-функцию, структуру W32THREAD, на которую указывает блок ETHREAD.

   Поля блока потока, показанные на рис. 6-7, в большинстве своем не требуют дополнительных пояснений. Первое поле – это блок потока ядра (KTHREAD). За ним следуют идентификационные данные потока и процесса (включая указатель на процесс – владелец данного потока, что обеспечивает доступ к информации о его окружении), затем информация о защите в виде указателя на маркер доступа и сведения, необходимые для олицетворения (подмены одного процесса другим), а также поля, связанные с сообщениями LPC и незавершенными запросами на ввод-вывод. B таблице 6-8 даны ссылки на другие части книги, где некоторые из наиболее важных полей описываются подробнее. Чтобы получить более детальные сведения о внутренней структуре блока ETHREAD, используйте команду
dtотладчика ядра.

   Давайте повнимательнее присмотримся к двум ключевым структурам потока, упомянутым выше, – к блокам KTHREAD и TEB. Первый содержит информацию, нужную ядру Windows для планирования потоков и их синхронизации с другими потоками. Схема блока KTHREAD показана на рис. 6-8.

   Ключевые поля блока KTHREAD кратко рассмотрены в таблице 6-9.
   
Таблица 6-9.
Ключевые поля блока KTHREAD

    

ЭКСПЕРИМЕНТ: просмотр структур ETHREAD и KTHREAD

   Структуры ETHREAD и KTHREAD можно просмотреть с помощью команды
dtотладчика ядра. B следующем выводе показан формат ETHREAD:

   Для просмотра KTHREAD предназначена аналогичная команда:

    

ЭКСПЕРИМЕНТ: использование команды

!thread

отладчика ядра

   Команда
!threadотладчика ядра выдает дамп подмножества информации из структур данных потока. Отладчик ядра выводит ряд важных данных, не показываемых любыми другими утилитами: адреса внутренних структур, детальные сведения о приоритетах, данные стека, список незавершенных запросов на ввод-вывод и список ожидаемых объектов для тех потоков, которые находятся в состоянии ожидания.
   Чтобы получить информацию о потоке, используйте либо команду
!process(которая выводит все блоки потоков после блока процесса), либо команду
!thread(которая сообщает сведения только об указанном потоке). Ниже дан пример информации о потоке с пояснением ее важнейших полей.

Адрес Идентификатор ETHREAD потока Адрес TEB

    

ЭКСПЕРИМЕНТ: просмотр информации о потоке

   Утилита Tlist из Windows Debugging Tools позволяет получить подробную информацию о процессе, пример которой приведен ниже. Заметьте, что в списке потоков указывается «Win32StartAddress». Это адрес, передаваемый функции
CreateThread приложением.Остальные утилиты, кроме Process Explorer, показывающие стартовый адрес потока, выводят его истинный стартовый адрес, а не стартовый адрес, заданный приложением.

   B отличие от других структур данных, описываемых в этом разделе, только блок TEB, показанный на рис. 6-9, присутствует в адресном пространстве процесса, а не системы. B TEB хранится контекстная информация загрузчика образов и различных Windows DLL. Поскольку эти компоненты выполняются в пользовательском режиме, им нужны структуры данных, доступные для записи из этого режима. Вот почему TEB размещается в адресном пространстве процесса, а не системы, где он был бы доступен для записи только из режима ядра. Адрес TEB можно найти с помощью команды
!threadотладчика ядра.

    

ЭКСПЕРИМЕНТ: исследуем TEB

   Вы можете получить дамп структуры TEB, используя команду
!tebотладчика ядра. Ee вывод выглядит так:

 

Переменные ядра

 

   Как и в случае процессов, ряд переменных ядра Windows контролирует выполнение потоков. Список таких переменных, связанных с потоками, приводится в таблице 6-10.
   
Таблица 6-10.
Переменные ядра, относящиеся к потокам

 

Счетчики производительности

   Большая часть важной информации в структурах данных потоков экспортируется в виде счетчиков производительности, перечисленных в таблице 6-11. Даже используя только оснастку Performance, вы можете получить довольно много сведений о внутреннем устройстве потоков.

 

Сопутствующие функции

   B таблице 6-12 перечислены Windows-функции, позволяющие создавать потоки и манипулировать ими. Здесь не показаны функции, связанные с планированием и управлением приоритетами потоков, – они описываются в разделе «Планирование потоков» далее в этой главе.

 

Рождение потока

   Жизненный цикл потока начинается при его создании программой. Запрос на его создание в конечном счете поступает исполнительной системе Windows, где диспетчер процессов выделяет память для объекта «поток» и вызывает ядро для инициализации блока потока ядра. Ниже перечислены основные этапы создания потока Windows-функцией
CreateThread(которая находится в Kernel32.dll).
   1.
CreateThreadсоздает стек пользовательского режима в адресном пространстве процесса.
   2.
CreateThreadинициализирует аппаратный контекст потока, специфичный для конкретной архитектуры процессора. (Подробнее о блоке контекста потока см. раздел справочной документации Windows API по структуре CONTEXT.)
   3. Для создания объекта «поток» исполнительной системы вызывается
Nt-CreateThread.Он создается в приостановленном состоянии. Описание операций, выполняемых
NtCreateThread,см. в разделе «Что делает функция
CreateProcess»(этапы
3и
6)ранее в этой главе.
   4.
CreateThread уведомляетподсистему Windows о создании нового потока, и та выполняет некоторые подготовительные операции.
   5
.Вызвавшему коду возвращаются описатель и идентификатор потока (сгенерированный на этапе
3).
   6. Выполнение потока возобновляется, и ему может быть выделено процессорное время, если только он не был создан с флагом CREATE_SUSPENDED. Перед вызовом по пользовательскому стартовому адресу поток выполняет операции, описанные в разделе «Этап
3:создание первичного потока, его стека и контекста» ранее в этой главе.

Наблюдение за активностью потоков

   He только оснастка Performance, но и другие утилиты (таблица 6-13) позволяют получать сведения о состоянии потоков в Windows. ^Утилиты, показывающие информацию о планировании потоков, перечисляются в разделе «Планирование потоков» далее в этой главе.)

    

ПРИМЕЧАНИЕ

Чтобы получить информацию о потоке с помощью Tlist, введите tlistxxx, где xxx – имя образа процесса или заголовок окна (можно использовать символы подстановки).

   Process Explorer позволяет наблюдать за активностью потоков в процессе. Это особенно важно, когда вы пытаетесь понять, почему процесс зависает или запускается какой-то процесс, служащий хостом для множества сервисов (например, Svchost.exe, Dllhost.exe, Inetinfo.exe или System).
   Для просмотра потоков в процессе выберите этот процесс и откройте окно его свойств двойным щелчком. Потом перейдите на вкладку Threads. Ha этой вкладке отображается список потоков в процессе. Для каждого потока показывается процентная доля использованного процессорного времени (с учетом заданного интервала обновления), число переключений контекста для данного потока и его стартовый адрес. Поддерживается сортировка по любому из этих трех столбцов.

image Привет, Хаброжители! Ядро Windows таит в себе большую силу. Но как заставить ее работать? Павел Йосифович поможет вам справиться с этой сложной задачей: пояснения и примеры кода превратят концепции и сложные сценарии в пошаговые инструкции, доступные даже начинающим.

В книге рассказывается о создании драйверов Windows. Однако речь идет не о работе с конкретным «железом», а о работе на уровне операционной системы (процессы, потоки, модули, реестр и многое другое).

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

Глава 8

Уведомления потоков и процессов

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

В этой главе:

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

Уведомления процессов

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

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

Windows предоставляет другие механизмы уведомления о создании или уничтожении процессов. Например, с механизмом ETW (Event Tracing for Windows) такие уведомления могут приниматься процессами пользовательского режима (работающими с повышенными привилегиями). Впрочем, предотвратить создание процесса при этом не удастся. Более того, у ETW существует внутренняя задержка уведомлений около 1–3 секунд (по причинам, связанным с быстродействием), так что процесс с коротким жизненным циклом может завершиться до получения уведомления. Если в этот момент будет сделана попытка открыть дескриптор для созданного процесса, произойдет ошибка.

Основная функция API для регистрации уведомлений процессов PsCreateSetProcessNotifyRoutineEx определяется так:

NTSTATUS
PsSetCreateProcessNotifyRoutineEx (
    _In_ PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine,
    _In_ BOOLEAN Remove);

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

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

typedef void
(*PCREATE_PROCESS_NOTIFY_ROUTINE_EX) (
    _Inout_ PEPROCESS Process,
    _In_ HANDLE ProcessId,
    _Inout_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo);

Второй аргумент PsCreateSetProcessNotifyRoutineEx указывает, что делает драйвер — регистрирует обратный вызов или отменяет его регистрацию (FALSE — первое). Обычно драйвер вызывает эту функцию с аргументом FALSE в своей функции DriverEntry, а потом вызывает ту же функцию с аргументом TRUE в своей функции выгрузки.

Аргументы функции уведомления:

  • Process — объект создаваемого или уничтожаемого процесса.
  • ProcessId — уникальный идентификатор процесса. Хотя аргумент объявлен с типом HANDLE, на самом деле это идентификатор.
  • CreateInfo — структура с подробной информацией о создаваемом процессе. Если процесс уничтожается, то этот аргумент равен NULL.

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

В Windows 10 версии 1607 появилась другая функция для уведомлений процессов: PsCreateSetProcessNotifyRoutineEx2. Эта «расширенная» функция создает обратный вызов, сходный с предыдущим, но обратный вызов также активизируется для процессов Pico. Процессы Pico используются хост-процессами Linux для WSL (Windows Subsystem for Linux). Если драйвер заинтересован в таких процессах, он должен регистрироваться с расширенной функцией.

У драйвера, использующего эти обратные вызовы, должен быть установлен флаг IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY в заголовке PE (Portable Executable). Без установки флага вызов функции регистрации возвращает STATUS_ACCESS_DENIED (значение не имеет отношения к режиму тестовой подписи драйверов). В настоящее время Visual Studio не предоставляет пользовательского интерфейса для установки этого флага. Он должен задаваться в параметрах командной строки компоновщика ключом /integritycheck. На рис. 8.1 показаны свойства проекта при указании этого ключа.

image

Структура данных, предоставляемая для создания процесса, определяется следующим образом:

typedef struct _PS_CREATE_NOTIFY_INFO {
   _In_ SIZE_T Size;
   union {
       _In_ ULONG Flags;
       struct {
            _In_ ULONG FileOpenNameAvailable : 1;
            _In_ ULONG IsSubsystemProcess : 1;
            _In_ ULONG Reserved : 30;
        };
     };
     _In_ HANDLE ParentProcessId;
     _In_ CLIENT_ID CreatingThreadId;
     _Inout_ struct _FILE_OBJECT *FileObject;
     _In_ PCUNICODE_STRING ImageFileName;
     _In_opt_ PCUNICODE_STRING CommandLine;
     _Inout_ NTSTATUS CreationStatus;
} PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;

Описание важнейших полей этой структуры:

  • CreatingThreadId — комбинация идентификаторов потока и процесса, вызывающего функцию создания процесса.
  • ParentProcessId — идентификатор родительского процесса (не дескриптор). Этот процесс может быть тем же, который предоставляется CreateThreadId.UniqueProcess, но может быть и другим, так как при создании процесса может быть передан другой родитель, от которого будут наследоваться некоторые свойства.
  • ImageFileName — имя файла с исполняемым образом; доступен при установленном флаге FileOpenNameAvailable.
  • CommandLine — полная командная строка, используемая для создания процесса. Учтите, что он может быть равен NULL.
  • IsSubsystemProcess — этот флаг устанавливается, если процесс является процессом Pico. Это возможно только в том случае, если драйвер регистрируется PsCreateSetProcessNotifyRoutineEx2.
  • CreationStatus — статус, который будет возвращен вызывающей стороне. Драйвер может остановить создание процесса, поместив в это поле статус ошибки (например, STATUS_ACCESS_DENIED).

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

Реализация уведомлений процессов

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

Наш драйвер будет называться SysMon (хотя он никак не связан с программой SysMon из пакета Sysinternals). Он будет хранить всю информацию о создании/уничтожении в связном списке (с использованием структур LIST_ENTRY). Так как к связному списку могут одновременно обращаться несколько потоков, необходимо защитить его мьютексом или быстрым мьютексом; мы воспользуемся быстрым мьютексом, так как он более эффективен.

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

enum class ItemType : short {
    None,
    ProcessCreate,
    ProcessExit
};
struct ItemHeader {
    ItemType Type;
    USHORT Size;
    LARGE_INTEGER Time;
};

Приведенное выше определение перечисления ItemType использует новую возможность C++ 11 — перечисления с областью видимости (scoped enums). В таких перечислениях значения имеют область видимости (ItemType в данном случае). Также размер этих перечислений может быть отличен от int — short в данном случае. Если вы работаете на C, используйте классические перечисления или даже #define.

Структура ItemHeader содержит информацию, общую для всех типов событий: тип события, время события (выраженное в виде 64-разрядного целого числа) и размер полезных данных. Размер важен, так как каждое событие имеет собственную информацию. Если позднее вы захотите упаковать массив таких событий и (допустим) предоставить его клиенту пользовательского режима, клиент должен знать, где заканчивается каждое событие и начинается новое.

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

struct ProcessExitInfo : ItemHeader {
    ULONG ProcessId;
};

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

Если вы работаете на C, наследование вам недоступно. Впрочем, его можно имитировать — создайте первое поле типа ItemHeader, а затем добавьте конкретные поля; структура памяти остается одинаковой.

struct ExitProcessInfo {
    ItemHeader Header;
    ULONG ProcessId;
};

Для идентификатора процесса используется тип ULONG. Использовать тип HANDLE не рекомендуется, так как в пользовательском режиме он может создать проблемы. Кроме того, тип DWORD не используется, хотя в заголовках пользовательского режима тип DWORD (32-разрядное целое без знака) встречается часто. В заголовках WDK тип DWORD не определен. И хотя определить его явно нетрудно, лучше использовать тип ULONG — он означает то же самое, но определяется в заголовках как пользовательского режима, так и режима ядра.

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

В новом файле с именем SysMon.h определяется параметризованная структура, в которой хранится поле LIST_ENTRY с основной структурой данных:

template<typename T>
struct FullItem {
    LIST_ENTRY Entry;
    T Data;
};

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

struct FullProcessExitInfo {
     LIST_ENTRY Entry;
     ProcessExitInfo Data;
};

Также возможно наследовать от LIST_ENTRY, а затем добавить структуру ProcessExitInfo. Но такое решение менее элегантно, так как наши данные не имеют никакого отношения к LIST_ENTRY, поэтому расширение — искусственный прием, которого следует избегать.

Тип FullItem избавляет от хлопот с созданием этих отдельных типов.

Если вы используете C, то, естественно, решение с шаблонами будет недоступно, поэтому вам придется применить представленный структурный подход. Не буду снова упоминать C — всегда существует обходное решение, которым можно воспользоваться в случае необходимости.

Заголовок связанного списка должен где-то храниться. Мы создадим структуру данных для хранения всего глобального состояния драйвера (вместо набора отдельных переменных). Определение структуры выглядит так:

struct Globals {
    LIST_ENTRY ItemsHead;
    int ItemCount;
    FastMutex Mutex;
};

В определении используется тип FastMutex, который был разработан в главе 6. Также в определении встречается RAII-обертка AutoLock на C++ (тоже из главы 6).

Функция DriverEntry

Функция DriverEntry для драйвера SysMon похожа на одноименную функцию драйвера Zero из главы 7. В нее нужно добавить регистрацию уведомлений процессов и инициализацию объекта Globals:

Globals g_Globals;

extern "C" NTSTATUS
DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING) {
    auto status = STATUS_SUCCESS;
    InitializeListHead(&g_Globals.ItemsHead);
    g_Globals.Mutex.Init();

    PDEVICE_OBJECT DeviceObject = nullptr;
    UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\??\sysmon");
    bool symLinkCreated = false;

    do {
        UNICODE_STRING devName = RTL_CONSTANT_STRING(L"\Device\sysmon");
        status = IoCreateDevice(DriverObject, 0, &devName,
            FILE_DEVICE_UNKNOWN, 0, TRUE, &DeviceObject);
        if (!NT_SUCCESS(status)) {
            KdPrint((DRIVER_PREFIX "failed to create device (0x%08X)n",
               status));
               break;
          } 
          DeviceObject->Flags |= DO_DIRECT_IO;

          status = IoCreateSymbolicLink(&symLink, &devName);
          if (!NT_SUCCESS(status)) {
              KdPrint((DRIVER_PREFIX "failed to create sym link (0x%08X)n",
                 status));
              break;
          }
          symLinkCreated = true;

          // Регистрация для уведомлений процессов
          status = PsSetCreateProcessNotifyRoutineEx(OnProcessNotify, FALSE);
          if (!NT_SUCCESS(status)) {
              KdPrint((DRIVER_PREFIX "failed to register process callback (0x%08X)n",
              status));
          break;
         }
    } while (false);

    if (!NT_SUCCESS(status)) {
        if (symLinkCreated)
             IoDeleteSymbolicLink(&symLink);
        if (DeviceObject)
             IoDeleteDevice(DeviceObject);
     }
     DriverObject->DriverUnload = SysMonUnload;
     DriverObject->MajorFunction[IRP_MJ_CREATE] =
     DriverObject->MajorFunction[IRP_MJ_CLOSE] = SysMonCreateClose;
     DriverObject->MajorFunction[IRP_MJ_READ] = SysMonRead;
     return status;
}

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

Обработка уведомлений о выходе из процессов

В приведенном выше коде функция уведомления процессов называется OnProcessNotify, а ее прототип был представлен ранее в этой главе. Эта функция обратного вызова обрабатывает события создания и завершения процессов. Начнем с выхода из процессов, так как это событие намного проще создания процесса (как вы вскоре увидите). Общая схема функции обратного вызова выглядит так:

void OnProcessNotify(PEPROCESS Process, HANDLE ProcessId,
    PPS_CREATE_NOTIFY_INFO CreateInfo) {
    if (CreateInfo) {
       // Создание процесса
    }
    else {
      // Завершение процесса
    }
}

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

auto info = (FullItem<ProcessExitInfo>*)ExAllocatePoolWithTag(PagedPool,
   sizeof(FullItem<ProcessExitInfo>), DRIVER_TAG);
if (info == nullptr) {
   KdPrint((DRIVER_PREFIX "failed allocationn"));
   return;
}

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

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

auto& item = info->Data;
KeQuerySystemTimePrecise(&item.Time);
item.Type = ItemType::ProcessExit;
item.ProcessId = HandleToULong(ProcessId);
item.Size = sizeof(ProcessExitInfo);

PushItem(&info->Entry);

Сначала мы обращаемся к самому элементу данных (в обход LIST_ENTRY) через переменную info. Затем заполняется информация заголовка: тип элемента хорошо известен, так как текущей является ветвь, обрабатывающая уведомления о завершении процессов; время можно получить при помощи функции KeQuerySystemTimePrecise, возвращающей текущее системное время (UTC, не местное время) в формате 64-разрядного целого числа, с отчетом от 1 января 1601 года. Наконец, размер элемента — величина постоянная, равная размеру структуры данных, предоставляемой пользователю (а не размеру FullItem).

Функция API KeQuerySystemTimePrecise появилась в Windows 8. В более ранних версиях следует использовать функцию API KeQuerySystemTime.

Дополнительные данные при завершении процесса состоят из идентификатора процесса. В коде используется функция HandleToULong для корректного преобразования объекта HANDLE в 32-разрядное целое без знака.

А теперь остается добавить новый элемент в конец связного списка. Для этого мы определим функцию с именем PushItem:

void PushItem(LIST_ENTRY* entry) {
    AutoLock<FastMutex> lock(g_Globals.Mutex);
    if (g_Globals.ItemCount > 1024) {
       // Слишком много элементов, удалить самый старый
       auto head = RemoveHeadList(&g_Globals.ItemsHead);
       g_Globals.ItemCount--;
       auto item = CONTAINING_RECORD(head, FullItem<ItemHeader>, Entry);
       ExFreePool(item);
     }
     InsertTailList(&g_Globals.ItemsHead, entry);
     g_Globals.ItemCount++;
}

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

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

Реализуйте это ограничение с чтением из реестра в DriverEntry. Подсказка: используйте такие функции API, как ZwOpenKey или IoOpenDeviceRegistryKey, а также ZwQueryValueKey.

Если счетчик элементов превысил максимальное значение, самый старый элемент удаляется; фактически связанный список рассматривается как очередь (RemoveHeadList). При освобождении элемента его память должна быть освобождена. Указателем на элемент не обязательно должен быть указатель, изначально использованный для выделения памяти (хотя в данном случае это так, потому что объект LIST_ENTRY стоит на первом месте в структуре FullItem<>), поэтому для получения начального адреса объекта FullItem<> используется макрос CONTAINING_RECORD. Теперь элемент можно освободить вызовом ExFreePool.

На рис. 8.2 изображена структура объектов FullItem.

image

Наконец, драйвер вызывает InsertTailList, чтобы добавить элемент в конец списка, а счетчик элементов увеличивается на 1.

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

Обработка уведомлений о создании процессов

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

struct ProcessCreateInfo : ItemHeader {
    ULONG ProcessId;
    ULONG ParentProcessId;
    WCHAR CommandLine[1024];
};

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

Какие проблемы могут возникнуть при использовании приведенного определения?

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

А можно ли использовать решение следующего вида:

struct ProcessCreateInfo : ItemHeader {
    ULONG ProcessId;
    ULONG ParentProcessId;
    UNICODE_STRING CommandLine; // Будет работать?
};

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

Ниже приведен другой вариант, который мы используем в драйвере:

struct ProcessCreateInfo : ItemHeader {
     ULONG ProcessId;
     ULONG ParentProcessId;
     USHORT CommandLineLength;
     USHORT CommandLineOffset;
};

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

С таким объявлением можно приступить к построению реализации для создания процесса:

USHORT allocSize = sizeof(FullItem<ProcessCreateInfo>);
USHORT commandLineSize = 0;
if (CreateInfo->CommandLine) {
    commandLineSize = CreateInfo->CommandLine->Length;
    allocSize += commandLineSize;
}
auto info = (FullItem<ProcessCreateInfo>*)ExAllocatePoolWithTag(PagedPool,
    allocSize, DRIVER_TAG);
if (info == nullptr) {
    KdPrint((DRIVER_PREFIX "failed allocationn"));
return;
}

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

auto& item = info->Data;
KeQuerySystemTimePrecise(&item.Time);
item.Type = ItemType::ProcessCreate;
item.Size = sizeof(ProcessCreateInfo) + commandLineSize;
item.ProcessId = HandleToULong(ProcessId);
item.ParentProcessId = HandleToULong(CreateInfo->ParentProcessId);

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

Затем необходимо скопировать командную строку по адресу за базовой структурой, а также обновить длину и смещение:

if (commandLineSize > 0) {
    ::memcpy((UCHAR*)&item + sizeof(item), CreateInfo->CommandLine->Buffer,
        commandLineSize);
    item.CommandLineLength = commandLineSize / sizeof(WCHAR); // Длина в WCHAR
    item.CommandLineOffset = sizeof(item);
}
else {
    item.CommandLineLength = 0;
}
PushItem(&info->Entry);

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

Передача данных в пользовательский режим

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

Начнем обработку запроса чтения с получения адреса пользовательского буфера с применением прямого ввода/вывода (настраивается в DriverEntry):

NTSTATUS SysMonRead(PDEVICE_OBJECT, PIRP Irp) {
    auto stack = IoGetCurrentIrpStackLocation(Irp);
    auto len = stack->Parameters.Read.Length;
    auto status = STATUS_SUCCESS;
    auto count = 0;
    NT_ASSERT(Irp->MdlAddress); // Используем прямой ввод/вывод

    auto buffer = (UCHAR*)MmGetSystemAddressForMdlSafe(Irp->MdlAddress,
        NormalPagePriority);
     if (!buffer) {
        status = STATUS_INSUFFICIENT_RESOURCES;
}
else {

Теперь необходимо обратиться к связанному списку и извлечь элементы из заголовка:

AutoLock lock(g_Globals.Mutex); // C++ 17
while (true) {
    if (IsListEmpty(&g_Globals.ItemsHead)) // также можно проверить
                                       // g_Globals.ItemCount
        break;
    auto entry = RemoveHeadList(&g_Globals.ItemsHead);
    auto info = CONTAINING_RECORD(entry, FullItem<ItemHeader>, Entry);
    auto size = info->Data.Size;
    if (len < size) {
        // Пользовательский буфер заполнен, вставить элемент обратно
        InsertHeadList(&g_Globals.ItemsHead, entry);
        break;
    }
    g_Globals.ItemCount--;
    ::memcpy(buffer, &info->Data, size);
    len -= size;
    buffer += size;
    count += size;
    // Освободить данные после копирования
    ExFreePool(info);
}

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

Наконец, запрос завершается с текущим статусом, а в поле Information сохраняется значение переменной count:

Irp->IoStatus.Status = status;
Irp->IoStatus.Information = count;
IoCompleteRequest(Irp, 0);
return status;

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

void SysMonUnload(PDRIVER_OBJECT DriverObject) {
    // Отмена регистрации уведомлений процессов
    PsSetCreateProcessNotifyRoutineEx(OnProcessNotify, TRUE);

    UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\??\sysmon");
    IoDeleteSymbolicLink(&symLink);
    IoDeleteDevice(DriverObject->DeviceObject);

    // Освобождение оставшихся элементов
    while (!IsListEmpty(&g_Globals.ItemsHead)) {
       auto entry = RemoveHeadList(&g_Globals.ItemsHead);
       ExFreePool(CONTAINING_RECORD(entry, FullItem<ItemHeader>, Entry));
}
}

Клиент пользовательского режима

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

Функция main вызывает ReadFile в цикле с небольшой приостановкой, чтобы поток не потреблял ресурсы процессора постоянно. Поступившие данные отправляются для вывода:

int main() {
    auto hFile = ::CreateFile(L"\\.\SysMon", GENERIC_READ, 0,
         nullptr, OPEN_EXISTING, 0, nullptr);
    if (hFile == INVALID_HANDLE_VALUE)
         return Error("Failed to open file");

    BYTE buffer[1 << 16]; // 64-килобайтный буфер

    while (true) {
         DWORD bytes;
         if (!::ReadFile(hFile, buffer, sizeof(buffer), &bytes, nullptr))
            return Error("Failed to read");

         if (bytes != 0)
            DisplayInfo(buffer, bytes);
         ::Sleep(200);
     }
}

Функция DisplayInfo должна разобраться в структуре полученного буфера. Так как все события начинаются с общего заголовка, функция различает события по значению ItemType. После того как событие будет обработано, поле Size в заголовке указывает, где начинается следующее событие:

void DisplayInfo(BYTE* buffer, DWORD size) {
    auto count = size;
    while (count > 0) {
        auto header = (ItemHeader*)buffer;

        switch (header->Type) {
        case ItemType::ProcessExit:
        {
             DisplayTime(header->Time);
             auto info = (ProcessExitInfo*)buffer;
             printf("Process %d Exitedn", info->ProcessId);
             break;
         }

         case ItemType::ProcessCreate:
         {
              DisplayTime(header->Time);
              auto info = (ProcessCreateInfo*)buffer;
              std::wstring commandline((WCHAR*)(buffer +
                                           info->CommandLineOffset),
                     info->CommandLineLength);
               printf("Process %d Created. Command line: %wsn",
                                               info->ProcessId,
                      commandline.c_str());
                break;
           }
           default:
               break;
     }
     buffer += header->Size;
     count -= header->Size;
   }
}

Для правильного извлечения командной строки в коде используется конструктор класса C++ wstring, который может построить строку по указателю и длине строки. Вспомогательная функция DisplayTime форматирует время в виде, удобном для чтения:

void DisplayTime(const LARGE_INTEGER& time) {
    SYSTEMTIME st;
    ::FileTimeToSystemTime((FILETIME*)&time, &st);
    printf("%02d:%02d:%02d.%03d: ",
           st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
}

Драйвер устанавливается и запускается так, как было описано в главе 4.

sc create sysmon type= kernel binPath= C:BookSysMon.sys
sc start sysmon

Пример вывода, полученного при запуске SysMonClient.exe:

C:Book>SysMonClient.exe
12:06:24.747: Process 13000 Exited
12:06:31.032: Process 7484 Created. Command line: SysMonClient.exe
12:06:42.461: Process 3128 Exited
12:06:42.462: Process 7936 Exited
12:06:42.474: Process 12320 Created. Command line: "C:$WINDOWS.~BT
                                                    Sourcesmighost.
exe" {5152EFE5-97CA-4DE6-BBD2-4F6ECE2ABD7A} /InitDoneEvent:MigHost.
                                                    {5152EFE5-97CA-4D
E6-BBD2-4F6ECE2ABD7A}.Event /ParentPID:11908 /LogDir:"C:$WINDOWS.~BTSources
                                                     Panthe
r"
12:06:42.485: Process 12796 Created. Command line: ??C:WINDOWSsystem32
                                                     conhost.e
xe 0xffffffff -ForceV1
12:07:09.575: Process 6784 Created. Command line: "C:WINDOWSsystem32cmd.exe"
12:07:09.590: Process 7248 Created. Command line: ??C:WINDOWSsystem32
                                                     conhost.ex
e 0xffffffff -ForceV1
12:07:11.387: Process 7832 Exited
12:07:12.034: Process 2112 Created. Command line: C:WINDOWSsystem32
                                                     ApplicationFra
meHost.exe -Embedding
12:07:12.041: Process 5276 Created. Command line: "C:WindowsSystemApps
                                                     Microsoft.M
icrosoftEdge_8wekyb3d8bbweMicrosoftEdge.exe" -ServerName:MicrosoftEdge.
                                                     AppXdnhjhccw
3zf0j06tkg3jtqr00qdm0khc.mca
12:07:12.624: Process 2076 Created. Command line: C:WINDOWSsystem32
                                                     DllHost.exe /P
rocessid:{7966B4D8-4FDC-4126-A10B-39A3209AD251}
12:07:12.747: Process 7080 Created. Command line: C:WINDOWSsystem32
                                                     browser_broker
.exe -Embedding
12:07:13.016: Process 8972 Created. Command line: C:WINDOWSSystem32
                                                        svchost.exe -k
LocalServiceNetworkRestricted
12:07:13.435: Process 12964 Created. Command line: C:WINDOWSsystem32
                                                     DllHost.exe /
Processid:{973D20D7-562D-44B9-B70B-5A0F49CCDF3F}
12:07:13.554: Process 11072 Created. Command line: C:WINDOWSsystem32
                                                     Windows.WARP.
JITService.exe 7f992973-8a6d-421d-b042-6afd93a19631
S-1-15-2-3624051433-2125758914-1
423191267-1740899205-1073925389-3782572162-737981194
S-1-5-21-4017881901-586210945-2
666946644-1001 516
12:07:14.454: Process 12516 Created. Command line: C:WindowsSystem32RuntimeBroker.exe -Embedding
12:07:14.914: Process 10424 Created. Command line: C:WINDOWSsystem32
                                                    MicrosoftEdge
SH.exe SCODEF:5276 CREDAT:9730 APH:1000000000000017 JITHOST /prefetch:2
12:07:14.980: Process 12536 Created. Command line: "C:WindowsSystem32
                                                    MicrosoftEdg
eCP.exe" -ServerName:Windows.Internal.WebRuntime.ContentProcessServer
12:07:17.741: Process 7828 Created. Command line: C:WINDOWSsystem32
                                                    SearchIndexer.
exe /Embedding
12:07:19.171: Process 2076 Exited
12:07:30.286: Process 3036 Created. Command line: "C:WindowsSystem32
                                                    MicrosoftEdge
CP.exe" -ServerName:Windows.Internal.WebRuntime.ContentProcessServer
12:07:31.657: Process 9536 Exited

Уведомления потоков

Ядро предоставляет обратные вызовы создания и уничтожения потоков, аналогичные обратным вызовам процессов. Для регистрации используется функция API PsSetCreateThreadNotifyRoutine, а для ее отмены — другая функция, PsRemoveCreateThreadNotifyRoutine. В аргументах функции обратного вызова передается идентификатор процесса, идентификатор потока, а также флаг создания/уничтожения потока.

Расширим существующий драйвер SysMon, чтобы он получал не только уведомления процессов, но и уведомления потоков. Начнем с добавления значений перечисления и структуры, представляющей информацию, — все это добавляется в заголовочный файл SysMonCommon.h:

enum class ItemType : short {
    None,
    ProcessCreate,
    ProcessExit,
    ThreadCreate,
    ThreadExit
};
struct ThreadCreateExitInfo : ItemHeader {
    ULONG ThreadId;
    ULONG ProcessId;
};

Затем можно добавить вызов регистрации в DriverEntry, непосредственно за вызовом регистрации уведомлений процессов:

status = PsSetCreateThreadNotifyRoutine(OnThreadNotify);
if (!NT_SUCCESS(status)) {
    KdPrint((DRIVER_PREFIX "failed to set thread callbacks (status=%08X)n", status)
);
   break;
}

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

void OnThreadNotify(HANDLE ProcessId, HANDLE ThreadId, BOOLEAN Create) {
   auto size = sizeof(FullItem<ThreadCreateExitInfo>);
   auto info = (FullItem<ThreadCreateExitInfo>*)ExAllocatePoolWithTag(PagedPool,
        size, DRIVER_TAG);
    if (info == nullptr) {
        KdPrint((DRIVER_PREFIX "Failed to allocate memoryn"));
        return;
    }
    auto& item = info->Data;
    KeQuerySystemTimePrecise(&item.Time);
    item.Size = sizeof(item);
    item.Type = Create ? ItemType::ThreadCreate : ItemType::ThreadExit;
    item.ProcessId = HandleToULong(ProcessId);
    item.ThreadId = HandleToULong(ThreadId);

    PushItem(&info->Entry);
}

Большая часть кода выглядит довольно знакомо.

Чтобы завершить реализацию, мы добавим в клиент код для вывода информации о создании и уничтожении потоков (в DisplayInfo):

case ItemType::ThreadCreate:
{
    DisplayTime(header->Time);
    auto info = (ThreadCreateExitInfo*)buffer;
    printf("Thread %d Created in process %dn",
         info->ThreadId, info->ProcessId);
    break;
}
case ItemType::ThreadExit:
{
     DisplayTime(header->Time);
     auto info = (ThreadCreateExitInfo*)buffer;
     printf("Thread %d Exited from process %dn",
          info->ThreadId, info->ProcessId);
     break;
}

Пример вывода с обновленным драйвером и клиентом:

13:06:29.631: Thread 12180 Exited from process 11976
13:06:29.885: Thread 13016 Exited from process 8820
13:06:29.955: Thread 12532 Exited from process 8560
13:06:30.218: Process 12164 Created. Command line: SysMonClient.exe
13:06:30.219: Thread 12004 Created in process 12164
13:06:30.607: Thread 12876 Created in process 10728

...

13:06:33.260: Thread 4524 Exited from process 4484
13:06:33.260: Thread 13072 Exited from process 4484
13:06:33.263: Thread 12388 Exited from process 4484
13:06:33.264: Process 4484 Exited
13:06:33.264: Thread 4960 Exited from process 5776
13:06:33.264: Thread 12660 Exited from process 5776
13:06:33.265: Process 5776 Exited
13:06:33.272: Process 2584 Created. Command line: "C:$WINDOWS.~BTSources
                                                      mighost.e
xe" {CCD9805D-B15B-4550-94FB-B2AE544639BF} /InitDoneEvent:MigHost.
                                                     {CCD9805D-B15B-455
0-94FB-B2AE544639BF}.Event /ParentPID:11908 /LogDir:"C:$WINDOWS.~BTSources
                                                      Panther
"
13:06:33.272: Thread 13272 Created in process 2584
13:06:33.280: Process 12120 Created. Command line: ??C:WINDOWSsystem32
                                                               conhost.e
xe 0xffffffff -ForceV1
13:06:33.280: Thread 4200 Created in process 12120
13:06:33.283: Thread 4400 Created in process 12120
13:06:33.284: Thread 9632 Created in process 12120
13:06:33.284: Thread 6064 Created in process 12120
13:06:33.289: Thread 2472 Created in process 12120

Добавьте в клиент код вывода имени образа процесса при создании и завершении потока.

Уведомления о загрузке образов

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

Функция API PsSetLoadImageNotifyRoutine регистрируется для получения этих уведомлений, а функция PsRemoveImageNotifyRoutine отменяет регистрацию. Функция обратного вызова имеет следующий прототип:

typedef void (*PLOAD_IMAGE_NOTIFY_ROUTINE)(
    _In_opt_ PUNICODE_STRING FullImageName,
    _In_ HANDLE ProcessId, // pid, с которым связывается образ
    _In_ PIMAGE_INFO ImageInfo);

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

Аргумент FullImageName не так прост. Как указывает аннотация SAL, он необязателен и может содержать NULL. Но даже если он отличен от NULL, он не всегда содержит точное имя файла образа.

Причины кроются глубоко в ядре и выходят за рамки книги. В большинстве случаев решение работает нормально, а путь использует внутренний формат NT, начинающийся с «DeviceHadrdiskVolumex…» вместо «c:…». Преобразование может быть выполнено разными способами. Тема более подробно рассматривается в главе 11.

Аргумент ProcessId содержит идентификатор процесса, в котором загружается образ. Для драйверов (образов режима ядра) это значение равно нулю.

Аргумент ImageInfo содержит дополнительную информацию об образе; его объявление выглядит так:

#define IMAGE_ADDRESSING_MODE_32BIT 3

typedef struct _IMAGE_INFO {
    union {
      ULONG Properties;
      struct {
         ULONG ImageAddressingMode : 8; // Режим адресации
         ULONG SystemModeImage : 1; // Образ системного режима
         ULONG ImageMappedToAllPids : 1; // Образ отображается во все процессы
         ULONG ExtendedInfoPresent : 1; // Доступна структура IMAGE_INFO_EX
         ULONG MachineTypeMismatch : 1; // Несоответствие типа архитектуры
         ULONG ImageSignatureLevel : 4; // Уровень цифровой подписи
         ULONG ImageSignatureType : 3; // Тип цифровой подписи
         ULONG ImagePartialMap : 1; // Не равно 0 при частичном
                                                       отображении
         ULONG Reserved : 12;
     };
   };
   PVOID ImageBase;
   ULONG ImageSelector;
   SIZE_T ImageSize;
   ULONG ImageSectionNumber;
} IMAGE_INFO, *PIMAGE_INFO;

Краткая сводка важных полей структуры:

  • SystemModeImage — флаг устанавливается для образа режима ядра и сбрасывается для образа пользовательского режима.
  • ImageSignatureLevel — уровень цифровой подписи (Windows 8.1 и выше). См. описание констант SE_SIGNING_LEVEL_ в WDK.
  • ImageSignatureType — тип сигнатуры (Windows 8.1 и выше). См. описание перечисления SE_IMAGE_SIGNATURE_TYPE в WDK.
  • ImageBase — виртуальный адрес, по которому загружается образ.
  • ImageSize — размер образа.
  • ExtendedInfoPresent — если флаг установлен, IMAGE_INFO является частью большей структуры IMAGE_INFO_EX:

typedef struct _IMAGE_INFO_EX {
    SIZE_T Size;
    IMAGE_INFO ImageInfo;
    struct _FILE_OBJECT *FileObject;
} IMAGE_INFO_EX, *PIMAGE_INFO_EX;

Для обращения к большей структуре драйвер использует макрос CONTAINING_RECORD:

if (ImageInfo->ExtendedInfoPresent) {
    auto exinfo = CONTAINING_RECORD(ImageInfo, IMAGE_INFO_EX, ImageInfo);
    // Обращение к FileObject
}

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

Добавьте в драйвер SysMon уведомления о загрузке образов; драйвер должен собирать информацию только для образов пользовательского режима. Клиент должен выводить путь образа, идентификатор процесса и базовый адрес образа.

Упражнения

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

2. Напишите драйвер (или расширьте драйвер SysMon), который будет обнаруживать удаленное создание потоков, — то есть создание потоков в процессе, отличном от текущего. Подсказка: первый поток в процессе всегда создается «удаленно». Уведомите клиента пользовательского режима об этом событии. Напишите тестовое приложение, которое использует функцию CreateRemoteThread для тестирования.

Итоги

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

Более подробно с книгой можно ознакомиться на сайте издательства
» Оглавление
» Отрывок

Для Хаброжителей скидка 25% по купону — Windows

По факту оплаты бумажной версии книги на e-mail высылается электронная книга.


1


Управление процессами и потоками в Microsoft Windows Представление процессов Диспетчеризация процессов и потоков Планирование процессов и потоков


2


Характеристики процесса Процесс как самостоятельная сущность рассматривается на уровне архитектуры ЭВМ и на уровне ОС. На уровне архитектуры ЭВМ процесс характеризуется: — контекстом; — адресным пространством. На уровне ОС процесс описывается специальной структурой уровня ядра – PCB (Process Control Block)


3


Представление процесса в защищенном режиме процессора Intel GDTR LDTR TR CS GDT LDT Оперативная память TSS Сегмент кода Доп. сегмент основной сегменты кода, данных, стека Дескриптор TSS активного процесса Дескриптор сегмента кода активного процесса Дескриптор доп. сегмента Селектор сегмента кода Копия дескриптора TSS Селектор TSS


4


Адресное пространство процесса в защищенном режиме процессора Intel — Через дескрипторы в таблице LDT — Селекторы дополнительных сегментов хранятся в сегментных регистрах DS, ES, FS, GS, SS Могут содержать код и данные, в т.ч. собственные стеки процесса (задачи) Другие сегменты — Через дескриптор в таблице LDT — Селектор дескриптора всегда хранится в регистре CS Каждая задача обязательно должна иметь хотя бы один сегмент кода Сегмент кода — Через специальный системный дескриптор, который хранится в таблице GDT — Селектор дескриптора и копия самого дескриптора хранятся в регистре TR Task Status Segment – сегмент состояния задачи Хранит полное описание контекста задачи TSS АдресацияОписаниеСегмент


5


Контекст процесса в защищенном режиме процессора Intel — Регистры общего назначения (EAX, EBX, ECX, EDX, EDI, ESI, ESP, EBP); — Регистры состояния (EIP, EFLAGS); — Регистры состояния процессора (CR0-CR4); — Сегментные регистры (CS, DS, ES, FS, GS, SS); — Регистры управления памятью (LDTR, TR); — Значение поля селектора TSS из регистра TR для процесса, который запустил данный процесс; — Содержимое таблицы LDT; — Содержимое каталогов и таблиц страниц; — Адрес битовой карты ввода/вывода.


6


Представление процесса на уровне ОС В общем случае – информация о процессе (контекст процесса) ОС хранят в структурах PCB – Process Control Block (блок управления процессом) Структура PCB и состав контекста процесса различен для каждой конкретной ОС


7


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


8


Структуры Windows для управления процессами и потоками (обобщенная схема)


9


Структура EPROCESS


10


Блок процесса ядра (PCB)


11


Блок переменных окружения процесса (PEB)


12


Структура ETHREAD


13


Структура KTHREAD


14


Блок переменных окружения потока (TEB)


15


Уровни управления процессами Аппаратный уровеньРеализуются примитивные операции по управлению процессами: -прерывания; -переключение контекстов задач. Диспетчеризация процессов (уровень ядра) Реализуются примитивы перевода процессов из одного состояния в другое – запуск, приостановка, возобновление, остановка (завершение) Планирование процессов (уровень менеджера процессов) Реализуется строгий и сложный алгоритм управления состояниями процессов с учетом различных факторов, условий функционирования процессов, особенностей аппаратной части и операционной системы


16


Механизмы управления процессами, реализуемые аппаратно Базовые аппаратные механизмы Прерывания Переключение задач Все механизмы, реализуемые аппаратно, в ОС Microsoft Windows замаскированы уровнем HAL


17


Представление процесса на аппаратном уровне (CPU Intel) GDTR LDTR TR CS LDT Оперативная память TSS Сегмент кода Доп. сегмент основной сегменты кода, данных, стека Дескриптор TSS активного процесса Дескриптор сегмента кода активного процесса Дескриптор доп. сегмента Селектор сегмента кода Селектор TSS Копия дескриптора TSS


18


Реализация переключения задач в защищенном режиме CPU Intel Для команд CALL и JMP проверяет привилегии (CPL текущей задачи и RPL селектора новой задачи не могут быть больше, чем DPL шлюза или TSS, на который передается управление). Проверяется дескриптор TSS (его бит присутствия и лимит). Проверяется, что новый TSS, старый TSS и все дескрипторы сегментов находятся в страницах, отмеченных как присутствующие. Сохраняется состояние задачи. Загружается регистр TR. Если на следующих шагах происходит исключение, его обработчику придется доделывать переключение задач, вместо того чтобы повторять ошибочную команду. Тип новой задачи в дескрипторе изменяется на занятый и устанавливается флаг TS в CR0. Загружается состояние задачи из нового TSS: LDTR, CR3, EFLAGS, EIP, регистры общего назначения и сегментные регистры.


19


Механизмы диспетчеризации процессов уровня ядра Windows Создание процесса Создание потока Приостановка процесса Приостановка потока Активизация процесса Активизация потока Завершение процесса Завершение потока


20


Создание процесса в Windows и функция CreateProcess Открытие файла-образа процесса (имя.exe или имя.com) Создание объекта процесс исполнительной системы (PCB) Создание первичного потока Уведомление о создании процесса и первичного потока Начать выполнение первичного потока Инициализировать адресное пространство процесса в контексте нового процесса и потока, подгрузить динамические библиотеки (*.dll) Начать выполнение программы


21


Предварительные действия при запуске процесса Перед запуском процесса CreateProcess проверяет параметр CreationFlags, который указывается при запуске нового процесса и задает приоритеты процесса. Если задано несколько приоритетов, Windows выберет самый низкий из них Если приоритеты процесса не заданы, то Windows установит приоритет Normal Если приоритет процесса-создателя Idle или Bellow Normal, а приоритет нового процесса не указан, то Windows установит приоритет нового процесса равный приоритету процесса создателя Если создается процесс с приоритетом Real-Time, а создатель не имеет привилегий на создание таких процессов и/или привилегии Increase Scheduling Priority, то новый процесс будет создан с приоритетом High, что ниже приоритета Real-Time Происходит сопоставление окна процесса с объектом «рабочий стол»


22


Этап 1. Открытие файла-образа Поиск Windows-образа, который будет выполнять указанный процесс, и создание объекта «раздел» для последующего проецирования его на адресное пространство нового процесса В качестве имени Windows-образа выбирается первый параметр командной строки Проверка политики безопасности (не запрещает ли она запуск данного процесса) Если запускается программа Windows, то она используется напрямую Если запускается не-Windows приложение, то ОС ищет специальный образ поддержки (support image) для запуска данной программы


23


Этап 1. Открытие файла-образа


24


Этап 1. Открытие файла-образа: завершение Запускаемый файл успешно открыт Для него создан объект «раздел», но этот раздел еще не спроецирован в память Если в процессе открытия файла возникают проблемы, то Windows находит в реестре информацию об отладчике для данного типа приложений и повторно запускает CreateProcess


25


Этап 2. Создание объекта «процесс» Формируется блок EPROCESS Создается начальное адресное пространство процесса Инициализируется блок процесса ядра (KPROCESS) Инициализируется адресное пространство процесса, в т.ч. список рабочего набора и дескрипторы виртуального адресного пространства Образ процесса проецируется на адресное пространство Формируется блок PEB Завершается инициализация объекта «процесс» исполнительной системы Windows


26


Этап 2а: Формирование блока EPROCESS Создание блока EPROCESS От родительского процесса наследуется привязка к процессорам Устанавливаются минимальное и максимальное значения рабочего набора Настраивается блок квот нового процесса (устанавливается ссылка на блок квот родительского процесса и увеличивается счетчик ссылок последнего) Наследуется пространство имен устройств Windows Для нового процесса сохраняется идентификатор родительского процесса Настраивается маркер доступа Наследуется от родителей Если используется функция CreateProcessAsUser создается новый маркер Инициализируется таблица описателей Статус нового процесса заменяется на STATUS_PENDING


27


Этап 2b: создание начального адресного пространства процесса В соответствующих таблицах страниц формируются записи, позволяющие подкачивать начальные страницы процесса Значение максимально доступного количества резидентных страниц процесса уменьшается на значение количества страниц в рабочем наборе по умолчанию На адресное пространство проецируются страницы таблицы страниц для неподкачиваемого системного адресного пространства и системного кэша


28


Этап 2c: создание блока процесса ядра (KPROCESS) Инициализация блока KPROCESS Устанавливаются ссылки на блоки потоков ядра (KTHREAD) Устанавливаются ссылки на таблицы страниц процесса Инициализируются поля блока KPROCESS, хранящие информацию о времени выполнения процесса Устанавливается базовый приоритет процесса Устанавливается привязка процесса к процессорам Определяется базовое значение кванта времени для данного процесса


29


Этап 2d: инициализация адресного пространства процесса Диспетчер виртуальной памяти присваивает времени последнего усечения рабочего набора процесса текущее время Инициализируется список страниц рабочего набора Раздел проецируется на адресное пространство процесса, и базовый адрес раздела процесса приравнивается базовому адресу образа На адресное пространство процесса проецируется ntdll.dll На адресное пространство процесса проецируются таблицы NLS (National Language Support)


30


Этап 2е: инициализация блока PEB Формируется блок PEB Заполняются поля блока PEB


31


Этап 2f: завершение инициализации блока «процесс» исполнительной системы Если разрешен аудит, то факт создания процесса фиксируется в журнале безопасности Если родительский процесс входил в задание, то и новый процесс включается в задание Если для процесса был указан флаг создания, отмечающий, что данный процесс может выполняться только в однопроцессорной системе, то для выполнения всех потоков процесса выбирается один процессор Привязка процесса к процессору копируется в PEB и в дальнейшем используется как привязка по умолчанию Блок процесса помещается в конец списка активных процессов Устанавливается время создания процесса Возвращается дескриптор созданного процесса


32


Этап 3. Создание первичного потока К началу этого этапа процесс полностью создан, но не может ничего делать, т.к. у него нет ни одного потока Для создания потока необходимо создать стек и контекст, в котором он будет выполняться Первичный поток создается системным вызовом NtCreateThread с параметром PEB Поток будет создан в приостановленном состоянии и не будет ничего делать до окончания инициализации процесса


33


Этап 3. Создание первичного потока: NtCreateThread Увеличивает счетчик потоков для данного процесса Создает и инициализирует блок потока ETHREAD Генерирует идентификатор нового потока В пользовательском адресном пространстве формируется TEB Стартовый адрес потока пользовательского режима сохраняется в блоке ETHREAD Для Windows-потоков это адрес системной стартовой функции потока в kernel32.dll Стартовый адрес, указанный пользователем, также сохраняется в ETHREAD, но в другом месте, что позволяет системной функции вызвать пользовательскую стартовую функцию


34


Этап 3. Создание первичного потока: NtCreateThread (продолжение) Инициализируется блок KTHREAD, в который заносятся параметры, необходимые для диспетчеризации и планирования потоков Вызываются общесистемные функции для уведомления о создании потока Настраиваются маркеры доступа потока Поток готов к выполнению


35


Этап 4. Уведомление подсистемы Windows о новом процессе К данному моменту созданы и процесс, и его первичный поток Kernel32.dll отправляет подсистеме Windows уведомление: Описатели процесса и потока Флаги создания Идентификатор родительского процесса Флаг принадлежности процесса к Windows- приложениям Получив уведомление Windows должна подготовиться к выполнению нового процесса и потока


36


Этап 4. Уведомление подсистемы Windows о новом процессе CreateProcess дублирует описатели процесса и потока, в результате чего количество пользователей процесса становится равным 2 Устанавливается класс приоритета процесса Создается и инициализируется блок процесса Csrss Настраивается порт исключений процесса как общий порт функций для подсистемы Windows. Таким образом, она сможет получать сообщения о возникновении исключений Если процесс в данный момент отлаживается, то в качестве порта функций выбирается отладочный порт Создается и инициализируется блок потока Csrss


37


Этап 4. Уведомление подсистемы Windows о новом процессе (продолжение) CreateProcess включает поток в список потоков процесса Увеличивается счетчик процессов в данном сеансе Устанавливается уровень завершения процесса по умолчанию 0х280 Блок нового процесса включается в список общесистемных процессов Windows Создается и инициализируется структура W32PROCESS, индивидуальная для каждого процесса и используемся той частью процесса, которая выполняется в режиме ядра Выводится курсор в виде стрелки с песочными часами – система ждет от процесса GUI-вызова


38


Этап 5. Запуск первичного потока Для завершения инициализации процесса необходимо возобновить его первичный поток (который был создан, как приостановленный), если при этом не указан флаг CREATE_SUSPENDED


39


Этап 6. Инициализация в контексте нового процесса Начинается выполнение первичного потока нового процесса Происходит системный вызов KiThreadStartup, который понижает уровень IRQL с DPC/dispatch до APC Вызывается системная функция PspUserThreadStartup, параметром которой являтся стартовый адрес потока Функция PspUserThreadStartup проверяет, разрешена ли в системе предвыборка и, если да, то производит предвыборку страниц процесса, которые он запускал в течении первых 10 сек при предыдущем запуске Затем функция PspUserThreadStartup ставит APC пользовательского режима в очередь для запуска процедуры инициализации потока


40


Этап 6. Инициализация в контексте нового процесса (продолжение) Когда поток вернется в пользовательский режим, APC будет доставлен После завершения PspUserThreadStartup управление передается KiThreadStartup, которая возвращается из режима ядра, доставляет APC, инициализирует загрузчик, кучи, таблицы NLS, массивы локальной памяти потока и структуры критической секции Функция KiThreadStartup загружает необходимые dll и вызывает их точки входа Управление возвращается диспетчеру APC пользовательского режима Начинается выполнение образа в пользовательском режиме


41


Создание потока в Windows и функция CreateThread Создание стека пользовательского режима в адресном пространстве процесса Инициализация аппаратного контекста потока Создать объект потока исполнительной системы (в приостановленном состоянии) Уведомление Windows о создании нового потока Вернуть порождающему процессу (потоку) идентификатор и дескриптор (HANDLE) созданного потока Начать выполнение потока


42


Планирование потоков в Windows Дисциплина планирования Windows реализована на основе вытесняющего планирования В системе используются приоритеты Всегда первым вытесняется готовый к выполнению поток, обладающий наибольшим приоритетом На выбор потоков к выполнению также влияет привязка потоков (а точнее – процессов) к процессору Для управления временем выполнения потока используется система квантования времени Величина кванта не постоянна и зависит от Конфигурационных параметров системы Статуса процесса (активный или фоновый) Использованием объекта «задание»


43


Диспетчеризация на уровне ядра – диспетчер ядра Диспетчеризация может быть вызвана одним из следующих событий: Поток готов к выполнению – только что создан или вышел из состояния ожидания Поток вышел из состояния Running (истек квант времени или поток переходит в состояние ожидания) Изменяется приоритет процесса из-за системного вызова или самой Windows Изменена привязка к процессорам, из-за чего процесс не может больше выполняться на данном процессоре


44


Уровни приоритета


45


Назначение приоритетов с точки зрения WindowsAPI По приоритету процесса Real-time High Above normal Normal Bellow normal Idle По относительному приоритету потоков в рамках процесса Time-critical Highest Above normal Normal Bellow normal Lowest Idle


46


Связь между приоритетами WindowsAPI и ядра Windows


47


Установка базового приоритета процессов и потоков Базовый приоритет процесса устанавливается при его создании (параметром CreationFlags функции CreateProcess) Если базовый приоритет процесса не указан, то он наследуются от родительского процесса (как правило, он устанавливается в Normal) Базовый приоритет потока устанавливается в соответствии с унаследованным приоритетом процесса и собственным относительным приоритетом Кроме базового приоритета, каждый поток имеет текущий приоритет, на основании которого принимаются все решения относительно планирования потока


48


Состояния потоков в ОС Windows 2000/XP


49


Состояния потоков в OC Windows Server 2003


50


База данных диспетчера ядра Для каждого уровня приоритетов – своя очередь 32-битна маска Бит устанавливается в 1, если в соответствующей очереди есть поток


51


Квант Квант – интервал процессорного времени, отводимый Windows для выполнения потока По истечении кванта Windows пытается передать управление потоку с тем же приоритетом. Если такого нет, очередной квант отводится тому же потоку, что и выполнялся В Windows 2000/XP величина кванта по умолчанию составляет 2 интервала таймера В Windows Server 2003 – 12 интервалов (увеличен, чтобы минимизировать переключения контекста) Длительность интервала таймера определяется HAL, а не ядром


52


Учет квантов Величина кванта для каждого процесса хранится в блоке процесса ядра в виде количество_интервалов * 3 Это значение кванта используется при запуске потоков Во время выполнения потока величина кванта уменьшается по таймеру, каждый раз на 3 Если поток прерывается, квант выполнения потока все равно уменьшается на 3 при каждом прерывании таймера Для ожидающих потоков (WaitForSingleObject или WaitForMultileObjects) величина кванта уменьшается всегда, даже если они сразу получили доступ к ожидаемому объекту (при этом квант уменьшается на 1)


53


Изменение величины кванта Пользователь может изменить величину кванта, указав одно из двух значений – короткий (2) или длинный (12) в оснастке Performance Options Начиная с Windows NT 4.0 Workstation система использует алгоритм динамического изменения квантов для активных процессов (со статусом выше Idle). Если хотя бы один поток процесса использует активное окно, то его квант времени увеличивается в 3 раза Управление квантами возможно через реестр Windows


54


Сценарии планирования Самостоятельное переключение Вытеснение Завершение кванта Завершение потока


55


Самостоятельное переключение Поток самостоятельно переходит в состояние ожидания (WaitForSingleObject или WaitForMultipleObjects) Система запускает первый готовый поток с наивысшим приоритетом


56


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


57


Завершение кванта Для данного потока Windows не понизила приоритет – он остался в конце своей очереди При завершении кванта Windows может понизить приоритет приостановленного потока. Тогда она будет искать для выполнения новый поток с приоритетом, не ниже нового приоритета приостановленного потока


58


Динамическое повышение приоритета После завершения операций ввода-вывода По окончании ожидания на семафоре или событии исполнительной системы По окончании операции ожидания потоками активного процесса При пробуждении GUI-процессов в связи с операциями с окнами Если поток, готовый к выполнению, задерживается из-за нехватки процессорного времени


59


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


60


Изменение приоритета после завершения операций ввода-вывода


61


Повышение приоритета после завершения ожидания на семафорах и событиях Приоритет потока повышается на 1, если он ожидал семафора или события (функции WaitForSingleObject или WaitForMultipleObjects), после выполнения функций SetEvent, PulseEvent, ReleaseSemaphore Правила повышения приоритета аналогичны рассмотренным ранее


62


Повышение приоритетов потоков активного процесса после завершения ожидания Текущий приоритет потока в активном процессе всегда повышается на величину PsPrioritySeparation, если он завершил ожидание на объекте ядра Фактически PsPrioritySeparation – индекс в таблице квантов и используется для выбора величины квантов для потоков активного процесса Но в данном случае эта величина (величина кванта) используется как величина изменения приоритета Используется для повышения отзывчивости интерактивных приложений


63


Повышение приоритетов при пробуждении GUI-процессов в связи с операциями с окнами Приоритет потоков, владеющих окнами, повышается на 2 после их пробуждения из-за активности системы управления окнами Используется для повышения отзывчивости интерактивных приложений


64


Повышение приоритетов при нехватке процессорного времени Применяется при блокировке потоков с низким приоритетом потоками с высокими приоритетами (тупики) Диспетчер настройки баланса раз в 1 сек сканирует очереди, чтобы обнаружить тупиковые ситуации При обнаружении блокированных потоков, которые в свою очередь блокируют другие потоки, Windows повышает их приоритет до 15 В Windows 2000/XP квант времени таких потоков удваивается В Windows Server 2003 квант времени для таких потоков устанавливается равным 4


65


Планирование потоков в многопроцессорных системах База данных диспетчера ядра включает в себя несколько стеков очередей – по одной для каждого процессора В БД включается маска активных процессоров, позволяющая определить количество доступных процессоров В системах с HyperThreading Количество логических процессоров не попадает под лицензионные ограничения на количество поддерживаемых процессоров При выборе логического процессора для запуска потока система учитывает, на каком физическом процессоре он реализуется Нагрузка на процессоры рассчитывается на основании физической привязки логических процессоров


66


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


67


Выбор процессора для потока при наличии простаивающих процессоров При наличии простаивающих процессоров Windows пытается подключить поток к ним в следующем порядке: Идеальный Предыдущий Текущий В Windows 2000 выбирается первый простаивающий В Windows XP и Windows Server 2003 Анализируется маска привязки потока Если система с Hyperthreading и имеется простаивающий физический процессор, то список доступных процессоров сокращается для набора его логических Из полученного списка исключаются спящие процессоры Из списка выбирается процессор с наименьшим номером


68


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


69


Задания Задание – объект ядра, обеспечивающий управление одним или несколькими процессами, как группой Задание позволяет определить: Максимальное число процессов в группе Общий лимит на процессорное время (для всех процессов задания) в пользовательском режиме Индивидуальные лимиты процессов на процессорное время в пользовательском режиме Класс планирования задания (размер кванта для потоков процессов, входящих в задание) Привязку к процессорам Приоритет всех процессов задания Максимальный и минимальный размеры рабочего набора Лимит на виртуальную память

Понравилась статья? Поделить с друзьями:
  • Последовательность загрузки windows 10 после загрузки диспетчера загрузки bootmgr exe
  • Последняя версия vivaldi для windows xp
  • Последняя версия rdp клиента windows xp
  • Последовательность десятичных числовых кодов в кодировке windows онлайн
  • Последняя версия pycharm для windows 7