Что такое дескрипторы в процессоре
Каждый процесс предоставляет ресурсы, необходимые для выполнения программы. Процесс содержит виртуальное адресное пространство, исполняемый код, открытые дескрипторы для системных объектов, контекст безопасности, уникальный идентификатор процесса, переменные среды, класс приоритета, минимальный и максимальный размеры рабочего множества и, по крайней мере, один поток выполнения. Каждый процесс запускается с одним потоком, который часто называется основным потоком, но может создавать дополнительные потоки из любого потока.
Поток — это сущность внутри процесса, для которой можно запланировать выполнение. Все потоки процесса совместно используют свои виртуальные адресные пространства и системные ресурсы. Кроме того, каждый поток поддерживает обработчики исключений, приоритет планирования, локальное хранилище потока, уникальный идентификатор потока и набор структур, которые система будет использовать для сохранения контекста потока, пока он не будет запланирован. Контекст потока включает набор регистров компьютера, стек ядра, блок среды потока и пользовательский стек в адресном пространстве процесса потока. Потоки также могут иметь собственный контекст безопасности, который можно использовать для олицетворения клиентов.
Microsoft Windows поддерживает вытеснение по нескольким задачам, что создает воздействие одновременного выполнения нескольких потоков из нескольких процессов. На многопроцессорном компьютере система может одновременно выполнять столько потоков, сколько процессоров на компьютере.
Объект задания позволяет управлять группами процессов как единым целым. Объекты заданий являются намаблеными, защищаемыми объектами, совместно используемые объекты, которые управляют атрибутами связанных с ними процессов. Операции, выполняемые над объектом задания, влияют на все процессы, связанные с объектом Job.
Приложение может использовать пул потоков , чтобы уменьшить число потоков приложения и обеспечить управление рабочими потоками. Приложения могут ставить в очередь рабочие элементы, связывать работу с ожидающими дескрипторами, автоматически ставить в очередь на основе таймера и выполнять привязку с помощью операций ввода-вывода.
Планирование пользовательского режима (UMS) — это упрощенный механизм, с помощью которого приложения могут планировать собственные потоки. Приложение может переключаться между UMS-потоками в пользовательском режиме без использования планировщика системы и повторного получения управления процессором, если поток UMS блокируется в ядре. Каждый поток UMS имеет собственный контекст потока вместо того, чтобы совместно использовать контекст потока одного потока. Возможность переключения между потоками в пользовательском режиме делает UMS более эффективным, чем пулы потоков для кратковременных рабочих элементов, требующих небольшого количества системных вызовов.
Волокно — это единица выполнения, которая должна быть запланирована приложением вручную. Волокна выполняются в контексте потоков, планирующих их. Каждый поток может запланировать несколько волокон. Как правило, волокна не предоставляют преимуществ по сравнению с хорошо спроектированным многопоточным приложением. Однако использование волокон может упростить перенос приложений, разработанных для планирования собственных потоков.
В этой статье мы поговорим на такие темы, как процессы и потоки, дискрипторы процесса, поговорим о синзронизации потоков и затронем всеми любимый диспетчер задач windows.
На протяжении существования процесса его выполнение может быть многократно прервано и продолжено. Для того, чтобы возобновить выполнение процесса, необходимо восстановить состояние его операционной среды. Состояние операционной среды отображается состоянием регистров и программного счетчика, режимом работы процессора, указателями на открытые файлы, информацией о незавершенных операциях ввода-вывода, кодами ошибок выполняемых данным процессом системных вызовов и т.д. Эта информация называется контекстом процесса.
Для того чтобы ОС могла управлять процессами, она должна располагать всей необходимой для этого информацией. С этой целью на каждый процесс заводится дескриптор процесса.
Дескриптор – специальная информационная структура, которая заводится на каждый процесс (описатель задачи, блок управления задачей).
В общем случае дескриптор содержит следующую информацию:
- Идентификатор процесса.
- Тип (или класс) процесса, который определяет для супервизора некоторые правила предоставления ресурсов.
- Приоритет процесса.
- Переменную состояния, которая определяет, в каком состоянии находится процесс (готов к работе, в состоянии выполнения, ожидание устройства ввода-вывода и т.д.)
- Защищенную область памяти (или адрес такой зоны), в которой хранятся текущие значения регистров процессора, если процесс прерывается, не закончив работы. Эта информация называется контекстом задачи.
- Информацию о ресурсах, которыми процесс владеет и/или имеет право пользоваться (указатели на открытые файлы, информация о незавершенных операциях ввода/вывода и т.п.).
- Место (или его адрес) для организации общения с другими процессами.
- Параметры времени запуска (момент времени, когда процесс должен активизироваться, и периодичность этой процедуры).
- В случае отсутствия системы управления файлами – адрес задачи на диске в ее исходном состоянии и адрес на диске, куда она выгружается из оперативной памяти, если ее вытесняет другая.
Дескриптор процесса по сравнению с контекстом содержит более оперативную информацию, которая должна быть легко доступна подсистеме планирования процессов. Контекст процесса содержит менее актуальную информацию и используется операционной системой только после того, как принято решение о возобновлении прерванного процесса.
Дескрипторы, как правило, постоянно располагаются в оперативной памяти с целью ускорить работу супервизора, который организует их в списки (очереди) и отображает изменение состояния процесса перемещением соответствующего описателя из одного списка в другой.
Для каждого состояния (за исключением состояния выполнения для однопроцессорной системы) ОС ведет соответствующий список задач, находящийся в этом состоянии. Однако для состояния ожидания может быть не один список, а столько, сколько различных видов ресурсов могут вызывать состояние ожидания.
Например, состояний ожидания завершения операции ввода/вывода может быть столько, сколько устройств ввода/вывода содержится в системе.
Дерево процессов
В Windows процессы знают только своих родителей, а более древних предков не знают.
Например у нас есть такое дерево процессов:
Если мы завершим дерево процессов “Процесс_1“, то завершатся все процессы. Потому что “Процесс_1” знает про “Процесс_2“, а “Процесс_2” знает про “Процесс_3“.
Если мы вначале завершим “Процесс_2“, а затем завершаем дерево процессов “Процесс_1“, то завершится только “Процесс_1“, так как между “Процесс_1” и “Процесс_3” не останется связи.
Например, запустите командную строку и выполните команду title parrent чтобы изменить заголовок окна и start cmd чтобы запустить второе окно командной строки:
Измените заголовок второго окна на child и из него запустите программу paint:
В окне командной строке child введите команду exit, окно закроется а paint продолжит работать:
После этого на рабочем столе останутся два приложения, командная строка parrent и paint. При этом parrent будет являться как бы дедом для paint.
Запустите “Диспетчер задач”, на вкладке “Процессы” найдите процесс “Обработчик команд Windows”, разверните список и найдите “parrent“. Затем нажмите на нём правой копкой мыши и выберите “Подробно”:
Вы переключитесь на вкладку “Подробно” с выделенным процессом “cmd.exe“. Нажмите правой кнопкой по этому процессу и выберите «Завершить дерево процессов»:
Окно командной строки Parrent завершится а Paint останется работать. Так мы убедились что связи между первым процессом и его внуком нет, если у внука нет непосредственного родителя.
Взаимоблокировка процессов
При организации параллельного выполнения нескольких процессов одной из главных функций ОС является корректное распределение ресурсов между выполняющимися процессами и обеспечение процессов средствами взаимной синхронизации и обмена данными.
При параллельном исполнении процессов могут возникать ситуации, при которых два или более процесса все время находятся в заблокированном состоянии. Самый простой случай – когда каждый из двух процессов ожидает ресурс, занятый другим процессом. Из-за такого ожидания ни один из процессов не может продолжить исполнение и освободить в конечном итоге ресурс, необходимый другому процессу. Эта тупиковая ситуация называется дедлоком (dead lock), тупиком, клинчем или взаимоблокировкой.
Говорят, что в мультизадачной системе процесс находится в состоянии тупика, если он ждет события, которое никогда не произойдет.
Тупиковые ситуации надо отличать от простых очередей, хотя и те и другие возникают при совместном использовании ресурсов и внешне выглядят похоже: процесс приостанавливается и ждет освобождения ресурса. Однако очередь — это нормальное явление, неотъемлемый признак высокого коэффициента использования ресурсов при случайном поступлении запросов. Она возникает тогда, когда ресурс недоступен в данный момент, но через некоторое время он освобождается, и процесс продолжает свое выполнение. Тупик же является в некотором роде неразрешимой ситуацией.
Проблема тупиков включает в себя следующие задачи:
- предотвращение тупиков.
- распознавание тупиков.
- восстановление системы после тупиков.
Тупики могут быть предотвращены на стадии написания программ, то есть программы должны быть написаны таким образом, чтобы тупик не мог возникнуть ни при каком соотношении взаимных скоростей процессов. Так, если бы в предыдущем примере процесс А и процесс В запрашивали ресурсы в одинаковой последовательности, то тупик был бы в принципе невозможен. Второй подход к предотвращению тупиков называется динамическим и заключается в использовании определенных правил при назначении ресурсов процессам, например, ресурсы могут выделяться в определенной последовательности, общей для всех процессов.
В некоторых случаях, когда тупиковая ситуация образована многими процессами, использующими много ресурсов, распознавание тупика является нетривиальной задачей. Существуют формальные, программно-реализованные методы распознавания тупиков, основанные на ведении таблиц распределения ресурсов и таблиц запросов к занятым ресурсам. Анализ этих таблиц позволяет обнаружить взаимные блокировки.
Если же тупиковая ситуация возникла, то не обязательно снимать с выполнения все заблокированные процессы. Можно снять только часть из них, при этом освобождаются ресурсы, ожидаемые остальными процессами, можно вернуть некоторые процессы в область свопинга, можно совершить «откат» некоторых процессов до так называемой контрольной точки, в которой запоминается вся информация, необходимая для восстановления выполнения программы с данного места. Контрольные точки расставляются в программе в местах, после которых возможно возникновение тупика.
Защищенный режим работы процессора
Возможности 80386 используются полностью, если он работает в режиме ВА. В этом режиме пространство исполнительных адресов расширяется до 4 Гбайт (2**32 байт), а область виртуальных адресов - до 64 Тбайт (2**64 байт), т.е. практически не ограничена. Режим ВА позволяет использовать дополнительные команды, специально оптимизированные для поддержки многозадачных операционных систем.
Механизм (схема) адресации
В формировании исполнительного адреса участвуют два компонента: 16-ти битовый селектор, используемый для определения адреса базы сегмента и 32-х битовый эффективный адрес.
Сформированный исполнительный адрес является физическим адресом, если не используется механизм страничной адресации. В противном случае, он преобразуется в физический. В режиме ВА селектор используется, как индекс, указывающий операционной системе на определенный элемент специальной таблицы, в которую помещен 32-х битовый адрес базы данного сегмента. Физический адрес формируется сложением базового адреса с 32-х битовым смещением.
Страничная адресация памяти является дополнительным механизмом управления памятью, используемым только в режиме ВА. Этот механизм позволяет преобразовывать исполнительные адреса в физические.
Сегментация
- PL. Уровень привилегированности - один из 4-х уровней привилегированности. Наиболее привилегированный уровень 0, наименее - 3.
- RPL. Запрашиваемый уровень привилегированности - уровень привилегированности конкретного подключаемого селектора. RPL определяется двумя младшими битами селектора.
- DPL. Уровень привилегированности дескриптора - это младший уровень привилегированности, который имеет задача, обращающаяся к дескриптору (и к связанному с ним сегменту). DPL определяется битами 6 и 5 байта.
- CPL. Текущий уровень привилегированности - уровень привилегированности, который имеет текущая выполняемая задача. Он равен уровню привилегированности выполняемого кодового сегмента. CPL может быть также определен проверкой двух младших битов CS регистра, получаемых для соответствующих кодовых сегментов.
- EPL. Эффективный привилегированный уровень - наименьший уровень из RPL и DPL, т.е. цифровой максимум из RPL и DPL.
Задача (процесс) - совокупность выполняемых программ.
Таблицы дескрипторов
Таблицы дескрипторов определяют все сегменты, которые используются в системе на базе 80386.
Эти регистры GDTR, LDTR и IDTR загружаются командами LGDT, LLDT, LIDT и сохраняются командами SGDT, SLDT и SIDT. Манипуляция таблицами осуществляется операционной системой при помощи привилегированных команд.
Таблица глобальных дескрипторов (GDT)
Таблица GDT содержит дескрипторы, доступные для всех задач системы. GDT может содержать любые дескрипторы сегментов за исключением дескрипторов, используемых для обслуживания прерываний. Обычно GDT содержит сегменты команд и данных, используемые ОС и дескрипторы таблиц LDT. Первому элементу GDT соответствует несуществующий селектор, который не используется.
Таблица локальных дескрипторов (LDT)
Таблица LDT содержит дескрипторы, используемые в данной задаче. Обычно ОС разрабатываются таким образом, чтобы каждая задача имела отдельную LDT. LDT может содержать только дескрипторы команд, данных, стека, номера задачи и вызова номера. Таблицы LDT служат механизмом для изоляции сегментов команд и данных отдельных задач от ОС, в то время, как GDT хранит дескрипторы сегментов, являющихся общими для всех задач. Сегмент не может быть доступен задаче, если его дескриптор отсутствует внутри текущей LDT или GTR. Это обеспечивает защиту сегментов задач и в то же время, совместное использование глобальных данных различными задачами. В отличие от 6-ти байтовых GDT и IDT регистров, которые хранят базовый адрес и ограничитель, видимая часть регистра LDT хранит только 16 битовый селектор. Этот селектор определяет дескриптор LDT в GDT.
Таблица дескрипторов прерываний (IDT)
IDT содержит дескрипторы, указывающие расположение 256 подпрограмм обслуживания прерываний. IDT может хранить только вектора задач, вектора прерываний и вектора трассировок. IDT должна быть не менее, чем 256 байтовой, чтобы хранить дескрипторы 32 прерываний, зарезервированных ф. Intel. Каждое прерывание, используемое в системе, должно быть описано в IDT. IDT используется при выполнении команд INT, внешних запросов прерываний в особых ситуациях.
Дескрипторы
Биты атрибутов дескриптора
Все сегменты 80386 имеют 3 единых поля: P, DPL, S.
Дескрипторы команд и данных (S=1)
Формат такого дескриптора и байт прав доступа приведены на рис.4-6 и табл.4-1 соответственно.
Дескрипторы сегментов команд и данных имеют несколько общих полей: A, G.
A - бит используется ОС для получения статистики по обращению к данному сегменту.
G - бит указывает величину, в которых указывается размер сегмента. Длина сегмента может быть 1 Мбайт (G=0, т. е. задан в байтах) или 4 Гбайт (G=1, т.е. задан в страницах - 2**20 страниц по 4 Кбайт каждая).
Система на базе 80386 может включать сегменты как байтовой размерностью, так и со страничной размерностью, если включен блок страничной адресации.
Бит Е указывает, какой из сегментов выполняется: командный (E=1, S=1) или данных (E=0, S=1).
Кодовый сегмент может находится в стадии только выполнения (бит R=0) или выполнения/чтения (R=1). Запись в кодовый сегмент не возможна.
Замечания.
Кодовые сегменты могут изменяться, через вымышленные имена (метки). Метки приписываются сегментам данных, которые располагаются в некотором пространстве исполнительных адресов, как сегменты команд.
Бит D указывает размерность операндов и эффективных адресов (32 бита при D=1, 16 бит при D=0). Это позволяет выполнять команды для 80286.
Другим атрибутом сегментов команд является бит согласования С. При С=1 согласованные сегменты могут выполняться в режиме разделения программами, имеющими различные уровни привилегированности.
Сегменты, определяемые как сегменты данных (E=0, S=1), могут быть двух типов: сегменты стека и сегменты данных. Бит направления расширения (ED) определяет тип сегмента. Если сегмент стековый, то смещение должно быть больше, чем граница сегмента. Для сегмента данных смещение должно быть меньше или равно границе сегмента. Другими словами, сегменты стека начинаются от базы исполнительного адреса + максимального размера сегмента и размещаются в сторону уменьшения до базы исполнительного адреса + граница. Сегмент данных начинается с базы исполнительного адреса и кончается границей сегмента.
Бит записи W управляет записью в сегменте. При W=0 сегменты данных работают только на чтение. Стековые сегменты должны иметь W=1.
Бит B указывает размерность регистра - указателя стека. Если В=1, то ESP работает как 32-х битовый регистр и верхняя граница стека FFFFFFFF. Если В=0 - все команды работы со стеком используют SP (16 битовый) и верхняя граница стека FFFF.
Форматы системных дескрипторов
Эти дескрипторы содержат информацию о системных таблицах, задачах и паролях. Системный дескриптор 80386 имеет 32 разрядную базу и 20 битовую границу, системный дескриптор 80286 соответствует 24 и 16 бит (старшие 16 бит равны 0).
Дескрипторы LDT (S=0, TYPE=2)
Дескрипторы LDT содержат информацию о таблицах локальных дескрипторов. Таблица локальных дескрипторов содержит дескрипторы сегментов, используемых конкретной задачей. Команда загрузки регистра LDT должна иметь нулевой уровень привилегированности (поле DPL игнорируется).
Дескрипторы TSS (S=0, TYPE = 1, 3, 9, B)
Дескриптор сегмента состояния задачи содержит информацию о местонахождении, размере и уровне привилегированности сегмента состояния задачи (TSS). TSS - сегмент специального формата, который содержит всю необходимую информацию о задаче и поле, обеспечивающее связь задач между собой. Поле TYPE указывает на текущую исполняемую задачу (из цепочки активных задач) или доступный TSS. Поле TYPE также указывает, используется ли TSS для МП 286 или 386. Регистр задач (TR) хранит селектор, который указывает текущий TSS.
Дескрипторы шлюзов (S=0, TYPE=4-7, C, F)
Шлюзы используются для управления доступом к определенным точкам внутри управляющего кодового сегмента.
- шлюзы вызовов;
- шлюзы задач;
- шлюзы прерываний;
- шлюзы трассировок.
Шлюзы реализуют дисциплину взаимодействия при управлении передачей между источником и приемником. При взаимодействии процессор автоматически проверяет защиту. Это также позволяет проектировщикам системы управлять точками внутри ОС. Шлюзы вызова используются для изменения уровней привилегированности. Шлюзы задач используются для переключения задач, а шлюзы прерываний и трассировок используются для определения специальных программ обслуживания прерываний.
Шлюз вызова, главным образом, используются для передачи управления программам с более высоким уровнем привилегированности.
- байт доступа;
- указатель длины (селектор и смещение), который указывает начало программы;
- счетчик слов, определяющий количество параметров, передаваемых из стека вызывающей программы в стек вызываемой программы.
Поле счетчика слов используется только в шлюзах вызыва, которые изменяют уровень привилегированности, в остальных типах это поле игнорируется.
Шлюзы прерываний и трассировок используют поля селектора назначения и смещения назначения дескриптора для указания начала программ управления прерыванием или трассировкой. Различия между шлюзами прерываний и шлюзами трассировок заключается в том, что шлюз прерывание выключает прерывания (сбрасывает бит IF) в то время, как шлюз трассировки нет.
Шлюзы задач используются при переключении задач. Шлюзы задач могут обращаться только к сегменту состояния задач (TSS), поэтому используется только селекторная часть а смещение игнорируется.
Прерывание 13 формируется, когда селектор назначения указывает на некорректный тип дескриптора.
Формат байта прав доступа одинаков для дескрипторов всех шлюзов. Р=1 - указывает на то, что шлюз открыт. Р=0 - шлюз закрыт, при этом, если необходимо, формируется прерывание 11. DPL указывает уровень привилегированности и указывает, в каких случаях данный дескриптор может использоваться задачей (см. 4.4). Бит 4 поля S должен быть равен 0 и тем самым указывать, что это дескриптор управления системой.
Поле селектора
- индикатор LDT или GDT (TI);
- смещение (индекс) внутри таблицы дескрипторов;
- уровень привилегированности (RPL). Бит TI указывает на одну из двух таблиц. Поле индекса выбирает один из 8к дескрипторов из таблицы дескрипторов. Биты RPL обеспечивают высокоскоростную проверку атрибутов привилегированности селектора.
КЭШ - ЗУ дескрипторов сегментов
Форматы регистров дескрипторов
Содержимое этих регистров зависит от режима работы МП. Формат регистров для режима RM приведен на рис.4-11.
Для обеспечения совместимости с архитектурой 8086 база устанавливается в течение 16-ти тактов в соответствии со значением селектора, граница фиксирована и равна 0000FFFF, а атрибуты соответствуют наличию и полной доступности сегмента. В РМ внутренний уровень привилегированности всегда максимальный (=0), поэтому команды ввода - вывода и другие привилегированные команды могут выполняться.
Формат регистров в режиме РМ представлен на рис.4-12.
В РМ значения полей определяется содержимым дескриптора сегмента индексированного селектором.
Формат регистра для режима виртуального 8086 приведен на рис.4-13.
В отличие от режима RM виртуальная программа имеет минимальный уровень привилегированности (=3), обеспечивая трассировку всех команд IOPL и команд с нулевым уровнем привилегированности.
Это уже пятая статья из моей серии публикаций "Преодолевая границы Windows", в которых я рассказываю о максимальном значении и объеме ресурсов, которыми управляет Windows, таких как физическая память, виртуальная память, процессы и потоки:
На этот раз я собираюсь разобраться в реализации дескрипторов, чтобы найти и объяснить существующие для них ограничения. Дескрипторы - это структуры данных, которые представляют собой открытые экземпляры базовых объектов операционной системы, с которыми взаимодействуют приложения; например, файлы, ключи системного реестра, примитивы синхронизации и общая память. Существует два ограничения, связанные с количеством дескрипторов, которое может создать процесс: максимальное число дескрипторов, которое система может установить для процесса, и объем памяти, доступный для хранения дескрипторов и объектов, которые приложение связывает с их дескрипторами.
В большинстве случаев эти ограничения для дескрипторов находятся далеко от тех значений, которые обычно используются приложениями или системой. Однако, приложения, при разработке которых не учитывались эти ограничения, могут достигнуть их неожиданными для разработчиков путями. Чаще всего подобные проблемы возникают из-за того, что срок жизни этого вида ресурсов должен управляться приложениями, и, как в случае с виртуальной памятью, задача управления сроком жизни ресурсов является своего рода вызовом даже для самых лучших разработчиков. Приложение, которое не в состоянии освобождать неиспользуемые ресурсы, вызывает их утечку, что, в конечном счете, может привести к тому, что предел использования ресурса будет достигнут, приводя к странному и трудно диагностируемому поведению как данного, так и других приложений, или же всей системы в целом.
Как обычно, я рекомендую вам прочитать мои предыдущие публикации, поскольку в них объясняются некоторые из понятий, используемых в данной статье (например, выгружаемый пул).
Дескрипторы и объекты
Ядро Windows, работающее в привилегированном режиме, реализованное в образе %SystemRoot%\System32\Ntoskrnl.exe, состоит из различных подсистем, таких как диспетчер памяти, диспетчер процессов, диспетчер ввода/вывода, диспетчер конфигураций (системный реестр), которые является частями исполнительной системы. Каждая из этих подсистем вместе с диспетчером объектов определяет один или более типов для представления ресурсов, которые они выделяют приложениям. Например, диспетчер конфигураций определяет объект "ключ" (Key) для представления открытого ключа системного реестра; диспетчер памяти объект "Секция" (Section) для общей памяти; исполнительная система определяет объекты "семафор" (Semaphore), "мутант" (Mutant, внутреннее название для мьютекса) и "синхронизация событий" (Event synchronization) (эти объекты представляют собой оболочку для структур данных, определенных подсистемой Ядро операционной системы); диспетчер ввода/вывода определяет объект "файл" (File) для представления открытых экземпляров ресурсов драйверов устройств, которые включают в себя файлы файловой системы; и, наконец, диспетчер процессов создает объекты "поток" (Thread) и "процесс" (Process), о которых я рассказывал в своей последней публикации из данной серии. Каждый релиз Windows вводит новые типы объектов, в том числе и Windows 7, которые принес с собой в общей сложности 42 новых типа. Определенный в вашей системы объекты вы можете увидеть при помощи утилиты Winobj от Sysinternals, запущенной с правами администратора, открыв директорию ObjectTypes пространства имен Object Manager:
Когда приложение желает получить управление над одним из этих ресурсов, оно сначала должно вызвать соответствующий API, чтобы создать или открыть ресурс. Например, функция CreateFile открывает или создает файл, функция RegOpenKeyEx открывает ключ реестра, а функция CreateSemaporeEx открывает или создает семафор. Если такая функция успешно выполняется, Windows размещает дескриптор в таблице дескрипторов процесса приложения и возвращает значение этого дескриптора, которое приложение обрабатывает неявно, однако фактически речь идет об индексе возвращенного дескриптора в таблице дескрипторов.
Имея в своем распоряжении дескриптор, приложение делает запросы и управляет объектом, передавая значение дескриптора в такие функции API, как ReadFile , SetEvent , SetThreadPriority и MapViewOfFile . Система может организовать поиск объекта, к которому обращается дескриптор путем индексирования таблицы дескрипторов, чтобы определить местонахождения соответствующей дескриптору записи, которая содержит указатель на объект. Запись дескриптора также хранит информацию об операциях, которые стали доступны процессу, когда он открыл объект, что позволяет системе быть уверенной в том, что процесс не сможет выполнять над объектом операции, на которые у этого процесса нет разрешения. Например, если процесс успешно открыл файл для чтения, то запись дескриптора выглядела примерно так:
Если бы этот процесс попытался бы что-то записать в файл, то функция завершила выполнение с ошибкой, поскольку подобный вид доступа к файлу не был предоставлен, а факт кэширования доступа на чтение означает, что система не должна снова повторять более затратную в плане ресурсов проверку прав доступа.
Максимальное число дескрипторов
Чтобы исследовать первое ограничение вы можете воспользоваться инструментальным средством Testlimit, которое я уже использовал в рамках данной серии статей для того, чтобы опытным путем изучить ограничения на ресурсы системы. Его можно скачать на странице Windows Internals здесь . Для определения количества дескрипторов, которое может создать процесс, Testlimit используется с параметром -h, который указывает ему создать столько дескрипторов, сколько возможно. Это достигается путем создания объекта event с помощью функции CreateEvent и последующего неоднократного дублирования возвращаемого системой дескриптора с помощью функции DuplicateHandle . Используя дублирование, Testlimit избегает необходимости создания новых объектов и все ресурсы, потребляемые этим инструментов, расходуются для записей таблицы дескрипторов. Вот результат работы Testlimit с ключом -h в 64-битной системе:
Этот результат, однако, не отображает общее количество дескрипторов, которые может создать процесс, поскольку системные библиотеки DLL открывают различные объекты во время инициализации процесса. Общее число дескрипторов процесса вы можете увидеть, добавив соответствующую колонку в диспетчере задач или Process Explorer. Общее цифра для Testlimit в данном случае равна 16’771’680:
Когда вы запускаете Testlimit на 32-битной системе, число дескрипторов будет немного отличаться:
Общее число дескрипторов также другое, 16’744’448:
Чем обуславливаются эти различия? Ответ состоит в том, что исполнительная система, ответственная за управление таблицей дескрипторов, устанавливает ограничение на количество дескрипторов для каждого процесса, а также на размер записи в таблице дескрипторов. Здесь мы имеем дело с одним из тех редких случаев, когда Windows устанавливает жесткое ограничение на использование ресурса, так что в данном случае исполнительная система определяет число 16’777’216 (16*1024*1024) как максимальное количество дескрипторов, которое может быть выделено процессу. Любой процесс, которые имеет более десяти тысяч дескрипторов одновременно в какой-либо момент времени, весьма вероятно имеет либо серьезные недочеты, допущенные при его проектировании, или утечку дескрипторов. Так что предел в 16 миллионов дескрипторов практически недостижим и призван помочь предотвратить утечку памяти, вызванную вмешательством в работу процесса со стороны остальной системы. Чтобы понять причину того, почему число, отображаемое в диспетчере задач, отличается от жестко установленного максимума, требуется рассмотреть то, каким образом исполнительная система организовывает таблицу дескрипторов.
Запись таблицы дескрипторов должна иметь достаточный размер для того, чтобы хранить маску прав доступа и указатель на объект. Маска доступа является 32-битной, но размер указателя, очевидно, зависит от того, является система 32-битной или 64-битной. Следовательно, запись дескриптора занимает 8 байт на 32-битной Windows и 12-байт на 64-битной Windows. 64-битная Windows дополняет структуры данных записи дескриптора до 64-битных границ, так что 64-битная запись дескриптора фактически занимает 16 байт. Вот определение записи дескрипторов на 64-битной Windows, отображенное в отладчике ядра с помощью команды dt (dump type):
Здесь мы видим, что данная структура содержит в себе и другую информацию, помимо указателя на объект и маски доступа, выделенных на скриншоте.
Исполнительная система размещает таблицы дескрипторов в блоки, имеющие размер страницы, которые она делит на записи таблицы дескрипторов. Это означает, что страница, которая занимает 4096 байт и на системах x86, и на системах x64, может сохранить 512 записей в 32-битной Windows и 256 записей в 64-битной Windows. Исполнительная система определяет максимальное число страниц, которые она может выделить под записи дескрипторов, путем деления жестко установленного максимума (16’777’216) на число записей дескрипторов на странице; для 32-битной Windows это число равно 32’768, а для 64-битной Windows - 65’536. Поскольку исполнительная система использует первую запись на каждой странице для своей собственной идентификационной информации, действительное количество дескрипторов, доступных для процесса, следует получать, вычитая из 16,777,216 полученные выше числа, что объясняет результаты, полученные при запуске Testlimit: 16’777’216 - 65’536 = 16’711’680 и 16’777’216 - 32’768 = 16’744’488.
Дескрипторы и выгружаемый пул
Вторым ограничением, касающимся дескрипторов, является объем памяти, требуемой для хранения таблицы дескрипторов, который исполнительная система выделяет из выгружаемого пула. Исполнительная система для отслеживания выделяемых ею под таблицы дескрипторов страниц использует трехуровневую схему, подобную той, которую используют модули управления памятью (MMU) процессора для руководства трансляциями виртуальных адресов в физические. Мы уже рассматривали с вами организацию нижнего и среднего уровней, которые фактически хранят в себе записи таблицы дескрипторов. Верхний уровень служит в качестве указателей на таблицы среднего уровня и включает в себя 1024 записи на страницу в 32-битной Windows. Отсюда следует, что общее количество страниц, требуемых для хранения максимального числа дескрипторов для 32-битной Windows можно вычислить как 16’777’216/512*4096, что равно 128 Мб. Это совпадает с показателями использования Testlimit выгружаемого пула, которые показывает диспетчер задач:
В 64-битной версии Windows на верхнем уровне содержится 256 указателей на страницу. Это означает в общем для размещения полной таблицы дескрипторов используется 256 Мб выгружаемого пула (16’777’216/256*4096). Правильность данных вычислений подтверждается показателями использования Testlimit выгружаемого пула на 64-битной Windows:
Объема выгружаемого пула более чем достаточно, чтобы сохранить эти структуры данных, однако, как я уже говорил ранее, процесс, который создает слишком много дескрипторов, почти наверняка исчерпает какой-то другой ресурс, и, если он достигнет ограничения на количество дескрипторов для одного процесса, то ему не удастся открыть никакие другие объекты.
Утечки дескрипторов
Для процесса, допускающего утечку дескрипторов характерно то, что число потерянных дескрипторов постоянно возрастает. Причина этого кроется в том, что утечка дескрипторов очень коварна - в отличие от случая с Testlimit, который создавал дескрипторы для одного и того же объекта, процесс, имеющий утечку дескрипторов, вероятнее всего теряет вместе с ними и объекты. Например, если процесс создает события, но не может закрыть их, он создает утечку как записей дескрипторов, так и объектов событий. Объекты "event" располагаются в невыгружаемом пуле, так что данная утечка затронет в дополнение к выгружаемому пулу и невыгружаемый пул.
Вы можете визуально определить объекты, доступ к которым потерял процесс, используя представление дескрипторов в Process Explorer, поскольку там новые дескрипторы выделены зеленым, а закрытые - красным; если вы увидите много зеленых записей при малом количестве красных, значит вы, скорее всего, столкнулись с утечкой. Чтобы увидеть подобное выделение дескрипторов Process Explorer в действии, вы можете открыть процесс командной строки и, выбрав этот процесс в Process Explorer, перейти к просмотру дескрипторов, после чего следует сменить текущую директорию в командной строке. Дескриптор старой рабочей директории подсветится красным, а новой - зеленым:
По умолчанию Process Explorer показывает только дескрипторы, которые указывают на объекты, имеющие имена, что означает, что вы не увидите всех дескрипторов процесса, если не включите опцию "Show Unnamed Handles and Mappings" в меню View. Вот некоторые безымянные дескрипторы из таблицы дескрипторов командной строки:
К счастью, Windows включает в себя средство отслеживания дескрипторов, которое вы можете использовать для установления факта утечки и определения ответственного за эту утечку программного обеспечения. Оно работает с каждым процессом в отдельности и активируется исполнительной системой для записи активности стека каждый раз, когда какой-либо дескриптор создается или закрывается. Вы можете воспользоваться этим инструментом или при помощи утилиты Application Verifier , которую можно бесплатно скачать с сайта Microsoft, или воспользовавшись отладчиком Windows (Windbg) . Если вы хотите, чтобы система проследила за активностью дескрипторов процесса, начиная с его запуска, то вам нужно использовать Application Verifier. В остальных случаях вам нужно использовать отладчик и команду !htrace , чтобы увидеть информацию об активности процесса.
Чтобы продемонстрировать отслеживание активности в действии, я запустил Windbg и подключился к командной строке, которую я запустил ранее. Чтобы включить отслеживание дескрипторов, я ввел команду !htrace с ключом -enable:
Я позволил процессу продолжать работу и снова сменил директорию. После этого я переключился обратно на Windbg, остановил выполнение процесса и запустил команду htrace без параметров, которая выдает список всех открытых и закрытых операций, которые выполнил процесс, начиная с предыдущего запуска команды !htrace с параметром snapshot или с того момента, когда была включена запись активности дескрипторов. Вот результаты работы этой команды для той же сессии отладчика:
Здесь перечислены события, начиная с самой последней операции, так что, если читать снизу, мы увидим, что командная строка открыла дескриптор 0xb8, затем закрыла его, потом открыла дескриптор 0x22c и в конце закрыла дескриптор 0xec. Process Explorer отметил бы дескриптор 0x22c зеленым и 0xec красным, если бы он был обновлен после смены директории, но, по всей вероятности, не увидел бы 0xb8, если бы обновление не произошло между открытием и закрытием этого дескриптора. Стек для открытия 0x22c показывает, что данная операция стала результатом выполнения командной строкой (cmd.exe) своей функции ChangeDirectory. Если добавить в Process Explorer колонку Handle, она подтвердит, что новый дескриптор - 0x22c:
Если вы ищите только утечки, то вам нужно использовать !htrace с параметром -diff, которая показывает только новые дескрипторы, начиная с последней засечки или с начала запуска отслеживания активности. Как и ожидалось, в результате выполнения этой команды мы видим только дескриптор 0x22c:
Прекрасным источником советов о том, как можно устранить утечки дескрипторов является интервью инженера по технической поддержке Microsoft Джеффа Дэйли (Jeff Dailey) для Channel 9.
В следующий раз я рассмотрю ограничения, установленные для таких основанных на дескрипторах ресурсов, как объекты GDI и USER. Дескрипторы этих ресурсов управляются подсистемой Windows, отличной от исполнительной системы, а потому используют другие ресурсы и имеют другие ограничения.
Работа операционной системы Windows основана на работе процессов. В этой статье разберём что такое Windows процессы, их свойства, состояния и другое.
Процессы
Процесс стоит воспринимать как контейнер с набором ресурсов для выполнения программы. То есть запускаем мы программу, для неё выделяется часть ресурсов компьютера и эта программа работает с этими ресурсами.
Процессы нужны операционной системе для многозадачности, так как программы работают в своих процессах и не мешают друг другу, при этом по очереди обрабатываются процессором.
Windows процессы состоят из следующего:
- Закрытое виртуальное адресное пространство, то есть выделенная для процесса часть оперативной памяти, которая называется виртуальной.
- Исполняемая программа выполняя свой код, помещает его в виртуальную память.
- Список открытых дескрипторов. Процесс может открывать или создавать объекты, например файлы или другие процессы. Эти объекты нумеруются, и их номера называют дескрипторами. Ссылаться на объект по дескриптору быстрее, чем по имени.
- Контекст безопасности. Сюда входит пользователь процесса, группа, привилегии, сеанс и другое.
- Идентификатор процесса, то есть его уникальный номер.
- Программный поток (как минимум один или несколько). Чтобы процесс хоть что-то делал, в нем должен существовать программный поток. Если потока нет, значит что-то пошло не так, возможно процесс не смог корректно завершиться, или стартовать.
У процессов есть еще очень много свойств которые вы можете посмотреть в “Диспетчере задач” или “Process Explorer“.
Процесс может быть в различных состояниях:
В Windows существуют процессы трёх типов:
- Приложения. Процессы запущенных приложений. У таких приложений есть окно на рабочем столе, которое вы можете свернуть, развернуть или закрыть.
- Фоновые процессы. Такие процессы работают в фоне и не имеют окна. Некоторые процессы приложений становятся фоновыми, когда вы сворачиваете их в трей.
- Процессы Windows. Процессы самой операционной системы, например “Диспетчер печати” или “Проводник”.
Процессы и потоки
Чтобы поддерживать мультипрограммирование, ОС должна определить и оформить для себя те внутренне единицы работы, между которыми будет разделяться процессор и другие ресурсы компьютера. В настоящее время в большинстве ОС определены два типа единиц работы:
- Процесс (более крупная единица работы).
- Поток (нить или тред) – более мелкая единица работы, которую требует для своего выполнения процесс.
- Когда говорят о процессах, то тем самым хотят отметить, что ОС поддерживает их обособленность: у каждого процесса имеется свое виртуальное адресное пространство, каждому процессу назначаются свои ресурсы – файлы, окна и др. Такая обособленность нужна для того, чтобы защитить один процесс от другого, поскольку они, совместно используя все ресурсы вычислительной системы, конкурируют друг с другом.
В общем случае процессы просто никак не связаны между собой и могут принадлежать даже различным пользователям, разделяющим одну вычислительную систему. Другими словами, в случае процессов ОС считает их совершенно несвязанными и независимыми. При этом именно ОС отвечает за конкуренцию между процессами по поводу ресурсов.
Для повышения быстродействия процессов есть возможность задействовать внутренний параллелизм в самих процессах.
Например, некоторые операции, выполняемые приложением, могут требовать для своего исполнения достаточно длительного использования ЦП. В этом случае при интерактивной работе с приложением пользователь вынужден долго ожидать завершения заказанной операции и не может управлять приложением до тех пор, пока операция не выполнится до самого конца. Такие ситуации встречаются достаточно часто, например, при обработке больших изображений в графических редакторах. Если же программные модули, исполняющие такие длительные операции, оформлять в виде самостоятельных «подпроцессов» (потоков), которые будут выполняться параллельно с другими «подпроцессами», то у пользователя появляется возможность параллельно выполнять несколько операций в рамках одного приложения (процесса).
Можно выделить следующие отличия потоков от процессов:
- ОС для потоков не должна организовывать полноценную виртуальную машину.
- Потоки не имеют своих собственных ресурсов, они развиваются в том же виртуальном адресном пространстве, могут пользоваться теми же файлами, виртуальными устройствами и иными ресурсами, что и данный процесс.
- Единственное, что потокам необходимо иметь, — это процессорный ресурс. В однопроцессорной системе потоки разделяют между собой процессорное время так же, как это делают обычные процессы, а в многопроцессорной системе могут выполняться одновременно, если не встречают конкуренции из-за обращения к иным ресурсам.
Главное, что обеспечивает многопоточность, — это возможность параллельно выполнять несколько видов операций в одной прикладной программе. За счет чего реализуется эффективное использование ресурсов ЦП, а суммарное время выполнения задач становится меньше.
Например, если табличный процессор или текстовый процессор были разработаны с учетом возможностей многопоточной обработки, то пользователь может запросить пересчет своего рабочего листа или слияние нескольких документов и одновременно продолжать заполнять таблицу или открывать для редактирования следующий документ.
Установка и подготовка к работе
Более подробную информацию о процессах и потоках можно получить с помощью программы Process Explorer из пакета Sysinternals. Его нужно скачать и запустить.
Некоторые возможности Process Explorer:
- информация по правам процесса: кто владелец процесса, у кого есть доступ к нему;
- выделение разными цветами процессов и потоков, для удобного восприятия информации:
- процессы служб – розовый;
- ваши собственные процессы – синий;
- новые процессы – зелёный;
- завершенные процессы – красный;
- число дескрипторов у процесса;
- активность потоков в процессе;
- подробную информация о распределении памяти.
Запустите Process Explorer:
Для начала скачиваем установщик «Пакет SDK для Windows 10».
Устанавливать все не нужно, достаточно при установки выбрать “Debugging Tools for Windows“:
Для настройки символических имен перейдите в меню Options / Configure / Symbols. Введите путь к библиотеке Dbghelp.dll, которая находится внутри установленного «Пакета SDK для Windows 10» по умолчанию:
- C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Dbghelp.dll.
И путь к серверу символической информации:
Некоторые основные настройки Process Explorer:
- Смена цветового выделения – Options / Configure Colors.
- Выбор колонок с информацией о процессах – View / Select Columns.
- Сортировка процессов – нужно щелкнуть на заголовке столбца Process, при первом щелчке сортировка будет в алфавитном порядке, при втором в обратном порядке, при третьем вернется в вид дерева.
- Просмотр только своих процессов – View / снять галочку Show Processes from All Users.
- Настройка времени выделения только что запущенных процессов и завершённых – Options / Difference Highlight Duration / введите количество секунд.
- Чтобы исследователь процесс подробнее можно дважды щелкнуть на нем и посмотреть информацию на различных вкладках.
- Открыть нижнюю панель для просмотра открытых дескрипторов или библиотек – Vies / Show Lower Panel.
Задания в Process Explorer
Process Explorer может выделить процессы, управляемые заданиями. Чтобы включить такое выделение откройте меню «Options» и выберите команду «Configure Colors», далее поставьте галочку «Jobs»:
Более того, страницы свойств таких процессов содержат дополнительную вкладку Job с информацией о самом объекте задания. Например приложение Skype работает со своими процессами как за заданием:
Запустите командную строку и введите команду:
Таким образом вы запустите еще одну командную строку от имени этого пользователя. Служба Windows, которая выполняет команды runas, создает безымянное задание, чтобы во время выхода из системы завершить процессы из задания.
В новой командной строке запустите блокнот:
Далее запускаем Process Explorer и находим такое дерево процессов:
Как видим, процесс cmd и notepad это процессы связанные с каким-то заданием. Если дважды кликнуть по любому из этих процессов и перейти на вкладку Job, то мы увидим следующее:
Process Explorer
Диспетчер задач
Чаще всего для получения информации о процессе мы используем «Диспетчер задач». Запустить его можно разными способами:
- комбинацией клавиш Ctrl+Shift+Esc;
- щелчком правой кнопкой мыши на панели задач и выборе «Диспетчер задач»;
- нажатием клавиш Ctrl+Alt+Del и выборе «Диспетчер задач»;
- запуском исполняемого файла C:\Windows\system32\Taskmgr.exe.
При первом запуске диспетчера задач он запускается в кратком режиме, при этом видны только процессы имеющие видимое окно. При нажатие на кнопку «Подробнее» откроется полный режим:
В полном режиме на вкладке «Процессы» виден список процессов и информация по ним. Чтобы получить больше информации можно нажать правой кнопкой мышки на заголовке и добавить столбцы:
Чтобы получить еще больше информации можно нажать правой кнопкой мышки на процессе и выбрать «Подробно». При этом вы переключитесь на вкладку «Подробности» и этот процесс выделится.
На вкладке «Подробности» можно получить ещё больше информации о процессе. А также здесь также можно добавить колонки с дополнительной информацией, для этого нужно щелкнуть правой кнопкой мыши по заголовку и нажать «Выбрать столбцы»:
Диспетчер задач WINDOWS
В диспетчере задач отображаются сведения о программах и процессах, выполняемых на компьютере. Кроме того, там можно просмотреть наиболее часто используемые показатели быстродействия процессов.
Диспетчер задач служит для отображения ключевых показателей быстродействия компьютера. Для выполняемых программ можно просмотреть их состояние и завершить программы, переставшие отвечать на запросы. Имеется возможность просмотра активности выполняющихся процессов с использованием до 15 параметров, а также графиков и сведений об использовании ЦП и памяти.
На вкладке Процессы отображаются сведения о выполняющихся на компьютере процессах: сведения об использовании ЦП и памяти, счетчике процессов и некоторые другие параметры:
На вкладке Быстродействие, отображаются сведения о счетчике дескрипторов и потоках, параметры памяти:
Потребность в синхронизации потоков возникает только в мультипрограммной ОС и связана с совместным использованием аппаратных и информационных ресурсов компьютера. Синхронизация необходима для исключения гонок (см. далее) и тупиков при обмене данными между потоками, разделении данных, при доступе к процессору и устройствам ввода-вывода.
Синхронизация потоков и процессов заключается в согласовании их скоростей путем приостановки потока до наступления некоторого события и последующей его активизации при наступлении этого события.
Пренебрежение вопросами синхронизации в многопоточной системе может привести к неправильному решению задачи или даже к краху системы.
Пример. Задача ведения базы данных клиентов некоторого предприятия.
Каждому клиенту отводится отдельная запись в базе данных, в которой имеются поля Заказ и Оплата. Программа, ведущая базу данных, оформлена как единый процесс, имеющий несколько потоков, в том числе:
- Поток А, который заносит в базу данных информацию о заказах, поступивших от клиентов.
- Поток В, который фиксирует в базе данных сведения об оплате клиентами выставленных счетов.
Оба эти потока совместно работают над общим файлом базы данных, используя однотипные алгоритмы:
- Считать из файла БД в буфер запись и клиенте с заданным идентификатором.
- Ввести новое значение в поле Заказ (для потока А) или оплата (для потока В).
- Вернуть модифицированную запись в файл БД.
Обозначим шаги 1-3 для потока А как А1-А3, а для потока В как В1-В3. Предположим, что в некоторый момент поток А обновляет поле Заказ записи о клиенте N. Для этого он считывает эту запись в свой буфер (шаг А1), модифицирует значение поля Заказ (шаг А2), но внести запись в базу данных не успевает, так как его выполнение прерывается, например, вследствие истечение кванта времени.
Предположим, что потоку В также потребовалось внести сведения об оплате относительно того же клиента N. Когда подходит очередь потока В, он успевает считать запись в свой буфер (шаг В1) и выполнить обновление поля Оплата (шаг В2), а затем прерывается. Заметим, что в буфере у потока В находится запись о клиенте N, в которой поле Заказ имеет прежнее, не измененное значение.
Важным понятием синхронизации процессов является понятие «критическая секция» программы. Критическая секция — это часть программы, в которой осуществляется доступ к разделяемым данным. Чтобы исключить эффект гонок по отношению к некоторому ресурсу, необходимо обеспечить, чтобы в каждый момент в критической секции, связанной с этим ресурсом, находился максимум один процесс. Этот прием называют взаимным исключением.
Простейший способ обеспечить взаимное исключение — позволить процессу, находящемуся в критической секции, запрещать все прерывания. Однако этот способ непригоден, так как опасно доверять управление системой пользовательскому процессу; он может надолго занять процессор, а при крахе процесса в критической области крах потерпит вся система, потому что прерывания никогда не будут разрешены.
Другим способом является использование блокирующих переменных. С каждым разделяемым ресурсом связывается двоичная переменная, которая принимает значение 1, если ресурс свободен (то есть ни один процесс не находится в данный момент в критической секции, связанной с данным процессом), и значение 0, если ресурс занят. На рисунке ниже показан фрагмент алгоритма процесса, использующего для реализации взаимного исключения доступа к разделяемому ресурсу D блокирующую переменную F(D). Перед входом в критическую секцию процесс проверяет, свободен ли ресурс D. Если он занят, то проверка циклически повторяется, если свободен, то значение переменной F(D) устанавливается в 0, и процесс входит в критическую секцию. После того, как процесс выполнит все действия с разделяемым ресурсом D, значение переменной F(D) снова устанавливается равным 1.
Если все процессы написаны с использованием вышеописанных соглашений, то взаимное исключение гарантируется. Следует заметить, что операция проверки и установки блокирующей переменной должна быть неделимой. Поясняется это следующим образом. Пусть в результате проверки переменной процесс определил, что ресурс свободен, но сразу после этого, не успев установить переменную в 0, был прерван. За время его приостановки другой процесс занял ресурс, вошел в свою критическую секцию, но также был прерван, не завершив работы с разделяемым ресурсом. Когда управление было возвращено первому процессу, он, считая ресурс свободным, установил признак занятости и начал выполнять свою критическую секцию. Таким образом, был нарушен принцип взаимного исключения, что потенциально может привести к нежелаемым последствиям. Во избежание таких ситуаций в системе команд машины желательно иметь единую команду «проверка-установка», или же реализовывать системными средствами соответствующие программные примитивы, которые бы запрещали прерывания на протяжении всей операции проверки и установки.
Реализация критических секций с использованием блокирующих переменных имеет существенный недостаток: в течение времени, когда один процесс находится в критической секции, другой процесс, которому требуется тот же ресурс, будет выполнять рутинные действия по опросу блокирующей переменной, бесполезно тратя процессорное время. Для устранения таких ситуаций может быть использован так называемый аппарат событий. С помощью этого средства могут решаться не только проблемы взаимного исключения, но и более общие задачи синхронизации процессов. В разных операционных системах аппарат событий реализуется по-своему, но в любом случае используются системные функции аналогичного назначения, которые условно называются WAIT(x) и POST(x), где x — идентификатор некоторого события.
Если ресурс занят, то процесс не выполняет циклический опрос, а вызывает системную функцию WAIT(D), здесь D обозначает событие, заключающееся в освобождении ресурса D. Функция WAIT(D) переводит активный процесс в состояние ОЖИДАНИЕ и делает отметку в его дескрипторе о том, что процесс ожидает события D. Процесс, который в это время использует ресурс D, после выхода из критической секции выполняет системную функцию POST(D), в результате чего операционная система просматривает очередь ожидающих процессов и переводит процесс, ожидающий события D, в состояние ГОТОВНОСТЬ.
Обобщающее средство синхронизации процессов предложил Дейкстра, который ввел два новых примитива. В абстрактной форме эти примитивы, обозначаемые P и V, оперируют над целыми неотрицательными переменными, называемыми семафорами. Пусть S такой семафор. Операции определяются следующим образом:
V(S): переменная S увеличивается на 1 одним неделимым действием; выборка, инкремент и запоминание не могут быть прерваны, и к S нет доступа другим процессам во время выполнения этой операции.
P(S): уменьшение S на 1, если это возможно. Если S=0, то невозможно уменьшить S и остаться в области целых неотрицательных значений, в этом случае процесс, вызывающий P-операцию, ждет, пока это уменьшение станет возможным. Успешная проверка и уменьшение также является неделимой операцией.
В частном случае, когда семафор S может принимать только значения 0 и 1, он превращается в блокирующую переменную. Операция P заключает в себе потенциальную возможность перехода процесса, который ее выполняет, в состояние ожидания, в то время как V-операция может при некоторых обстоятельствах активизировать другой процесс, приостановленный операцией P.
Потоки в Process Explorer
Потоки отдельного процесса можно увидеть в программе Process Explorer. Для этого нужно дважды кликнуть по процессу и в открывшемся окне перейти на вкладку «Threads»:
В колонках видна информация по каждому потоку:
- TID — идентификатор потока.
- CPU — загрузка процессора.
- Cycles Delta — общее количество циклов процессора, которое этот процесс использовал с момента последнего обновления работы Process Explorer. Скорость обновления программы можно настроить, указав например 5 минут.
- Suspend Count — количество приостановок потока.
- Service — название службы.
- Start Address — начальный адрес процедуры, который начинает выполнение нового потока. Выводится в формате:«модуль!функция».
При выделении потока, снизу показана следующую информация:
- Идентификатор потока.
- Время начала работы потока.
- Состояние потока.
- Время выполнения в режиме ядра и в пользовательском режиме.
- Счетчик переключения контекста для центрального процессора.
- Количество циклов процессора.
- Базовый приоритет.
- Динамический приоритет (текущий).
- Приоритет ввода / вывода.
- Приоритет памяти.
- Идеальный процессор (предпочтительный процессор).
Есть также кнопки:
Потоки
На центральном процессоре обрабатываются не сами процессы, а программные потоки. Каждый поток, это код загруженный программой. Программа может работать в одном потоке или создавать несколько. Если программа работает в несколько потоков, то она может выполняться на разных ядрах процессора. Посмотреть на потоки можно с помощью программы Process Explorer.
- два стека: для режима ядра и для пользовательского режима;
- локальную памятью потока (TLS, Thread-Local Storage);
- уникальный идентификатор потока (TID, Thread ID).
Приложение может создать дополнительный поток, например, когда у приложения есть графический интерфейс, который работает в одном потоке и ожидает от пользователя ввода каких-то данных, а второй поток в это время занимается обработкой других данных.
Изучение активности потока важно, если вам нужно разобраться, почему тот или иной процесс перестал реагировать, а в процессе выполняется большое число потоков. Потоков может быть много в следующих процессах:
Волокна и планирование пользовательского режима
Потоки выполняются на центральном процессоре, а за их переключение отвечает планировщик ядра. В связи с тем что такое переключение это затратная операция. В Windows придумали два механизма для сокращения таких затрат: волокна (fibers) и планирование пользовательского режима (UMS, User Mode Scheduling).
Во-первых, поток с помощью специальной функции может превратится в волокно, затем это волокно может породить другие волокна, таким образом образуется группа волокон. Волокна не видимы для ядра и не обращаются к планировщику. Вместо этого они сами договариваются в какой последовательности они будут обращаться к процессору. Но волокна плохо реализованы в Windows, большинство библиотек ничего не знает о существовании волокон. Поэтому волокна могут обрабатываться как потоки и начнутся различные сбои в программе если она использует такие библиотеки.
Потоки UMS (User Mode Scheduling), доступные только в 64-разрядных версиях Windows, предоставляют все основные преимущества волокон при минимуме их недостатков. Потоки UMS обладают собственным состоянием ядра, поэтому они «видимы» для ядра, что позволяет нескольким потокам UMS совместно использовать процессор и конкурировать за него. Работает это следующим образом:
- Когда двум и более потокам UMS требуется выполнить работу в пользовательском режиме, они сами могут периодически уступать управление другому потоку в пользовательском режиме, не обращаясь к планировщику. Ядро при этом думает что продолжает работать один поток.
- Когда потоку UMS все таки нужно обратиться к ядру, он переключается на специально выделенный поток режима ядра.
Задания
Задания Windows (Job) позволяют объединить несколько процессов в одну группу. Затем можно этой группой управлять:
- устанавливать лимиты (на память или процессорное время) для группы процессов входящих в задание;
- останавливать, приостанавливать, запускать такую группу процессов.
Посмотреть на задания можно с помощью Process Explorer.
Читайте также: