Файл: Debian Таненбаум Бос.pdf

ВУЗ: Не указан

Категория: Книга

Дисциплина: Операционные системы

Добавлен: 29.10.2018

Просмотров: 48008

Скачиваний: 190

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
background image

996  

 Глава 11. Изучение конкретных примеров: Windows 8 

Функция Win32 API

Описание

ReleaseSemaphore

Увеличить счетчик семафора на 1

EnterCriticalSection

Установить блокировку на критической секции

LeaveCriticalSection

Снять блокировку с критической секции

WaitOnAddress

Блокироваться, пока не будет изменено значение памяти по указан-
ному адресу

WakeByAddressSingle

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

WakeByAddressAll

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

InitOnceExecuteOnce

Обеспечить однократное выполнение подпрограммы инициализации

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

11.4.3. Реализация процессов и потоков

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

Процесс создается тогда, когда другой процесс делает вызов CreateProcess интерфейса 
Win32. Этот вызов запускает процедуру (пользовательского режима) из kernel.dll, 
которая осуществляет вызов NtCreateUserProcess в ядре, чтобы за несколько этапов 
создать процесс:

1.  Преобразуется имя исполняемого файла (заданное в виде параметра) из маршрута 

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

2.  Собираются все параметры создания процесса и передаются (вместе с полным 

маршрутом к исполняемой программе) собственному интерфейсу NtCreate-
UserProcess
.

3.  Работая в режиме ядра, NtCreateUserProcess обрабатывает параметры, а затем 

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

Таблица 11.12 (продолжение)


background image

11.4. Процессы и потоки в Windows   

997

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

4.  Диспетчер процессов выделяет и инициализирует объект процесса (структуру 

данных ядра, представляющую процесс как для ядра, так и для исполнительного 
уровня).

5.  Диспетчер памяти создает адресное пространство для нового процесса, выделяя 

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

6.  Для нового процесса создается таблица описателей, в которую дублируются все те 

описатели вызвавшей стороны, для которых разрешается наследование.

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

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

8.  Исполнительный уровень создает и инициализирует блок Process Environment 

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

9.  В новом процессе выделяется виртуальная память, которая используется для пере-

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

10. Из 

специальной таблицы описателей (которую поддерживает ядро для эффектив-

ного выделения локально-уникальных идентификаторов процессов и потоков) 
выделяется идентификатор процесса.

11.  Выделяется и инициализируется объект потока. Выделяется стек пользователь-

ского режима и блок Thread Environment Block (TEB). Инициализируется запись 
CONTEXT, которая содержит начальные значения регистров процессора для по-
тока (в том числе указатели команд и стека).

12.  Объект процесса добавляется в глобальный список процессов. В таблице опи-

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

13.  NtCreateUserProcess возвращается в пользовательский режим с созданным новым 

процессом, содержащим единственный поток, который готов к работе, но нахо-
дится в состоянии приостановки.

14.  Если интерфейс NT API дает сбой, то код Win32 проверяет, не принадлежит ли 

данный процесс к другой подсистеме (например, WOW64). Или, возможно, данная 
программа помечена для выполнения под управлением отладчика. Эти специ-
альные случаи обрабатываются специальным кодом пользовательского режима 
в CreateProcess.


background image

998  

 Глава 11. Изучение конкретных примеров: Windows 8 

15. Если 

NtCreateUserProcess отработал успешно, то нужно сделать еще кое-что. Про-

цессы Win32 нужно зарегистрировать в процессе csrss.exe подсистемы Win32. 
Kernel32.dll посылает сообщение в csrss, которое сообщает ему о новом процессе 
(а также передает описатели процесса и потока, чтобы он мог себя сдублировать). 
Процесс и потоки вносятся в таблицы подсистемы (чтобы там имелся полный 
список всех процессов и потоков Win32). Затем подсистема показывает курсор 
(указатель с песочными часами), чтобы сообщить пользователю о том, что в дан-
ный момент что-то происходит, но курсор все же можно использовать. Когда про-
цесс делает свой первый вызов графического интерфейса пользователя (обычно 
это делается для создания окна), то курсор исчезает (если нет других вызовов) — 
тайм-аут у него 2 с.

16. Если 

процесс 

ограничен (как имеющий низкие права Internet Explorer), то маркер 

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

17. Если приложение было помечено как подлежащее исправлению (shimmed) для 

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

18. И 

наконец, вызов NtResumeThread для отмены приостановки потока и возвращения 

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

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

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

Планирование

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

1.  Выполняющийся поток блокируется на семафоре, мьютексе, событии, вводе-вы-

воде и т. д.

2.  Поток подает сигнал об объекте (например, выставляет up на семафоре).

3.  Истекает квант времени потока.

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


background image

11.4. Процессы и потоки в Windows   

999

поэтому вызывает код планировщика для выбора своего преемника и загружает запись 
CONTEXT этого потока для продолжения его выполнения.

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

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

Планировщик вызывается также в двух других случаях:

1.  Завершается операция ввода-вывода.

2.  Истекает время ожидания.

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

Если в течение этого тайм-аута поток стал готовым, то будет выполнен планировщик, 
и если новый готовый к выполнению поток имеет более высокий приоритет, то теку-
щий поток вытесняется (как в случае 1).

Теперь мы дошли до самого алгоритма планирования. Интерфейс Win32 API предостав-
ляет два API для работы с планированием потоков. Первый — вызов SetPriorityClass
который устанавливает класс приоритета для всех потоков вызывающего процесса. 
Допустимые значения: real-time, high, above normal, normal и idle. Класс приоритета 
определяет относительный приоритет процесса. Класс приоритета процесса может 
также использоваться процессом для того, чтобы временно пометить самого себя как 
фоновый, — это значит, что он не должен мешать никакой другой активности систе-
мы. Обратите внимание на то, что класс приоритета устанавливается для процесса, но 
влияет на реальный приоритет каждого потока процесса (он устанавливает базовое 
значение приоритета, с которым стартует поток при создании).


background image

1000  

 Глава 11. Изучение конкретных примеров: Windows 8 

Второй интерфейс Win32 API — это SetThreadPriority. Он устанавливает относительный 
приоритет потока (возможно, вызывающего потока, но это не обязательно) по отноше-
нию к классу приоритета своего процесса. Допустимые значения: time critical, highest, 
above normal, normal, below normal, lowest и idle. Потоки time critical получают самый 
высокий приоритет планирования, а потоки idle — самый низкий (независимо от класса 
приоритета). Остальные значения приоритета подстраивают базовый приоритет потока 
относительно нормального значения, определенного классом приоритета (+2, +1, 0, –1, 
–2 соответственно). Использование классов приоритета и относительных приоритетов 
потоков облегчает приложениям принятие решений по указанию приоритетов.

Планировщик работает следующим образом. В системе имеется 32 приоритета с но-
мерами от 0 до 31. Сочетание класса приоритета и относительного приоритета отобра-
жается на 32 абсолютных значения приоритета (в соответствии с табл. 11.13). Номер 
в таблице определяет базовый приоритет (base priority) потока. Кроме того, каждый 
поток имеет текущий приоритет (current priority), который может быть выше (но не 
ниже) базового приоритета и который мы скоро обсудим.

Таблица 11.13. Соответствие приоритетов Win32 приоритетам Windows

Приоритеты потоков 

Win32

Классы приоритетов процессов Win32

Real-time

High

Above 

Normal

Normal

Below 

Normal

Idle

Time critical

31

15

15

15

15

15

Highest

26

15

12

10

8

6

Above normal

25

14

11

9

7

5

Normal

24

13

10

8

6

4

Below normal

23

12

9

7

5

3

Lowest

22

11

8

6

4

2

Idle

16

1

1

1

1

1

Для использования этих приоритетов при планировании система поддерживает массив 
из 32 списков потоков, соответствующих всем 32 приоритетам (от 0 до 31) в табл. 11.13. 
Каждый список содержит готовые потоки соответствующего приоритета. Базовый ал-
горитм планирования делает поиск по массиву от приоритета 31 до приоритета 0. Как 
только будет найден непустой список, поток выбирается сверху списка и выполняется 
в течение одного кванта. Если квант истекает, то поток переводится в конец очереди 
своего уровня приоритета и следующим выбирается верхний поток списка. Иначе 
говоря, когда есть много готовых потоков на самом высоком уровне приоритета, они 
выполняются циклически (по одному кванту времени каждый). Если готовых потоков 
нет, то процессор переходит в состояние ожидания, то есть переводится в состояние 
более низкого энергопотребления и ждет прерывания.

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