
- •Введение
- •Глава 1. Сущность многозадачности
- •1.1. Свойства многозадачной среды
- •1.2. Типы псевдопараллельной многозадачности
- •1.3. История развития многозадачных операционных систем
- •Глава 2. Моделирование режима многозадачности
- •2.1. Процессы и потоки
- •2.2. Модель режима многозадачности
- •Глава 3. Реализация многозадачности в Windows Vista
- •3.1. Фундаментальные концепции
- •3.2. Реализация процессов и потоков в WindowsVista
- •3.3. Планирование
- •Заключение
- •Список использованной литературы
3.3. Планирование
Ядро Windowsне имеет центрального потока планирования. Вместо этого (когда поток не может больше выполняться) поток входит в режим ядра и вызывает планировщик, чтобы увидеть, на какой поток следует переключиться. К выполнению текущим потоком кода планировщика приводят такие условия:
1. Текущий выполняющийся поток блокируется на семафоре, мьютексе, событии, вводе-выводе и т. д.
2. Поток сигнализирует объект (то есть делает up на семафоре или приводит к сигнализированию события).
3. Истекает квант.
В случае 1 поток уже работает в режиме ядра (для выполнения операции над диспетчером или объектом ввода-вывода). Вероятно, он не может продолжить выполнение, поэтому он вызывает код планировщика для выбора своего преемника и загружает запись CONTEXTэтого потока (для продолжения его выполнения).
В случае 2 работающий поток также находится в ядре. Однако после сигнализации некоторого объекта он может продолжить выполнение, поскольку сигнализация объекта никогда не приводит к блокировке. И все равно поток должен вызвать планировщик, чтобы увидеть, не освободился ли в результате его действий поток с более высоким приоритетом планирования (который готов к выполнению). Если это так, то происходит переключение потоков (поскольку Windowsявляется полностью вытесняющей, то есть переключение потоков может произойти в любой момент, а не только в конце кванта текущего потока). Однако (в случае многопроцессорной конфигурации) поток, который стал готовым, может быть запланирован на выполнение на другом процессоре, а исходный поток может продолжать выполнение на текущем процессоре (даже несмотря на то, что его приоритет планирования ниже).
В случае 3 происходит прерывание в режим ядра, в этот момент поток выполняет код планировщика (чтобы увидеть, кто будет выполняться следующим). В зависимости от того, какие потоки находятся в состоянии ожидания, может быть выбран тот же самый поток – в этом случае он получает новый квант и продолжает выполнение. В противном случае происходит переключение потоков.
Планировщик вызывается также в двух других случаях: завершается операция ввода-вывода; истекает время ожидания.
Первый случай – поток мог ожидать этого ввода-вывода и теперь он освобожден и может выполняться. Необходимо сделать проверку, чтобы увидеть, должен ли он вытеснить выполняющийся поток (поскольку не существует гарантированного минимального времени выполнения). Планировщик выполняется не в самом обработчике прерывания (поскольку это может привести к отключению прерываний на слишком долгое время). Вместо этого в очередь ставится отложенный вызов процедуры (DPC) – он будет выполняться после завершения обработчика прерываний. Во втором случае поток выполнилdown на семафоре или заблокировался на каком-то другом объекте, но с тайм-аутом, который уже истек. И опять-таки обработчику прерывания необходимо поставить в очередьDPC(чтобы избежать его выполнения во время работы обработчика прерывания таймера).
Если в течение этого тайм-аута поток стал готовым, то будет выполнен планировщик, и если новый готовый к выполнению поток имеет более высокий приоритет, то текущий поток вытесняется (как в случае 1).
Теперь рассмотрим сам алгоритм планирования. Интерфейс Win32 APIпредоставляет дваAPIдля работы с планированием потоков. Первый – вызовSetPriorityClass, который устанавливает класс приоритета для всех потоков вызывающего процесса. Допустимые значения: real-time, high, above normal, normal и idle. Класс приоритета определяет относительный приоритет процесса. (Начиная сWindowsVistaкласс приоритета процесса может также использоваться процессом для того, чтобы временно пометить самого себя как фоновый – это значит, что он не должен мешать никакой другой активности системы.) Класс приоритета устанавливается для процесса, но влияет на реальный приоритет каждого потока процесса (он устанавливает базовое значение приоритета, с которым стартует поток при создании).
Второй интерфейс Win32 API– этоSetThreadPriority. Он устанавливает относительный приоритет потока (возможно, вызывающего потока – но это не обязательно) по отношению к классу приоритета своего процесса. Допустимые значения: time critical, highest, above normal, normal, below normal, lowest и idle. Потокиtimecriticalполучают самый высокий приоритет планирования, а потокиidle– самый низкий (независимо от класса приоритета). Остальные значения приоритета подстраивают базовый приоритет потока относительно нормального значения, определенного классом приоритета (+2, +1, 0, -1, -2 соответственно). Использование классов приоритета и относительных приоритетов потоков облегчает приложениям принятие решений по указанию приоритетов.
Планировщик работает следующим образом. В системе имеется 32 приоритета с номерами от 0 до 31. Сочетание класса приоритета и относительного приоритета отображается на 32 абсолютных значения приоритета (в соответствии с табл. 1 Приложения 3). Номер в таблице определяет базовый приоритет (basepriority) потока. Кроме того, каждый поток имеет текущий приоритет (currentpriority), который может быть выше (но не ниже) базового приоритета.
Для использования этих приоритетов при планировании система поддерживает массив из 32 списков потоков, соответствующих всем 32 приоритетам (от 0 до 31) в табл. 1 Приложения 3. Каждый список содержит готовые потоки соответствующего приоритета. Базовый алгоритм планирования делает поиск по массиву от приоритета 31 до приоритета 0. Как только будет найден непустой список, то выбирается поток из верха списка и выполняется в течение одного кванта. Если квант истекает, то поток переводится в конец очереди своего уровня приоритета и следующим выбирается поток из верха списка. Иначе говоря, когда есть много готовых потоков на самом высоком уровне приоритета, то они выполняются циклически (по одному кванту времени каждый). Если готовых потоков нет, то процессор переходит в состояние ожидания, то есть переводится в состояние более низкого энергопотребления и ждет прерывания.
Необходимо отметить, что планирование выполняется путем выбора потока (независимо от того, какому процессу он принадлежит). Планировщик рассматривает только потоки (а не процессы). Он не учитывает, какому процессу принадлежит поток, он только определяет – не нужно ли ему изменить также и адресное пространство (при переключении потоков).
Для улучшения масштабируемости алгоритмов планирования (для многопроцессорных систем с большим количеством процессоров) планировщик старается не использовать блокировку, которая синхронизирует доступ к глобальному массиву списков приоритета. Вместо этого он смотрит, нельзя ли непосредственно диспетчеризировать поток (который готов выполняться) для работы на том процессоре, где ему следует выполняться.
Для каждого потока планировщик поддерживает идею его «идеального процессора» (idealprocessor) и пытается запланировать его выполнение именно на этом процессоре (по возможности). Это улучшает производительность системы, поскольку используемые потоком данные, скорее всего, уже имеются в наличии в принадлежащем его идеальному процессору кэше. Планировщик знает о таких многопроцессорных системах, в которых каждый процессор имеет свою собственную память и которые могут выполнять программы из любой области памяти (однако если это не локальная для данного процессора память, то стоимость такой операции выше).
Массив заголовков очередей показан на рис. 2 Приложения 3. На схеме показано, что реально имеется четыре категории приоритетов: real-time,user,zeroиidle(значение которого фактически равно -1). Это требует особого пояснения. Приоритеты 16-31 называются приоритетами реального времени и предназначены для создания систем, удовлетворяющих ограничениям реального времени (таким, как конечные сроки). Потоки с приоритетами реального времени выполняются до потоков с динамическими приоритетами (но не раньшеDPCиISR). Если приложение реального времени хочет выполняться в системе, то ему могут потребоваться такие драйверы устройств, которые не имеют длительного выполненияDPCилиISR(поскольку это может вызвать пропуск потоками реального времени их конечных сроков).
Обычные пользователи запускать потоки реального времени не могут. Если бы пользовательский поток выполнялся с более высоким приоритетом, чем, например, поток клавиатуры или мыши, и зациклился бы, то поток клавиатуры или мыши не смог бы выполняться, что привело бы в итоге к зависанию системы. Право устанавливать приоритет реального времени требует наличия специальной привилегии в маркере процесса. Обычные пользователи не имеют этой привилегии.
Потоки приложений обычно выполняются с приоритетами 1-15. При помощи установки приоритетов процессов и потоков приложение может определить, какие потоки получают преимущество. Системные потоки обнуления страниц (ZeroPage) работают с приоритетом 0 и преобразуют свободные страницы в заполненные нулями страницы. Для каждого реального процессора имеется отдельный поток обнуления страниц.
Каждый поток имеет базовый приоритет, основанный на классе приоритета процесса и относительном приоритете потока. Однако приоритет, используемый для поиска того списка, который содержит готовый поток, определяется текущим приоритетом, который обычно равен базовому (но это не всегда так). В определенных обстоятельствах текущий приоритет потока (не потока реального времени) поднимается ядром выше базового приоритета (но не выше 15). Поскольку массив на рис. 2 Приложения 3 основан на текущем приоритете, то его изменение влияет на планирование. Потоки реального времени никогда не корректируются.
Теперь рассмотрим, когда приоритет потока повышается. Во-первых, когда операция ввода-вывода завершается и освобождает находящийся в состоянии ожидания поток, то его приоритет повышается (чтобы он мог опять быстро запуститься и начать новую операцию ввода-вывода). Идея состоит в том, чтобы поддерживать загрузку устройств ввода-вывода. Величина повышения приоритета зависит от устройства ввода-вывода – обычно для диска это 1, для последовательной линии – 2, для клавиатуры – 6, а для звуковой карты – 8.
Во-вторых, если поток ждал на семафоре, мьютексе или другом событии, то при его освобождении он получает повышение приоритета на 2 уровня, если он находится в фоновом процессе (например, процесс, который управляет окном, в которое посылается ввод с клавиатуры), и на 1 уровень во всех остальных случаях. Такое повышение поднимает интерактивные процессы над большим количеством процессов, находящихся на уровне 8. И наконец, если поток графического интерфейса пользователя просыпается по причине наличия ввода от пользователя, то он также получает повышение (по той же самой причине).
Такие повышения делаются не навсегда. Они вступают в действие немедленно и могут вызвать изменения в планировании процессора. Однако если поток использует весь свой следующий квант, то он теряет один уровень приоритета и перемещается вниз на одну очередь в массиве приоритетов. Если же он использует второй полный квант, то он перемещается вниз еще на один уровень – и так до тех пор, пока он не дойдет до своего базового уровня (где и останется до следующего повышения).
Есть еще один случай корректировки приоритетов. Представим, что два потока работают вместе над задачей «производитель–потребитель». Работа производителя труднее, так что он получает более высокий приоритет (например, 12), чем потребитель (приоритет 4). В определенный момент производитель заполняет совместно используемый буфер и блокируется на семафоре (как показано на рис. 3 а) Приложения 3).До того как потребитель получает возможность запуститься, какой-то другой поток с приоритетом 8 получает готовность и начинает выполнение (рис. 3б) Приложения 3). Этот поток сможет выполняться столько, сколько он захочет, – поскольку он имеет более высокий приоритет планирования, чем потребитель (а производитель, приоритет которого еще выше, блокирован). В таких обстоятельствах производитель никогда не сможет запуститься (пока поток с приоритетом 8 не уйдет).
Windowsрешает эту проблему следующим способом. Система отслеживает, сколько времени прошло с того момента, когда в последний раз выполнялся готовый поток. Если это время превышает определенный предел, то его приоритет повышается до 15 на время двух квантов. Это может дать ей возможность разблокировать производителя. После истечения двух квантов времени это повышение резко снимается (а не уменьшается постепенно). Вероятно, более удачным решением было бы штрафовать потоки, которые используют свой квант снова и снова (снижать их приоритет). В конце концов, проблема была вызвана не голодным потоком, а прожорливым. Эта проблема хорошо известна как инверсия приоритетов (priorityinversion).
Аналогичная проблема происходит тогда, когда поток с приоритетом 16 захватывает мьютекс и в течение длительного времени не получает возможности выполнения (придушив таким образом более важные системные потоки, которые ждут этот мьютекс). Эту проблему можно было бы предотвратить внутри операционной системы, если бы нуждающийся в мьютексе поток на некоторое короткое время отключал планирование. (В многопроцессорной системе следует использовать спин-блокировку.)
На клиентских системах Windowsзначение кванта по умолчанию равно 20 мс. На серверных системахWindows– 180 мс. Короткий квант полезен интерактивным пользователям, а длинный – уменьшает количество переключений контекста и обеспечивает таким образом более высокую эффективность.
Алгоритм планирования имеет еще одну заплатку: когда новое окно становится окном переднего плана, то все его потоки получают более длинный квант (увеличенный на определенное значение, которое берется из реестра). Это дает им больше процессорного времени, что обычно обеспечивает улучшение восприятия пользователем его работы в данном приложении4.