Windows не перемещает уже выполняемый поток с одного процессора на второй, чтобы готовый поток с маской привязки именно к первому процессору немедленно начал на нем работать. Рассмотрим, например, такой сценарий: к процессору 0 подключен поток с приоритетом 8, который может работать на любом процессоре, а к процессору 1 – поток с приоритетом 4, который тоже может выполняться на любом процессоре. Ho вот готов поток с приоритетом 6, привязанный только к процессору 0. Что произойдет? Windows не станет переключать поток с приоритетом 8 на процессор 1 (вытесняя при этом поток с приоритетом 4), и поток с приоритетом 6 будет ждать освобождения процессора 0.
   Следовательно, изменение маски привязки для процесса или потока может привести к тому, что потоки будут получать меньше процессорного времени, чем обычно, поскольку это ограничивает Windows в выборе процессоров для данного потока. A значит, задавать маску привязки нужно с крайней осторожностью – в большинстве ситуаций оптимальнее оставить выбор за самой Windows.
 
Идеальный и последний процессоры
   B блоке потока ядра каждого потока хранятся номера двух особых процессоров:
    (o) идеального(ideal processor) – предпочтительного для выполнения данного потока;
    (o) последнего(last processor) – на котором поток работал в прошлый раз.
 
   Идеальный процессор для потока выбирается случайным образом при его создании с использованием зародышевого значения (seed) в блоке процесса. Это значение увеличивается на 1 всякий раз, когда создается новый поток, поэтому создаваемые потоки равномерно распределяются по набору доступных процессоров. Например, первый поток в первом процессе в системе закрепляется за идеальным процессором 0, второй поток того же процесса – за идеальным процессором 1. Однако у следующего процесса в системе идеальный процессор для первого потока устанавливается в 1, для второго – в 2 и т. д. Благодаря этому потоки внутри каждого процесса равномерно распределяются между процессорами.
   Заметьте: здесь предполагается, что потоки внутри процесса выполняют равные объемы работы. Ho в многопоточном процессе это обычно не так; в нем есть, как правило, один или более «служебных» потоков (housekeeping threads) и несколько рабочих. Поэтому, если в многопоточном приложении нужно задействовать все преимущества многопроцессорной платформы, целесообразно указывать номера идеальных процессоров для потоков вызовом функции SetTbreadIdealProcessor.
   B системах с Hyperthreading следующим идеальным процессором является первый логический процессор на следующем физическом. Например, в двухпроцессорной системе с Hyperthreading логических процессоров – 4; если для первого потока идеальным процессором назначен логический процессор 0, то для второго потока имело бы смысл назначить таковым логический процессор 2, для третьего – логический процессор 1, для четвертого – логический процессор 3 и т. д. Тогда потоки равномерно распределялись бы по физическим процессорам.
   B NUMA-системах идеальный узел для процесса выбирается при его (процесса) создании. Первому процессу назначается узел 0, второму – 1 и т. д. Затем идеальные процессоры для потоков процесса выбираются из идеального узла. Идеальным процессором для первого потока в процессе назначается первый процессор в узле. По мере создания дополнительных потоков в процессе за ними закрепляется тот же идеальный узел; следующий процессор в этом узле становится идеальным для следующего потока и т. д.
 
Алгоритмы планирования потоков в многопроцессорных системах
   Теперь, описав типы многопроцессорных систем, поддерживаемых Windows, а также привязку потоков к процессорам и выбор идеального процессора, мы готовы объяснить вам применение этой информации при определении того, какие потоки выполняются и на каких процессорах. При этом система принимает два базовых решения:
    (o)выбор процессора для потока, который готов к выполнению;
    (o)выбор потока для конкретного процессора.
 
Выбор процессора для потока при наличии простаивающих процессоров
   Как только поток готов к выполнению, Windows сначала пытается подключить его к простаивающему процессору. Если таких процессоров несколько, предпочтение отдается сначала идеальному процессору для данного потока, затем предыдущему, а потом текущему (т. е. процессору, на котором работает код, отвечающий за планирование). B Windows 2000, если все эти процессоры заняты, выбирается первый простаивающий процессор, на котором может работать данный поток, для чего сканируется маска свободных процессоров в направлении убывания их номеров.
   B Windows XP и Windows Server 2003 выбор простаивающего процессора не так прост. Во-первых, выделяются простаивающие процессоры из числа тех, на которых маска привязки разрешает выполнение данного потока. Если система имеет архитектуру NUMA и в узле, где находится идеальный процессор для потока, есть простаивающие процессоры, то список всех простаивающих процессоров уменьшается до этого набора. Если в результате такой операции в списке не останется простаивающих процессоров, список не сокращается. Затем, если в системе работают процессоры с технологией Hyperthreading и имеется физический процессор, все логические процессоры которого свободны, список простаивающих процессоров уменьшается до этого набора. И вновь, если в результате такой операции в списке не останется простаивающих процессоров, список не сокращается.
   Если текущий процессор (тот, который пытается определить, что делать с потоком, готовым к выполнению) относится к набору оставшихся простаивающих процессоров, поток планируется к выполнению именно на этом процессоре. A если текущий процессор не входит в список оставшихся простаивающих процессоров, если это система с технологией Hyperthreading и если есть простаивающий логический процессор на физическом, который содержит идеальный процессор для данного потока, то список простаивающих процессоров ограничивается этим набором. B ином случае система проверяет, имеются ли простаивающие логические процессоры на физическом, который содержит предыдущий процессор потока. Если такой набор не пуст, список простаивающих процессоров уменьшается до этого набора.
   Из оставшегося набора простаивающих процессоров исключаются все процессоры, находящиеся в состоянии сна. (Эта операция не выполняется, если в ее результате такой список опустел бы.) Наконец, поток подключается к процессору с наименьшим номером в оставшемся списке.
   Независимо от версии Windows, как только процессор выбран, соответствующий поток переводится в состояние Standby, и PRCB простаивающего процессора обновляется так, чтобы указывать на этот поток. При выполнении на этом процессоре цикл простоя обнаруживает, что поток выбран и подключает его к процессору.
 
Выбор процессора для потока в отсутствие простаивающих процессоров
   Если простаивающих процессоров нет в момент, когда готовый к выполнению поток переведен в состояние Standby, Windows проверяет приоритет выполняемого потока (или того, который находится в состоянии Standby) и идеальный процессор для него, чтобы решить, следует ли вытеснить выполняемый. B Windows 2000 маска привязки может исключить идеальный процессор. (B Windows XP такое не допускается.) Если этот процессор не входит в маску привязки потока, Windows выбирает для потока процессор с наибольшим номером.
   Если для идеального процессора уже выбран поток, ожидающий в состоянии Standby выделения процессорного времени, и его приоритет ниже, чем потока, готовящегося к выполнению, последний вытесняет первый и становится следующим выполняемым на данном процессоре. Если к процессору уже подключен какой-то поток, Windows сравнивает приоритеты текущего и нового потока. Если приоритет текущего меньше, чем нового, первый помечается как подлежащий вытеснению, и Windows ставит в очередь межпроцессорное прерывание, чтобы целевой процессор вытеснил текущий поток в пользу нового.
 
    ПРИМЕЧАНИЕ Windows сравнивает приоритеты текущего и следующего потоков не на всех процессорах, а только на одном, выбранном по только что описанным правилам. Если вытеснение подключенного к данному процессору потока невозможно, новый поток помещается в очередь готовых потоков, соответствующую его уровню приоритета, где он и ждет выделения процессорного времени. Поэтому Windows не гарантирует первоочередное выполнение всех потоков с наивысшим приоритетом, но всегда выполняет один поток с таким приоритетом.
 
   Как мы уже сказали, если готовый поток нельзя выполнить немедленно, он помещается в очередь готовых потоков и ждет выделения процессорного времени. Однако в Windows Server 2003 потоки всегда помещаются в очереди готовых потоков на своих идеальных процессорах.
 
