Добавлен: 29.10.2018
Просмотров: 48012
Скачиваний: 190
986
Глава 11. Изучение конкретных примеров: Windows 8
В современной Windows задания используются для группировки процессов, выполня-
ющих приложения. Процессы, содержащие выполняемое приложение, должны быть
идентифицированы операционной системой, чтобы она смогла управлять всем при-
ложением от имени пользователя.
На рис. 11.12 показана связь между заданиями (jobs), процессами (processes), потоками
(threads) и волокнами (fibers). Задания содержат процессы. Процессы содержат потоки.
Но потоки не содержат волокон. Связь между потоками и волокнами обычно имеет
тип «многие-ко-многим».
Рис. 11.12. Связь между заданиями, процессами, потоками и волокнами. Задания и волокна
не обязательны: не все процессы находятся в заданиях или содержат волокна
Волокна создаются путем выделения места в стеке и структуры данных волокна в поль-
зовательском режиме (для хранения регистров и данных, связанных с этим волокном).
Потоки преобразуются в волокна, однако волокна могут создаваться и независимо от
потоков. Такие волокна не будут выполняться до тех пор, пока уже выполняющееся
в потоке волокно не вызовет явно SwitchToFiber (для запуска волокна). Потоки могут
попытаться переключиться на то волокно, которое уже выполняется, так что програм-
мист должен предусмотреть синхронизацию (во избежание этого явления).
Основным преимуществом волокон является то, что издержки переключения между
волокнами гораздо ниже, чем переключения между потоками. Для переключения
между потоками надо войти в ядро и выйти из него. Переключение между волокнами
сохраняет и восстанавливает несколько регистров (без всякой смены режима).
Несмотря на то что производится совместное планирование волокон, при наличии мно-
жества планирующих волокна потоков требуется большой объем работы по тщательной
синхронизации (чтобы волокна не мешали друг другу). Для упрощения взаимодей-
ствия между потоками и волокнами часто бывает полезно создавать ровно столько
потоков, сколько имеется процессоров для их выполнения, а также аффинизировать
потоки (чтобы каждый поток работал только на определенном наборе процессоров
либо вообще только на одном процессоре).
Каждый поток может затем выполнять определенное подмножество волокон, создавая
связь типа «один-ко-многим» между потоками и волокнами (это упрощает синхрони-
зацию). И даже при этом с волокнами много проблем. Большинство библиотек Win32
абсолютно ничего не знает о существовании волокон, поэтому те приложения, которые
попытаются использовать волокна как потоки, будут испытывать различные сбои. Ядро
не знает о волокнах, и когда волокно входит в ядро, поток, в котором оно выполняется,
может заблокироваться, и ядро запланирует на процессор произвольный (другой) по-
11.4. Процессы и потоки в Windows
987
ток, после чего процессор будет недоступен для выполнения других волокон. По всем
этим причинам волокна используются редко (кроме случаев переноса кода с других
систем, для которых нужна предоставляемая волокнами функциональность).
Пулы потоков и планирование в пользовательском режиме
Пул потоков Win32 является надстройкой над моделью потоков Windows, предо-
ставляющей более удачную абстракцию для определенного типа программ. Создание
потока обходится слишком дорого, если обращаться к нему при каждом требовании
программы выполнить небольшую задачу в параллель с другими задачами, чтобы ис-
пользовать преимущества нескольких процессоров. Задачи могут быть сгруппированы
в более крупные задачи, но это сократит объем используемого в программе паралле-
лизма. Альтернативным подходом для программы будет выделение ограниченного
количества потоков и поддержка очереди задач, требующих выполнения. Как только
поток завершит выполнение задачи, он берет из очереди следующую задачу. Эта модель
отделяет вопросы управления ресурсами (сколько процессоров доступно и сколько
потоков должно быть создано) от модели программирования (что собой представляет
задача и как задачи синхронизируются). В Windows это решение оформлено в пул
потоков Win32, набор API-функций для автоматического управления динамическим
пулом потоков и в отправление ему задач.
Пулы потоков не являются идеальным решением, поскольку при блокировании пото-
ка на каком-нибудь ресурсе в середине задачи он не может переключиться на другую
задачу. Таким образом, пул потоков неминуемо будет создавать больше потоков, чем
имеется процессоров, чтобы при блокировке одних потоков могли быть спланированы
другие, готовые к выполнению потоки. Пул потоков интегрирован со многими обычны-
ми механизмами синхронизации, такими как ожидание завершения ввода-вывода или
блокирование до тех пор, пока не поступит сигнал от события ядра. Синхронизация
может использоваться в качестве инициатора для ведения очереди задач, чтобы по-
токам не назначалась задача до того, как она будет готова к запуску.
В реализации пула потоков используется тот же самый механизм очередей, который
предоставляется для синхронизации с завершением ввода-вывода, вместе с фабрикой
потоков режима ядра, которая по мере необходимости добавляет потоки процессу,
обеспечивая занятость доступных процессоров. Небольшие задачи бывают во мно-
гих приложениях, но особенно часто они попадаются в тех приложениях, которые
предоставляют службы в клиент-серверной модели вычислений, где один за другим
слудуют запросы, отправляемые клиентом серверу. Использование пула потоков для
таких сценариев повышает эффективность системы за счет сокращения издержек на
создание потоков и перемещения принятия решений по управлению потоками в пуле
за пределы приложения в операционную систему.
Программисты видят, как один Windows-поток фактически представляет собой два
потока: запускаемый в режиме ядра и запускаемый в пользовательском режиме.
Точно такая же модель имеется и в UNIX. Каждому из этих потоков выделяются его
собственный стек и его собственная память для хранения его регистров, когда он не
выполняется. Два потока оказываются одним потоком, потому что они не работают
в одно и то же время. Поток пользовательского режима работает в качестве расширения
потока режима ядра, запускаемого, только когда поток ядра переключается на него,
возвращаясь из режима ядра в пользовательский режим. Когда поток пользователь-
ского режима хочет выполнить системный вызов, столкнувшись с ошибкой отсутствия
988
Глава 11. Изучение конкретных примеров: Windows 8
страницы или вытеснением, система входит в режим ядра и переключается обратно на
соответствующий поток ядра. Обычно невозможно переключаться между потоками
в пользовательском режиме без предварительного переключения на соответствующий
поток режима ядра, переключения на новый поток режима ядра, а затем переключения
на его поток пользовательского режима.
Большую часть времени разница между потоками пользовательского режима и режима
ядра не заметна программисту. Но в Windows 7 Microsoft добавила средство под на-
званием UMS (User-Mode Scheduling — планирование в пользовательском режиме),
которое выставляет разницу напоказ. Планировщик UMS похож на те средства, кото-
рые используются в других операционных системах, например на scheduler activations
(планировщик активаций). Он может использоваться для переключения между по-
токами пользовательского режима без предварительного входа в ядро, предоставляя
преимущества волокон, но с намного лучшей интеграцией в Win32, поскольку здесь
используются настоящие потоки Win32.
В реализации UMS имеются три основных элемента:
1. Переключение в пользовательском режиме: планировщик пользовательского ре-
жима может быть написан для переключения между пользовательскими потоками
без входа в ядро. Когда пользовательский поток входит в режим ядра, UMS найдет
соответствующий поток ядра и немедленно на него переключится.
2. Возобновление работы планировщика в пользовательском режиме: когда вы-
полнение потока ядра блокируется в ожидании доступности ресурса, UMS пере-
ключается на специальный пользовательский поток и выполняет код планиров-
щика в пользовательском режиме, чтобы другой пользовательский поток мог быть
спланирован для запуска на текущем процессоре. Это позволяет текущему про-
цессу продолжить использование текущего процессора по полной программе и не
вставать в очередь за другими процессами, когда один из его потоков блокируется.
3. Завершение системного вызова: после того как заблокированный поток ядра
наконец-то завершит свое выполнение, уведомление с результатами системного
вызова будет поставлено в очередь планировшика в пользовательском режиме
и он сможет переключиться на соответствующий пользовательский поток, когда
будет принимать очередное решение о планировании.
UMS не включает планировщик в пользовательском режиме в качестве составной части
Windows. UMS применяется в качестве низкоуровневого механизма для использования
библиотеками времени выполнения, приложениями языка программирования и сервера
для реализации легких потоковых моделей, не конфликтующих с планированием по-
токов в режиме ядра. Эти библиотеки времени выполнения обычно реализуют плани-
ровщик в пользовательском режиме, который в большей степени подходит к их среде.
Краткая сводка по данным абстракциям приведена в табл. 11.11.
Таблица 11.11. Основные концепции, используемые для управления процессором
и ресурсами
Название
Описание
Примечания
Задание
Коллекция процессов, у которых общие квоты
и лимиты
Используется
в AppContainers
Процесс
Контейнер для ресурсов
11.4. Процессы и потоки в Windows
989
Название
Описание
Примечания
Поток
Единица планирования для ядра
Волокно
«Легкий» поток, управляемый полностью в про-
странстве пользователя
Используется
редко
Пул потоков
Модель программирования, ориентированная на
применение задач
Создается поверх
потоков
Поток пользо-
вательского
режима
Абстракция, позволяющая переключать потоки
в пользовательском режиме
Расширение по-
токов
Потоки
Любой процесс обычно начинается с одного потока, однако можно динамически со-
здать дополнительные. Потоки являются основой планирования процессора, поскольку
операционная система всегда выбирает для выполнения поток, а не процесс. Следова-
тельно, каждый поток имеет состояние (готов, выполняется, блокирован и т. д.), а про-
цессы не имеют состояний планирования. Потоки можно создавать динамически при
помощи вызова Win32, в котором указывается адрес в адресном пространстве процесса,
с которого он должен начинать работу.
Каждый поток имеет идентификатор потока, который выбирается из того же про-
странства, что и идентификатор процесса (так что процесс и поток никогда не могут
иметь одинаковый идентификатор). Идентификаторы процессов и потоков кратны
четырем, поскольку они выделяются исполнительным уровнем (с использованием
специальной таблицы описателей, предназначенной для выделения идентификаторов).
Система многократно использует показанные на рис. 11.9 и 11.10 средства управления
описателями. Таблица описателей не имеет ссылок на объекты, но использует поле
указателя для указания на процесс или поток (так что поиск процесса или потока по
идентификатору очень эффективен). В современных версиях Windows используется
порядок FIFO (очередь) для списка свободных описателей, так что повторное ис-
пользование идентификаторов происходит не сразу. Возникающие при немедленном
повторном использовании проблемы изучаются в конце этой главы.
Обычно поток выполняется в пользовательском режиме, однако когда он делает си-
стемный вызов, он переключается в режим ядра и продолжает выполняться как тот же
самый поток с теми же самыми свойствами и лимитами, которые он имел в пользова-
тельском режиме. Каждый поток имеет два стека: один — для использования в пользо-
вательском режиме, другой — для использования в режиме ядра. Когда поток входит
в ядро, он переключается на стек режима ядра. Значения регистров пользовательского
режима сохраняются в структуре данных CONTEXT (в нижней части стека режима
ядра). Поскольку единственным способом не выполняться для потока пользователь-
ского режима является вход в ядро, то CONTEXT всегда содержит состояние регистров
для невыполняющегося потока. CONTEXT потока можно изучить и модифицировать
из любого процесса, имеющего описатель потока.
Потоки обычно выполняются при помощи маркера доступа содержащего их процесса,
но в некоторых случаях (связанных с клиент-серверными вычислениями) выполня-
ющийся в служебном процессе поток может олицетворять свой клиент при помощи
временного маркера доступа (основанного на маркере клиента), чтобы выполнить опе-
990
Глава 11. Изучение конкретных примеров: Windows 8
рацию от имени клиента. (В общем случае служба не может использовать настоящий
маркер клиента, поскольку клиент и сервер могут работать на разных компьютерах.)
Потоки обычно являются центрами ввода-вывода. Потоки блокируются при выпол-
нении синхронного ввода-вывода, а невыполненные пакеты запросов асинхронного
ввода-вывода привязываются к потоку. Когда поток закончил выполнение, он может
выйти. Любые невыполненные запросы ввода-вывода будут отменены. Когда послед-
ний активный поток процесса выходит, процесс завершается.
Важно понять, что потоки являются концепцией планирования, а не концепцией
владения ресурсами. Любой поток может обращаться ко всем объектам, которые при-
надлежат его процессу. Все, что для этого нужно сделать, — использовать значение
описателя и сделать соответствующий вызов Win32. Нет такого ограничения, чтобы
поток не мог обратиться к объекту из-за того, что его создал (или открыл) другой по-
ток. Система даже не отслеживает, какой поток создал данный объект. После того как
описатель объекта был помещен в таблицу описателей процесса, любой поток процесса
может им пользоваться (даже если он олицетворяет другого пользователя).
Как уже описывалось, в дополнение к нормальным потокам (которые работают внутри
пользовательских процессов) Windows имеет несколько системных потоков, которые
работают только в режиме ядра и не связаны ни с одним пользовательским процессом.
Все эти системные потоки работают в специальном процессе, называемом системным
процессом
(system process). Этот процесс не имеет адресного пространства в пользова-
тельском режиме. Он обеспечивает такую среду, в которой потоки выполняются тогда,
когда они не работают от имени конкретного процесса пользовательского режима.
Мы изучим некоторые из этих потоков в дальнейшем, когда дойдем до управления
памятью. Некоторые выполняют административные задачи (такие, как запись на диск
измененных страниц), другие формируют пул рабочих потоков (которые назначаются
для выполнения конкретных краткосрочных задач, делегированных компонентами ис-
полнительного уровня или драйверами, которым нужно выполнить какую-то работу
в системном процессе).
11.4.2. Вызовы API для управления заданиями,
процессами, потоками и волокнами
Новые процессы создаются при помощи функции CreateProcess интерфейса Win32 API.
Эта функция имеет много параметров и массу опций. Она принимает имя подлежащего
выполнению файла, строки командной строки (без их разбора) и указатель на строки
окружения. Есть также флаги и значения, которые управляют многими деталями,
такими как настройка безопасности для процесса и первого потока, конфигурация от-
ладчика, приоритеты планирования. При помощи флага указывается также, будут ли
открытые описатели создателя передаваться новому процессу. Функция принимает
также текущий рабочий каталог для нового процесса и необязательную структуру
данных с информацией о том окне графического интерфейса пользователя, которое
должен использовать процесс. Вместо возврата идентификатора нового процесса
Win32 возвращает и описатель, и идентификатор (как для нового процесса, так и для
его исходного потока).
Большое количество параметров отражает те отличия, которые имеются по сравнению
с созданием процессов в UNIX: