ПРИМЕЧАНИЕ Начальный квант по умолчанию в клиентских и серверных версиях 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. Манифесты хранятся в каталоге \Windows\Winsxs\Manifests, а остальные ресурсы сборки – в подкаталогах \Windows\Winsxs с теми же именами, что и у соответствующих файлов манифестов, но без расширения .manifest.
   Пример общей сборки – 6-я версия DLL стандартных элементов управления Windows, comctl32.dll, которая является новинкой Windows XP. Ee файл манифеста называется \Windows\Winsxs\Manifest\ 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-dows\System32, – это экземпляр версии 5.x, не поддерживающей темы. Загружая приложение, загрузчик ищет его манифест и, если таковой есть, загружает DLL-модули из указанной сборки. DLL, не включенные в сборки, на которые ссылается манифест, загружаются традиционным способом. Поэтому унаследованные приложения связываются с версией в \Windows\System32, а новые приложения с поддержкой тем могут ссылаться на новую версию в своих манифестах.
   Чтобы увидеть, какой эффект дает манифест, указывающий системе задействовать новую библиотеку стандартных элементов управления в Windows XP, запустите User State Migration Wizard (\Windows\Sys tem32\Usmt\Migwiz.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 этой вкладке отображается список потоков в процессе. Для каждого потока показывается процентная доля использованного процессорного времени (с учетом заданного интервала обновления), число переключений контекста для данного потока и его стартовый адрес. Поддерживается сортировка по любому из этих трех столбцов.