Выбор потока для выполнения на конкретном процессоре (Windows 2000 и Windows XP)
   B некоторых случаях (например, когда поток входит в состояние ожидания, снижает свой приоритет, изменяет привязку, откладывает выполнение или передает управление) Windows нужно найти новый поток для подключения к процессору, на котором работал текущий поток. Как уже говорилось, в однопроцессорной системе Windows просто выбирает первый поток из непустой очереди готовых потоков с наивысшим приоритетом. Ho в многопроцессорной системе Windows 2000 или Windows XP должно быть соблюдено одно из дополнительных условий:
    (o) поток уже выполнялся в прошлый раз на данном процессоре; (o)данный процессор должен быть идеальным для этого потока; (o) поток провел в состоянии Ready более трех тактов системного таймера;
    (o)поток имеет приоритет не менее 24.
 
   Таким образом, потоки с жесткой привязкой, в маску которых данный процессор не входит, очевидно, пропускаются. Если потоков, отвечающих одному из условий, нет, Windows отберет поток из начала той очереди готовых потоков, с которой она начинает поиск.
   Почему так важно подключить поток именно к тому процессору, на котором он выполнялся в прошлый раз? Как обычно, все дело в быстродействии – процессор, на котором поток выполнялся в прошлый раз, скорее всего еще хранит его данные в своем кэше второго уровня.
 
Выбор потока для выполнения на конкретном процессоре (Windows Server 2003)
   Поскольку в Windows Server 2003 у каждого процессора собственный список потоков, ждущих выполнения на этом процессоре, то по окончании выполнения текущего потока процессор просто проверяет свою очередь готовых потоков. Если его очереди пусты, к процессору подключается поток простоя. Затем этот поток начинает сканировать очереди готовых потоков при других процессорах и ищет потоки, которые можно было бы выполнять на данном процессоре. Заметьте, что в NUMA-системах поток простоя проверяет процессоры сначала в своем узле, а потом в других узлах.
 
Объекты-задания
 
   Объект «задание» (job object) – это именуемый, защищаемый и разделяемый объект ядра, обеспечивающий управление одним или несколькими процессами как группой. Процесс может входить только в одно задание. По умолчанию его связь с объектом «задание» нельзя разрушить, и все процессы, создаваемые данным процессом и его потомками, будут сопоставлены с тем же заданием. Объект «задание» также регистрирует базовую учетную информацию всех включенных в него процессов, в том числе уже завершившихся. Windows-функции, предназначенные для создания объектов-заданий и манипулирования ими, перечислены в таблице 6-20.
   Ниже кратко поясняются некоторые ограничения, которые можно налагать на задания.
 
    (o) Максимальное число активных процессовОграничивает число одновременно выполняемых процессов задания.
    (o) Общий лимит на процессорное время в пользовательском режимеОграничивает максимальное количество процессорного времени, потребляемого процессами задания (с учетом завершившихся) в пользовательском режиме. Как только этот лимит будет исчерпан, все процессы задания завершатся с сообщением об ошибке, а создание новых процессов в задании станет невозможным (если лимит не будет переустановлен). Объект-задание будет переведен в свободное состояние, и все ожидавшие его потоки освободятся. Это поведение системы по умолчанию можно изменить через функцию EndOfJobTimeAction.
    (o) Индивидуальный лимит на процессорное время пользовательского режима для каждого процессаОграничивает максимальное количество процессорного времени, потребляемого каждым процессом в задании. По достижении этого лимита процесс завершается (не получая шанса на очистку).
    (o) Класс планирования заданияУстанавливает длительность кванта для потоков процессов, входящих в задание. Этот параметр применим только в системах, использующих длинные фиксированные кванты (в системах Windows Server по умолчанию). Длительность кванта определяется классом планирования задания, как показано в следующей таблице.
    (o) Привязка задания к процессорамУстанавливает маску привязки к процессорам для каждого процесса задания. (Отдельные потоки могут изменять свои привязки на любое подмножество привязок задания, но процессы этого делать не могут.)
    (o) Класс приоритета для всех процессов заданияОпределяет класс приоритета для каждого процесса в задании. Потоки не могут повышать свой приоритет относительно класса (как они это обычно делают). Все попытки повышения приоритета игнорируются. (При вызове SetTbreadPriorityошибка не генерируется, но и приоритет не повышается.)
    (o) Минимальный и максимальный размеры рабочего набора по умолчаниюУстанавливает указанные минимальный и максимальный размеры рабочего набора для каждого процесса задания. каждого процесса свой рабочий набор, но с одинаковыми максимальным и минимальным размерами.)
    (o) Лимит на виртуальную память, передаваемую процессу или заданиюУказывает максимальный размер виртуального адресного пространства, который можно передать либо одному процессу, либо всему заданию.
 
   Задания можно настроить на отправку в очередь объекта «порт завершения ввода-вывода» какого-либо элемента, который могут ждать другие потоки через 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 для просмотра объекта-задания и обратите внимание на дополнительные поля:
 
Резюме
   Мы изучили структуру процессов, потоков и заданий, узнали, как они создаются, а также познакомились с алгоритмами распределения процессорного времени в Windows.
   B этой главе было много ссылок на материалы, связанные с управлением памятью. Поскольку потоки выполняются в адресном пространстве процессов, следующим предметом рассмотрения станет управление виртуальной и физической памятью в Windows. Этому и посвящена глава 7.
 

Г Л A B A 7 Управление памятью

 
   B этой главе вы узнаете, как реализована виртуальная память в Microsoft Windows и как осуществляется управление той частью виртуальной памяти, которая находится в физической. Мы также опишем внутреннюю структуру диспетчера памяти и его компоненты, в том числе ключевые структуры данных и алгоритмы. Прежде чем изучать механизмы управления памятью, давайте рассмотрим базовые сервисы, предоставляемые диспетчером памяти, и основные концепции, такие как зарезервированная (reserved memory), переданная (committed memory) и разделяемая память (shared memory).
 
Введение в диспетчер памяти
   По умолчанию виртуальный размер процесса в 32-разрядной Windows – 2 Гб. Если образ помечен как поддерживающий большое адресное пространство и система загружается со специальным ключом (о нем мы расскажем позже), 32-разрядный процесс может занимать до 3 Гб в 32-разрядной Windows и до 4 Гб в 64-разрядной. Размер виртуального адресного пространства процесса в 64-разрядной Windows составляет 7152 Гб на платформе IA64 и 8192 Гб на платформе x64. (Это значение может увеличиться в следующих выпусках 64-разрядной Windows.)
   Как вы видели в главе 2 (особенно в таблице 2-4), максимальный объем физической памяти, поддерживаемый Windows, варьируется от 2 до 1024 Гб в зависимости от версии и редакции Windows. Так как виртуальное адресное пространство может быть больше или меньше объема физической памяти в компьютере, диспетчер управления памятью решает две главные задачи.
    (o)Трансляция, или проецирование (mapping), виртуального адресного пространства процесса на физическую память. Это позволяет ссылаться на корректные адреса физической памяти, когда потоки, выполняемые в контексте процесса, читают и записывают в его виртуальном адресном пространстве. Физически резидентное подмножество виртуального адресного пространства процесса называется рабочим набором(working set).
    (o)Подкачка части содержимого памяти на диск, когда потоки или системный код пытаются задействовать больший объем физической памяти, чем тот, который имеется в наличии, и загрузка страниц обратно в физическую память по мере необходимости.
   Кроме управления виртуальной памятью диспетчер памяти предоставляет базовый набор сервисов, на которые опираются различные подсистемы окружения Windows. K этим сервисам относится поддержка файлов, проецируемых в память (memory-mapped files) [их внутреннее название – объекты-разделы(section objects)], памяти, копируемой при записи, и приложений, использующих большие разреженные адресные пространства. Диспетчер памяти также позволяет процессу выделять и использовать большие объемы физической памяти, чем можно спроецировать на виртуальное адресное пространство процесса (например, в 32-разрядных системах, в которых установлено более 4 Гб физической памяти). Соответствующий механизм поясняется в разделе «Address Windowing Extensions» далее в этой главе.
 
Компоненты диспетчера памяти
   Диспетчер памяти является частью исполнительной системы Windows, содержится в файле Ntoskrnl.exe и включает следующие компоненты.
    (o)Набор сервисов исполнительной системы для выделения, освобождения и управления виртуальной памятью; большинство этих сервисов доступно через Windows API или интерфейсы драйверов устройств режима ядра.
    (o)Обработчики ловушек трансляции недействительных адресов (translation-not-valid) и нарушений доступа для разрешения аппаратно обнаруживаемых исключений, связанных с управлением памятью, а также загрузки в физическую память необходимых процессу страниц.
    (o)Несколько ключевых компонентов, работающих в контексте шести различных системных потоков режима ядра.
    (o) Диспетчер рабочих наборов(working set manager) с приоритетом 16. Диспетчер настройки баланса (системный поток, создаваемый ядром) вызывает его раз в секунду или при уменьшении объема свободной памяти ниже определенного порогового значения. Он реализует общие правила управления памятью, например усечение рабочего набора, старение и запись модифицированных страниц.
    (o) Поток загрузки и выгрузки стеков(process/stack swapper) с приоритетом 23. Выгружает (outswapping) и загружает (inswapping) стеки процесса и потока. При необходимости операций со страничным файлом этот поток пробуждается диспетчером рабочих наборов и кодом ядра, отвечающим за планирование.
    (o) Подсистема записи модифицированных страниц(modified page writer) с приоритетом 17. Записывает измененные страницы, зарегистрированные в списке модифицированных страниц, обратно в соответствующие страничные файлы. Этот поток пробуждается, когда возникает необходимость в уменьшении размера списка модифицированных страниц.
    (o) Подсистема записи спроецированных страниц(mapped page writer) с приоритетом 17. Записывает измененные страницы спроецированных файлов на диск. Пробуждается, когда нужно уменьшить размер
   списка модифицированных страниц или когда страницы модифицированных файлов находятся в этом списке более 5 минут. Этот второй поток записи модифицированных страниц требуется потому, что он может генерировать ошибки страниц, в результате которых выдаются запросы на свободные страницы. Если бы в системе был лишь один поток записи модифицированных страниц, она могла бы перейти в бесконечное ожидание свободных страниц.
    (o) Поток сегмента разыменования(dereference segment thread) с приоритетом 18. Отвечает за уменьшение размеров системного кэша и изменение размеров страничного файла.
    (o) Поток обнуления страниц(zero page thread) с приоритетом 0. Заполняет нулями страницы, зарегистрированные в списке свободных страниц. (B некоторых случаях обнуление памяти выполняется более скоростной функцией MiZeroInParallel)
 
Внутренняя синхронизация
   Как и другие компоненты исполнительной системы Windows, диспетчер памяти полностью реентерабелен и поддерживает одновременное выполнение в многопроцессорных системах, управляя тем, как потоки захватывают ресурсы. C этой целью диспетчер памяти контролирует доступ к собственным структурам данным, используя внутренние механизмы синхронизации, например спин-блокировку и ресурсы исполнительной системы (о синхронизирующих объектах см. главу 3).
   Диспетчер памяти должен синхронизировать доступ к таким общесистемным ресурсам, как база данных номеров фреймов страниц (PFN) (контроль через спин-блокировку), объекты «раздел» и системный рабочий набор (контроль через спин-блокировку с заталкиванием указателя) и страничные файлы (контроль через объекты «мьютекс»). B Windows XP и Windows Server 2003 ряд таких блокировок был либо удален, либо оптимизирован, что позволило резко снизить вероятность конкуренции. Например, в Windows 2000 для синхронизации изменений в системном адресном пространстве и при передаче памяти применялись спин-блокировки, но, начиная с Windows XP, эти спин-блокировки были удалены, чтобы повысить масштабируемость. Индивидуальные для каждого процесса структуры данных управления памятью, требующие синхронизации, включают блокировку рабочего набора (удерживаемую на время внесения изменений в список рабочего набора) и блокировку адресного пространства (удерживаемую в период его изменения). Синхронизация рабочего набора в Windows 2000 реализована с помощью мьютекса, но в Windows XP и более поздних версиях применяется более эффективная блокировка с заталкиванием указателя, которая поддерживает как разделяемый, так и монопольный доступ.