Что такое потоки процессора? Преодолевая границы Windows: процессы и потоки

Что такое потоки процессора? Преодолевая границы Windows: процессы и потоки

18.06.2019

Это четвертая статья из серии "Преодолевая границы Windows", в рамках которой я рассказываю об ограничениях, существующих для фундаментальных ресурсов в Windows. На сей раз, я собираюсь обсудить с вами ограничение на максимальное количество потоков и процессов, поддерживаемое Windows. Здесь я кратко опишу различие между потоком и процессом, ограничение потока опроса (от англ. survey thread), после чего мы поговорим об ограничениях, связанных с процессами. В первую очередь я решил рассказать об ограничениях потоков, так как каждый активный процесс имеет, по крайней мере, один поток (процесс, который завершился, но ссылка на который хранится в обработчике, предоставленном другим процессом, не имеет ни одного потока), так что ограничения процессов напрямую зависят от основных ограничений, связанных с потоками.

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

Процессы и потоки
Процесс Windows по своей сути является контейнером, в котором хранится код команд из исполняемого файла. Он представляет собой объект процесса ядра и Windows использует этот объект процесса и связанные с ним структуры данных для хранения и сопровождения информации об исполняемом коде приложения. Например, процесс имеет виртуальное адресное пространство, в котором хранятся его частные и общие данные и в которое отображаются исполняемый образ и связанные с ним библиотеки DLL. Windows с помощью инструментов диагностики записывает информацию об использовании процессом ресурсов для обеспечения учета и выполнения запросов и регистрирует ссылки процесса на объекты операционной системы в таблице дескриптора процесса. Процессы работают с контекстом безопасности, именуемом маркером, который идентифицирует учетную запись пользователя, группы учетной записи и привилегии, назначенные процессу.

Процесс включает в себя один или более потоков, которые фактически выполняют код в процессе (технически, выполняются не процессы, а потоки) и представлены в системе в виде объектов потоков ядра. Есть несколько причин, почему приложения создают потоки в дополнение к их исходному начальному потоку: 1) процессы, обладающие пользовательским интерфейсом, обычно создают потоки для того, чтобы выполнять свою работу и при этом сохранять отзывчивость основного потока к командам пользователя, связанными с вводом данных и управлением окнами; 2) приложения, которые хотят использовать несколько процессоров для масштабирования производительности или же которые хотят продолжать работать, в то время как потоки останавливают свою работу, ожидая синхронизации операций ввода/вывода, создают потоки, чтобы получить дополнительную выгоду от многопоточной работы.

Ограничения потоков
Помимо основной информации о потоке, включая данные о состоянии регистров ЦП, присвоенный потоку приоритет и информацию об использовании потоком ресурсов, у каждого потока есть выделенная ему часть адресного пространства процесса, называемая стеком, которую поток может использовать как рабочую память по ходу исполнения кода программы, для передачи параметров функций, хранения локальных переменных и адресов результатов работы функций. Таким образом, чтобы избежать нерациональной траты виртуальной памяти системы, первоначально распределяется только часть стека, или же часть ее передается потоку, а остаток просто резервируется. Поскольку стеки в памяти растут по нисходящей, система размещает так называемые "сторожевые" страницы (от англ. guard pages) памяти вне выделенной части стека, которые обеспечивают автоматическое выделение дополнительной памяти (называемой расширением стека), когда она потребуется. На следующей иллюстрации показано, как выделенная область стека углубляется и как сторожевые страницы перемещаются по мере расширения стека в 32-битном адресном пространстве:

Структуры Portable Executable (PE) исполняемых образов определяют объем адресного пространства, которое резервируется и изначально выделяется для стека потока. По умолчанию компоновщик резервирует 1Мб и выделяет одну страницу (4Кб), но разработчики могут изменять эти значения либо меняя значения PE, когда они организуют связь со своей программой, либо путем вызова для отдельного потока функции CreateTread . Вы можете использовать утилиту, такую как Dumpbin , которая идет в комплекте с Visual Studio, чтобы посмотреть настройки исполняемой программы. Вот результаты запуска Dumpbin с опцией /headers для исполняемой программы, сгенерированной новым проектом Visual Studio:

Переведя числа из шестнадцатеричной системы исчисления, вы можете увидеть, что размер резерва стека составляет 1Мб, а выделенная область памяти равна 4Кб; используя новую утилиту от Sysinternals под названием MMap , вы можете подключиться к этому процессу и посмотреть его адресное пространство, и тем самым увидеть изначально выделенную страницу памяти стека процесса, сторожевую страницу и остальную часть зарезервированной памяти стека:

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

Ограничения 32-битных потоков
Даже если бы у процесса вообще не было ни кода, ни данных и все адресное пространство могло бы быть использовано под стеки, то 32-битный процесс с установленным по умолчанию адресным пространством в 2 б мог бы создать максимум 2048 потоков. Вот результаты работы программы Testlimit , запущенной в 32-битной Windows с параметром -t (создание потоков), подтверждающие наличие этого ограничения:

Еще раз, так как часть адресного пространства уже использовалась под код и начальную динамическую память, не все 2Гб были доступны для стеков потоков, так что общее количество созданных потоков не смогло достигнуть теоретического предела в 2048 потоков.

Я попробовал запустить Testlimit с дополнительной опцией, предоставляющей приложению расширенное адресное пространство, надеясь, что если уж ему дадут больше 2Гб адресного пространства (например, в 32-битных системах это достигается путем запуска приложения с опцией /3GB или /USERVA для Boot.ini, или же эквивалентной опцией BCD на Vista и позднее increaseuserva), оно будет его использовать. 32-битным процессам выделяется 4Гб адресного пространства, когда они запускаются на 64-битной Windows, так сколько же потоков сможет создать 32-битный Testlimit, запущенный на 64-битной Windows? Если основываться на том, что мы уже обсудили, ответ должен быть 4096 (4Гб разделенные на 1Мб), однако на практике это число значительно меньше. Вот 32-битный Testlimit, запущенный на 64-битной Windows XP:

Причина этого несоответствия кроется в том факте, что когда вы запускаете 32-битное приложение на 64-битной Windows, оно фактические является 64-битным процессом, которое выполняет 64-битный код от имени 32-битных потоков, и потому в памяти для каждого потока резервируются области под 64-битные и 32-битные стеки потоков. Для 64-битного стека резервируется 256Кб (исключения составляют ОС, вышедшие до Vista, в которых исходный размер стека 64-битных потоков составляет 1Мб). Поскольку каждый 32-битный поток начинает свое существование в 64-битном режиме и размер стека, который ему выделяется при старте, превышает размер страницы, в большинстве случаев вы увидите, что под 64-битный стек потока выделяется как минимум 16Кб. Вот пример 64-битных и 32-битных стеков 32-битного потока (32-битный стек помечен как "Wow64"):

32-битный Testlimit смог создать в 64-битной Windows 3204 потока, что объясняется тем, что каждый поток использует 1Мб + 256Кб адресного пространство под стек (повторюсь, исключением являются версии Windows до Vista, где используется 1Мб+ 1Мб). Однако, я получил другой результат, запустив 32-битный Testlimit на 64-битной Windows 7:

Различия между результатами на Windows XP и Windows 7 вызвано более беспорядочной природой схемы распределения адресного пространства в Windows Vista, Address Space Layout Randomization (ASLR), которая приводит к некоторой фрагментации. Рандомизация загрузки DLL, стека потока и размещения динамической памяти, помогает улучшить защиту от вредоносного ПО. Как вы можете увидеть на следующем снимке программы VMMap, в тестовой системе есть еще 357Мб доступного адресного пространства, но наибольший свободный блок имеет размер 128Кб, что меньше чем 1Мб, необходимый для 32-битного стека:

Как я уже отмечал, разработчик может переустановить заданный по умолчанию размер резерва стека. Одной из возможных причин для этого может быть стремление избежать напрасного расхода адресного пространства, когда заранее известно, что стеком потока всегда будет использоваться меньше, чем установленный по умолчанию 1Мб. PE-образ Testlimit по умолчанию использует размер резерва стека в 64Кб, и когда вы указываете вместе параметром -t параметр -n, Testlimit создает потоки со стеками размером в 64Кб. Вот результат работы этой утилиты на системе с 32-битной Windows XP и 256Мб RAM (я специально провел этот тест на слабой системе, что подчеркнуть данное ограничение):

Здесь следует отметить, что произошла другая ошибка, из чего следует, что в данной ситуации причиной является не адресное пространство. Фактически, 64Кб-стеки должны обеспечить приблизительно 32 000 потоков (2Гб/64Кб = 32768). Так какое же ограничение проявилось в данном случае? Если посмотреть на возможных кандидатов, включая выделенную память и пул, то никаких подсказок в нахождении ответа на этот вопрос они не дают, поскольку все эти значения ниже их пределов:

Ответ мы можем найти в дополнительной информации о памяти в отладчике ядра, который укажет нам искомое ограничение, связанное с доступной резидентной памятью, весь объем которой был исчерпан:

Доступная резидентная память - это физическая память, выделяемая для данных или кода, которые обязательно должны находиться в оперативной памяти. Размеры невыгружаемого пула и невыгружаемых драйверов высчитываются независимо от этого, также как, например, память, зарезервированная в RAM для операций ввода/вывода. У каждого потока есть оба стека пользовательского режима, об этом я уже говорил, но у них также есть стек привилегированного режима (режима ядра), который используется тогда, когда потоки работают в режиме ядра, например, исполняя системные вызовы. Когда поток активен, его стек ядра закреплен в памяти, так что поток может выполнять код в ядре, для которого нужные страницы не могут отсутствовать.

Базовый стек ядра занимает 12Кб в 32-битной Windows и 24Кб в 64-битной Windows. 14225 потоков требуют для себя приблизительно 170Мб резидентной памяти, что точно соответствует объему свободной памяти на этой системе с выключенным Testlimit:

Как только достигается предел доступной системной памяти, многие базовые операции начинают завершаться с ошибкой. Например, вот ошибка, которую я получил, дважды кликнув на ярлыке Internet Explorer, расположенном на рабочем столе:

Как и ожидалось, работая на 64-битной Windows с 256Мб RAM, Testlimit смог создать 6600 потоков - примерно половину от того, сколько потоков эта утилита смогла создать в 32-битной Windows с 256Мб RAM - до того, как исчерпалась доступная память:

Причиной, по которой ранее я употреблял термин "базовый" стек ядра, является то, что поток, который работает с графикой и функциями управления окнами, получает "большой" стек, когда он исполняет первый вызов, размер которого равен (или больше) 20Кб на 32-битной Windows и 48Кб на 64-битной Windows. Потоки Testlimit не вызывают ни одного подобного API, так что они имеют базовые стеки ядра.
Ограничения 64-битных потоков

Как и у 32-битных потоков, у 64-битных потоков по умолчанию есть резерв в 1Мб для стека, но 64-битные имеют намного больше пользовательского адресного пространства (8Тб), так что оно не должно стать проблемой, когда дело доходит до создания большого количества потоков. И все же очевидно, что резидентная доступная память по-прежнему является потенциальным ограничителем. 64-битная версия Testlimit (Testlimit64.exe) смогла создать с параметром -n и без него приблизительно 6600 потоков на системе с 64-битной Windows XP и 256Мб RAM, ровно столько же, сколько создала 32-битная версия, потому что был достигнут предел резидентной доступной памяти. Однако, на системе с 2Гб оперативной памяти Testlimit64 смог создать только 55000 потоков, что значительно меньше того количества потоков, которое могла бы создать эта утилита, если бы ограничением выступила резидентная доступная память (2Гб/24Кб = 89000):

В данном случае причиной является выделенный начальный стек потока, который приводит к тому, что в системе заканчивается виртуальная память и появляется ошибка, связанная с нехваткой объема файла подкачки. Как только объем выделенной памяти достигает размера оперативной памяти, скорость создания новых потоков существенно снижается, потому что система начинает "пробуксовывать", ранее созданные стеки потоков начинают выгружаться в файл подкачки, чтобы освободить место для стеков новых потоков, и файл подкачки должен увеличиваться. С включенным параметром -n результаты те же, поскольку таким же остается начальный объем выделенной памяти стека.

Ограничения процессов
Число процессов, поддерживаемых Windows, очевидно, должно быть меньше, чем число потоков, потому как каждый процесс имеет один поток и сам по себе процесс приводит к дополнительному расходу ресурсов. 32-битный Testlimit, запущенный на системе с 64-битной Windows XP и 2Гб системной памяти создает около 8400 процессов:

Если посмотреть на результат работы отладчика ядра, то становится понятно, что в данном случае достигается ограничение резидентной доступной памяти:

Если бы процесс использовал резидентную доступную память для размещения только лишь стека потока привилегированного режима, Testlimit смог бы создать намного больше, чем 8400 потоков на системе с 2Гб. Количество резидентной доступной памяти на этой системе без запущенного Testlimit равно 1,9Гб:

Путем деления объема резидентной памяти, используемой Testlimit (1,9Гб), на число созданных им процессов получаем, что на каждый процесс отводится 230Кб резидентной памяти. Так как 64-битный стек ядра занимает 24 Кб, мы получаем, что без вести пропали примерно 206Кб для каждого процесса. Где же остальная часть используемой резидентной памяти? Когда процесс создан, Windows резервирует достаточный объем физической памяти, чтобы обеспечить минимальный рабочий набор страниц (от англ. working set). Это делается для того, чтобы гарантировать процессу, что любой ситуации в его распоряжении будет достаточное количество физической памяти для сохранения такого объема данных, который необходим для обеспечения минимального рабочего набора страниц. По умолчанию размер рабочего набора страниц зачастую составляет 200Кб, что можно легко проверить, добавив в окне Process Explorer столбец Minimum Working Set:

Оставшиеся 6Кб - это резидентная доступная память, выделяемая под дополнительную нестраничную память (от англ. nonpageable memory), в которой хранится сам процесс. Процесс в 32-битной Windows использует чуть меньше резидентной памяти, поскольку его привилегированный стек потока меньше.

Как и в случае со стеками потока пользовательского режима, процессы могут переопределять установленный для них по умолчанию размер рабочего набора страниц с помощью функции SetProcessWorkingSetSize . Testlimit поддерживает параметр -n, который, в совокупности с параметром -p, позволяет устанавливать для дочерних процессов главного процесса Testlimit минимально возможный размер рабочего набора страниц, равный 80Кб. Поскольку дочерним процессам нужно время, чтобы сократить их рабочие наборы страниц, Testlimit, после того, как он больше не сможет создавать процессы, приостанавливает работу и пробует ее продолжить, давая его дочерним процессам шанс выполниться. Testlimit, запущенный с параметром -n на системе с Windows 7 и 4Гб RAM уже другого, отличного от ограничения резидентной доступной памяти, предела - ограничения выделенной системной памяти:

На снимке снизу вы можете увидеть, что отладчик ядра сообщает не только о том, что был достигнут предел выделенной системной памяти, но и о том, что, после достижения этого ограничения, имели место тысячи ошибок распределения памяти, как виртуальной, так и памяти, выделенной под выгружаемый пул (предел выделенной системной памяти фактически был достигнут несколько раз, так как, когда случалась ошибка, связанная с нехваткой объема файла подкачки, этот самый объем увеличивался, отодвигая это ограничение):

До запуска Testlimit средний уровень выделенного объема памяти был равен приблизительно 1,5Гб, так что потоки заняли около 8Гб выделенной памяти. Следовательно, каждый процесс потреблял примерно 8 Гб/6600 или 1,2Мб. Результат выполнения команды!vm отладчика ядра, которая показывает распределение собственной памяти (от англ. private memory) для каждого процесса, подтверждает верность данного вычисления:

Начальный объем выделенной памяти под стек потока, описанный ранее, оказывает незначительное влияние на остальные запросы на предоставление памяти, требуемой для структур данных адресного пространства процесса, записей таблицы страниц, таблицы дескрипторов, объектов процесса и потока, и собственных данных, которые процесс создает во время своей инициализации.

Сколько процессов и потоков будет достаточно?
Таким образом, ответы на вопросы "сколько потоков поддерживает Windows?" и "сколько процессов вы можете одновременно запустить на Windows?" взаимосвязаны. Помимо нюансов методов, по которым потоки определяют размер их стека и процессы определяют их минимальный рабочий набор страниц, двумя главными факторами, определяющим ответы на эти вопросы для каждой конкретной системы, являются объем физической памяти и ограничение выделенной системной памяти. В любом случае, если приложение создает достаточное количество потоков или процессов, чтобы приблизиться к этим пределам, то его разработчику следует пересмотреть проект этого приложения, поскольку всегда существуют различные способы достигнуть того же результата с разумным числом процессов. Например, основной целью при масштабировании приложения является стремление сохранить число выполняющихся потоков равным числу ЦП, и один из способов добиться этого состоит в переходе от использования синхронных операции ввода/вывода к асинхронным с использованием портов завершения, что должно помочь сохранить соответствие числа запущенных потоков с числом ЦП.

Аннотация: Стратегия управления памятью, процессы, потоки и данные.

Процессы

Появление у компьютера операционной системы (ОС) позволило перейти от однопрограммного режима работы к многопрограммному (мультипрограммному) режиму работы. Операционную систему часто называют многозадачной, полагая, что она выполняет одновременно несколько задач. То, что для ОС является задачей, с точки зрения C# программиста является приложением или проектом. В разных операционных системах для одних и тех же или схожих понятий используются разные термины. Далее, говоря об ОС, будем иметь в виду ОС Windows , и будем использовать терминологию, характерную для этой ОС.

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

Одна из главных задач ОС состоит в распределении ограниченных ресурсов компьютера между всеми приложениями, претендующими на эти ресурсы. О каких ресурсах идет речь? Основными, конечно же, являются два ресурса - память и время - прежде всего, оперативная память и время процессоров. Экономия этих ресурсов является постоянной заботой программиста. В серьезных приложениях, разрабатывая алгоритм решения, программисту всегда приходится идти на компромисс , поскольку, как правило, эти два ресурса конфликтуют. Выиграешь в памяти, проиграешь во времени работы, пожертвуешь памятью, выиграешь во времени.

При создании новых компьютеров, согласно закону Мура, каждые полтора года эти ресурсы удваиваются. В 1960 году оперативная память компьютера Урал, одного из лучших компьютеров на тот момент, составляла 2К, а быстродействие - 100 операций в секунду. Сегодня современный суперкомпьютер имеет быстродействие , измеряемое петафлопами - 10 15 - тысяча триллионов операций с плавающей точкой. Аналогичным образом возросли и объемы оперативной памяти, примерно сто триллионов байтов. Казалось бы, можно не заботиться об экономии памяти и времени. Но это не так. Сложность появляющихся задач также растет по экспоненте. Считается, что всегда есть задачи, которые хотелось бы решить на компьютере, но мощности компьютеров не хватает для их решения.

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

Процесс - владелец ресурсов . Когда ОС создает процесс, то выделяет ему ресурсы. Процесс, несмотря на свое название, не выполняет код приложения, следовательно, время процессора непосредственно процессу не выделяется. Когда говорится, "процессы ядра ОС могут выполняться в привилегированном режиме, выполняя команды компьютера, недоступные другим процессам", то это некоторая условность. Код выполняют потоки. Именно потокам ОС выделяет процессорное время . При создании процесса ОС всегда создает поток , связывая его с процессом. В процессе выполнения потока могут создаваться и другие потоки, связанные с процессом. Подробнее об этом поговорим чуть позже, а сейчас рассмотрим стратегию управления памятью.

Процессы и стратегия управления памятью

Блестящая стратегическая идея в управлении памятью состоит в том, чтобы процессу выделять не реальную оперативную память, а виртуальную, которую уже потом некоторым образом связывать с реальной памятью. Для 32 разрядных компьютеров адресное пространство составляет 2 32 байтов, примерно 4 Гб. Оперативная память компьютера долгие годы была меньше виртуальной, теперь она практически сравнялась по объему. При желании можно приобрести 32-х разрядный ПК с 4 Гб оперативной памяти, хотя это и неэффективно, поскольку только 2 или 3 Гб будут использоваться в качестве оперативной памяти. По этой причине в ближайшие годы предстоит массовый переход на 64-х битную архитектуру, где виртуальная память становится практически неограниченной по нынешним меркам, так что любая реальная оперативная память будет составлять малую толику виртуального пространства.

Ноутбук, на котором я сейчас пишу этот текст, является 64-битным компьютером с оперативной памятью в 6 Гб, с 4-мя физическими ядрами и соответственно с 8-мю логическими процессорами.

Вернемся к 32-х битной архитектуре. Из 4-х Гб виртуальной памяти ОС отводит процессу 2 или 3 Гб виртуальной памяти, оставляя для себя оставшуюся часть пространства. Так что ни один из процессов не обижен, каждый получает виртуальную память одинакового размера. В то же время достаточное пространство отводится самой операционной системе, которая занимает постоянную часть виртуальной памяти, не пересекающееся с памятью, отводимой процессам. Следующая идея состоит в том, что виртуальная и оперативная память рассматривается как состоящая из страниц. Страницы могут быть большими и малыми. У тех и других есть свои преимущества. Малые страницы имеют сравнительно небольшой объем, обычно 4К.

При трансляции приложения - его программный код и необходимые данные размещаются в виртуальной памяти. На одной из виртуальных страниц находится точка входа в приложение - процедура Main, с которой начинается выполнение. Но процессор компьютера не может выполнять код и использовать данные, находящиеся в виртуальной памяти, они должны находиться в реальной оперативной памяти. Поэтому при создании процесса приложение загружается в оперативную память. Это означает, что соответствующие виртуальные страницы отображаются на страницы реальной оперативной памяти. Всякий раз, когда при выполнении требуется очередная виртуальная страница, менеджер операционной системы проверяет, загружен ли ее образ в оперативную память, и если нет, то происходит загрузка с диска (внешней памяти) соответствующей страницы в свободную страницу оперативной памяти. Но оперативная память ограничена по сравнению с виртуальной. Следует помнить, что ОС одновременно выполняет несколько приложений, все они претендуют на оперативную память, так что "пряников на всех может не хватить" - может оказаться, что свободных страниц оперативной памяти нет. Тогда наступает время свопинга - одна из занятых страниц оперативной памяти вытесняется на диск, и новая страница загружается на ее место. Какую страницу вытеснить - это проблема, решаемая операционной системой. У ОС есть свои критерии оценки того, какая из страниц наиболее вероятно не понадобится в ближайшее время. Как правило, эти критерии хорошо работают и свопинг происходит не часто, хотя встречаются "плохие" примеры, когда значительная часть времени уходит на свопинг - обмен страницами между внешней и оперативной памятью. Причина того, что свопинг происходит к счастью не часто, понятна - большую часть времени приложение проводит, выполняя в цикле некоторую часть программы, работая с фиксированным набором данных. В этом случае приложение локально работает с небольшим набором страниц, которые уже находятся в оперативной памяти. По ходу развития алгоритма точки локализации смещаются, используются новые страницы памяти, но изменение точек локализации происходит, как правило, не часто в сравнении с общим временем решения задачи.

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

Потоки и стратегия управления временем процессоров

Процесс - объект, владеющий памятью и другими ресурсами, но не выполняющий код. Поток - динамический объект, он может быть создан в процессе выполнения кода приложения и может быть удален по ходу выполнения. У процесса может быть несколько одновременно существующих потоков, выполняющих различные фрагменты кода. ОС планирует время процессоров между потоками, и для нее не имеет значение, какому процессу принадлежит тот или иной поток. Говоря о потоках в операционной системе, будем рассматривать общую схему, опуская многие детали, основываясь на стратегии распределения процессорного времени, характерной для ОС Windows. Эта стратегия носит название "вытесняющая приоритетная многозадачность". Многозадачность в данном контексте означает, что планировщик ОС, распределяет время процессора между многими потоками, присутствующими в ОС.

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

Значит ли это, что могут быть "обиженные" приложения с низким приоритетом, до выполнения которых никогда не дойдет очередь? Это не так. ОС старается никого не обидеть. Если некоторое приложение долго не выполнялось, то ОС временно повышает его приоритет, так что и оно начнет выполняться.

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

На Рис. 2.1 показаны возможные состояния потока и переходы из одного состояния в другое.

После создания потока и должной инициализации поток переходит в состояние "готовность", занимая в своей группе приоритетности место в конце очереди". Планировщик ОС в соответствии с описанной стратегией выбирает поток, переводя его в состояние "выполнение". По истечении отведенного кванта времени поток возвращается в состояние "готовность", становясь в хвост очереди в своей группе приоритетности. Из состояния "выполнение" поток может перейти в другие состояния и до завершения отведенного кванта времени. В состояние "готовность" он может перейти, если появился поток с большим приоритетом. В состояние "завершение" поток переходит, выполнив свою работу, завершив выполнение отведенного ему фрагмента кода. В состояние "ожидание" поток может перейти, если его дальнейшее выполнение возможно только после наступления некоторого события (например, ему требуются данные, а устройство компьютера, выполняющее ввод этих данных, еще не завершило свою работу). Из состояния "ожидание" поток может перейти в состояние "готовность", если наступило событие, ожидаемое потоком. За время жизни потока он многократно проходит цикл {готовность} -> {выполнение} -> {ожидание} -> {готовность}, иногда минуя переход в состояние "ожидания".

Для понимания картины в целом нужно помнить, что весь процесс вычислений на компьютере управляется событиями. Каждый поток во время своего выполнения многократно прерывается, уступая свое место другому потоку. События, приводящие к приостановке выполнения потока, могут быть асинхронными по отношению к его работе, - они могут произойти в любой момент выполнения потока. Такие события называются прерываниями. Синхронные события, связанные с тем, что по тем или иным причинам выполнение потока становится невозможным, называются исключениями или исключительными ситуациями. Типичными примерами исключительных ситуаций являются такие ситуации, как попытка деления целого числа на ноль или попытка чтения записи несуществующего файла.

Прерывания инициируются аппаратурой компьютера, чаще всего таймером и устройствами ввода-вывода. ОС в очень коротком цикле рассматривает все возникшие прерывания и должным образом их обрабатывает. Когда возникает прерывание от таймера, то ОС при его обработке из кванта времени, отводимого выполняемому потоку, вычитает время, равное интервалу таймера. Если отводимое потоку время исчерпано, поток снимается с выполнения, переходя в состояние "готовность". Когда устройство ввода заканчивает выполнение очередного задания, оно инициализирует аппаратное прерывание, свидетельствующее о завершении работы. Обрабатывая это прерывание, ОС может перевести некоторый поток из состояния "ожидания" в состояние "готовности", поскольку выполнена его заявка на ввод данных.

У исключений, связанных с самим потоком, более широкий спектр. Потоку, например, может понадобиться ввод внешних данных. Поток не может непосредственно обратиться к устройству ввода. Устройство одно, а потоков много. Поэтому поток вызывает соответствующий системный сервис. С точки зрения ядра ОС возникло исключение. При его обработке поток переводится в режим "ожидания", и начинает работать поток, содержащий соответствующий сервис, который анализирует загруженность устройства, формирует новую заявку для устройства, ставя ее в очередь.

Причина исключения может быть как аппаратной, так и программной. Деление на ноль, это, конечно же, программная ошибка. Исключения, связанные с тем, что не прочитаны требуемые внешние данные, могут быть связаны как со сбоем аппаратуры, так и с неверно заданными адресами в программе. Если письмо не доставлено, то виноватой может быть почтовая служба, а возможно вы послали письмо "на деревню дедушке".

Поток может сам инициировать исключение, уведомляя, например, ОС о том, что он "засыпает" на некоторое фиксированное время. Обрабатывая это прерывание, ОС переводит поток в состояние "ожидание". При обработке одного из очередных прерываний по таймеру, когда завершается время "сна", указанное потоком, поток переводится из состояния "ожидание" в состояние "готовность". Поток может перейти в состояние "ожидание" и по другим причинам, возникающим в ходе выполнения программного кода, например, ожидая завершения работы другого потока. Примеры того, как всем этим может управлять С# программист, будут рассмотрены позднее.

Современные компьютеры, настольные и портативные имеют несколько процессоров. Практически все продающиеся сегодня компьютеры, предназначенные для индивидуального использования, имеют от двух до четырех ядер. Это позволяет организовать параллельное выполнение фрагментов кода в одном приложении, ускоряя его работу. Для этого в приложении создаются несколько потоков, параллельно работающих, каждый в отдельном ядре процессора. Иногда удается при N ядрах примерно в N раз уменьшить общее время работы приложения. Но, конечно, это возможно не для всякого приложения, а если и возможно, то требует усилий со стороны программиста. Многопоточный параллельный алгоритм сложнее однопоточного последовательного алгоритма. Сложнее становится и отладка. Нужны ли программисту дополнительные сложности? Хотим мы того или нет, но параллельное программирование становится одним из важнейших направлений развития современного программирования. Современные суперкомпьютеры имеют сотни тысяч процессоров. Высокопроизводительные вычисления, требующие распараллеливания алгоритмов, становятся реальностью. Использовать многоядерный компьютер только для последовательных алгоритмов неэффективно, - все равно, что использовать телескоп в качестве лупы для чтения убористого текста.

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

Процессы, потоки и данные

Операционная система работает с процессами и потоками и ей необходимо хранить информацию об этих объектах. Каждый процесс хранит код приложения и данные, создаваемые в процессе выполнения приложения. С данными работает поток, выполняя программный код. Эти данные могут быть локальными для потока, созданы в потоке и используются только одним потоком. Но у процесса может быть несколько потоков, в этом случае существуют данные процесса, глобальные для потока, обеспечивающие взаимодействие между потоками.

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

Еще одна проблема с данными состоит в том, что поток может в любой момент быть прерванным, перейти в состояние "ожидание" или "готовность", а потом вновь продолжить свою работу в прерванной точке. Для поддержки такой возможности ОС использует объект, называемый контекстом потока. Он включает локальные данные потока, счетчик, указывающий на команду, с которой необходимо начать прерванное выполнение, другую служебную информацию, необходимую для корректного продолжения прерванной работы.

Есть еще одна проблема, связанная с данными, используемыми потоком. Дело в том, что команды процессора делятся на две группы - команды, выполняемые в привилегированном режиме, и команды, выполняемые в пользовательском режиме. Команды в привилегированном режиме могут выполнять только системные программы, составляющие ядро операционной системы. Эти системные сервисы могут вызываться потоком по ходу выполнения программного кода. Данные о потоке, используемые ядром ОС, хранятся отдельно от данных, используемых в пользовательском режиме.

В адресном пространстве ОС для каждого процесса в момент его создания выделяется специальный блок памяти, называемый EPROCESS, хранящий системную информацию о процессе. Еще один блок с системной информацией - PEB (Process Environment Block) хранится в адресном пространстве самого процесса. В страницах виртуального адресного пространства процесса хранится код приложения и данные, необходимые для работы. Данные хранятся в памяти, называемой стеком (stack) и кучей (heap). Куча создается в момент создания процесса. У процесса может быть несколько куч. Код приложения может храниться частично в закрытых страницах, частично разделяемых страни

цах памяти. Разделяемые страницы двух или более процессов могут отображаться на одни и те же страницы реальной оперативной памяти. За счет этого несколько процессов могут использовать один и тот же программный код в оперативной памяти. Разные приложения могут использовать одну и ту же библиотеку классов - DLL, расположенную в оперативной памяти без дублирования. Понятно, что это не касается данных, данные у каждого процесса свои. Для хранения данных процесса операционная система выделяет защищенные страницы, так что никакой процесс не может получить доступ к данным другого процесса. Есть исключение из этого правила, когда организуется взаимодействие между процессами, но эту ситуацию мы рассматривать не будем.

В адресном пространстве ОС для каждого потока в момент его создания выделяется специальный блок памяти, называемый TPROCESS, хранящий системную информацию о потоке, а в адресном пространстве процесса создается блок с системной информацией - TEB (Thread Environment Block). Для каждого потока создается контекст потока. Уже говорилось, что в ходе работы процессора компьютера с большой частотой происходит смена потоков - пользовательских и системных. Процессор прекращает выполнять один поток и начинает выполнять другой поток. Процесс переключения называется переключением контекстов. Понятно, что, если в любой момент выполнение потока может быть прервано, а затем продолжено через некоторое время, то контекст потока должен содержать всю информацию, необходимую для продолжения вычислений в точке прерывания. Поэтому контекст потока включает все локальные данные потока, адрес команды в программном коде, с которой продолжится вычисление, состояние всех системных регистров в момент прерывания, состояния всех файлов, с которыми работал поток.

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

Добрый день. Сегодня хотелось бы разобрать, что такое потоки в процессоре. Те самые, о функциях и возможностях которых большинство и не догадывается, однако любят хвастаться остальным.

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

Недостаток технологии заключается в следующем:

  • оба потока обращаются к единой кэш-памяти 2 и 3 уровней;
  • тяжелые вычислительные процессы могут вызвать конфликт в системе.

Если очень грубо, то все кирпичи с одного места на другое можно перенести в одной руке (1 поток), либо в двух (2 потока), но человек при этом один (1 ядро) и устает одинаково при любых условиях, хоть его производительность фактически увеличивается вдвое. Иными словами, мы упираемся в производительность ЦП, а конкретней в его частоту.

В этой статье мы поговорим на такие темы, как процессы и потоки , дискрипторы процесса , поговорим о синзронизации потоков и затронем всеми любимый диспетчер задач windows .

На протяжении существования процесса его выполнение может быть многократно прервано и продолжено. Для того, чтобы возобновить выполнение процесса , необходимо восстановить состояние его операционной среды. Состояние операционной среды отображается состоянием регистров и программного счетчика, режимом работы процессора, указателями на открытые файлы, информацией о незавершенных операциях ввода-вывода, кодами ошибок выполняемых данным процессом системных вызовов и т.д. Эта информация называется контекстом процесса .

Для того чтобы ОС могла управлять процессами, она должна располагать всей необходимой для этого информацией. С этой целью на каждый процесс заводится дескриптор процесса .

Дескриптор – специальная информационная структура, которая заводится на каждый процесс (описатель задачи, блок управления задачей).

В общем случае дескриптор содержит следующую информацию:

  1. Идентификатор процесса.
  2. Тип (или класс) процесса, который определяет для супервизора некоторые правила предоставления ресурсов.
  3. Приоритет процесса.
  4. Переменную состояния, которая определяет, в каком состоянии находится процесс (готов к работе, в состоянии выполнения, ожидание устройства ввода-вывода и т.д.)
  5. Защищенную область памяти (или адрес такой зоны), в которой хранятся текущие значения регистров процессора, если процесс прерывается, не закончив работы. Эта информация называется контекстом задачи .
  6. Информацию о ресурсах, которыми процесс владеет и/или имеет право пользоваться (указатели на открытые файлы, информация о незавершенных операциях ввода/вывода и т.п.).
  7. Место (или его адрес) для организации общения с другими процессами.
  8. Параметры времени запуска (момент времени, когда процесс должен активизироваться, и периодичность этой процедуры).
  9. В случае отсутствия системы управления файлами – адрес задачи на диске в ее исходном состоянии и адрес на диске, куда она выгружается из оперативной памяти, если ее вытесняет другая.

Дескриптор процесса по сравнению с контекстом содержит более оперативную информацию, которая должна быть легко доступна подсистеме планирования процессов. Контекст процесса содержит менее актуальную информацию и используется операционной системой только после того, как принято решение о возобновлении прерванного процесса.

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

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

Например, состояний ожидания завершения операции ввода/вывода может быть столько, сколько устройств ввода/вывода содержится в системе.

Процессы и потоки

Чтобы поддерживать мультипрограммирование, ОС должна определить и оформить для себя те внутренне единицы работы, между которыми будет разделяться процессор и другие ресурсы компьютера. В настоящее время в большинстве ОС определены два типа единиц работы:

  • Процесс (более крупная единица работы).
  • Поток (нить или тред) – более мелкая единица работы, которую требует для своего выполнения процесс.
  • Когда говорят о процессах , то тем самым хотят отметить, что ОС поддерживает их обособленность: у каждого процесса имеется свое виртуальное адресное пространство, каждому процессу назначаются свои ресурсы – файлы, окна и др. Такая обособленность нужна для того, чтобы защитить один процесс от другого, поскольку они, совместно используя все ресурсы вычислительной системы, конкурируют друг с другом.

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

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

Например, некоторые операции, выполняемые приложением, могут требовать для своего исполнения достаточно длительного использования ЦП. В этом случае при интерактивной работе с приложением пользователь вынужден долго ожидать завершения заказанной операции и не может управлять приложением до тех пор, пока операция не выполнится до самого конца. Такие ситуации встречаются достаточно часто, например, при обработке больших изображений в графических редакторах. Если же программные модули, исполняющие такие длительные операции, оформлять в виде самостоятельных «подпроцессов» (потоков ), которые будут выполняться параллельно с другими «подпроцессами», то у пользователя появляется возможность параллельно выполнять несколько операций в рамках одного приложения (процесса).

Можно выделить следующие отличия потоков от процессов :

  • ОС для потоков не должна организовывать полноценную виртуальную машину.
  • Потоки не имеют своих собственных ресурсов, они развиваются в том же виртуальном адресном пространстве, могут пользоваться теми же файлами, виртуальными устройствами и иными ресурсами, что и данный процесс.
  • Единственное, что потокам необходимо иметь, — это процессорный ресурс. В однопроцессорной системе потоки разделяют между собой процессорное время так же, как это делают обычные процессы, а в многопроцессорной системе могут выполняться одновременно, если не встречают конкуренции из-за обращения к иным ресурсам.

Главное, что обеспечивает многопоточность , — это возможность параллельно выполнять несколько видов операций в одной прикладной программе. За счет чего реализуется эффективное использование ресурсов ЦП, а суммарное время выполнения задач становится меньше.

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

Диспетчер задач WINDOWS

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

Диспетчер задач служит для отображения ключевых показателей быстродействия компьютера. Для выполняемых программ можно просмотреть их состояние и завершить программы, переставшие отвечать на запросы. Имеется возможность просмотра активности выполняющихся процессов с использованием до 15 параметров, а также графиков и сведений об использовании ЦП и памяти.

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

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

На вкладке Быстродействие, отображаются сведения о счетчике дескрипторов и потоках, параметры памяти:

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

Синхронизация потоков и процессов заключается в согласовании их скоростей путем приостановки потока до наступления некоторого события и последующей его активизации при наступлении этого события.

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

Пример . Задача ведения базы данных клиентов некоторого предприятия.

Каждому клиенту отводится отдельная запись в базе данных, в которой имеются поля Заказ и Оплата. Программа, ведущая базу данных, оформлена как единый процесс, имеющий несколько потоков, в том числе:

  • Поток А, который заносит в базу данных информацию о заказах, поступивших от клиентов.
  • Поток В, который фиксирует в базе данных сведения об оплате клиентами выставленных счетов.

Оба эти потока совместно работают над общим файлом базы данных, используя однотипные алгоритмы:

  1. Считать из файла БД в буфер запись и клиенте с заданным идентификатором.
  2. Ввести новое значение в поле Заказ (для потока А) или оплата (для потока В).
  3. Вернуть модифицированную запись в файл БД.

Обозначим шаги 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.

Взаимоблокировка процессов

При организации параллельного выполнения нескольких процессов одной из главных функций ОС является корректное распределение ресурсов между выполняющимися процессами и обеспечение процессов средствами взаимной синхронизации и обмена данными.

При параллельном исполнении процессов могут возникать ситуации, при которых два или более процесса все время находятся в заблокированном состоянии. Самый простой случай – когда каждый из двух процессов ожидает ресурс, занятый другим процессом. Из-за такого ожидания ни один из процессов не может продолжить исполнение и освободить в конечном итоге ресурс, необходимый другому процессу. Эта тупиковая ситуация называется дедлоком (dead lock), тупиком , клинчем или взаимоблокировкой .

Говорят, что в мультизадачной системе процесс находится в состоянии тупика, если он ждет события, которое никогда не произойдет.

Тупиковые ситуации надо отличать от простых очередей, хотя и те и другие возникают при совместном использовании ресурсов и внешне выглядят похоже: процесс приостанавливается и ждет освобождения ресурса. Однако очередь — это нормальное явление, неотъемлемый признак высокого коэффициента использования ресурсов при случайном поступлении запросов. Она возникает тогда, когда ресурс недоступен в данный момент, но через некоторое время он освобождается, и процесс продолжает свое выполнение. Тупик же является в некотором роде неразрешимой ситуацией.

Проблема тупиков включает в себя следующие задачи:

  1. предотвращение тупиков.
  2. распознавание тупиков.
  3. восстановление системы после тупиков.

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

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

Если же тупиковая ситуация возникла, то не обязательно снимать с выполнения все заблокированные процессы. Можно снять только часть из них, при этом освобождаются ресурсы, ожидаемые остальными процессами, можно вернуть некоторые процессы в область свопинга, можно совершить «откат» некоторых процессов до так называемой контрольной точки, в которой запоминается вся информация, необходимая для восстановления выполнения программы с данного места. Контрольные точки расставляются в программе в местах, после которых возможно возникновение тупика.

В однозадачной ОС пользователь может запустить одновременно не более одной программы и только после того как она закончит работу можно будет запустить другую. В многозадачной ОС пользователи могут одновременно запускать несколько программ или даже несколько копий одной программы.

В чем разница между программой и процессом? Программа – это статическая последовательность команд, а процесс (process ) – это программа и системные ресурсы, необходимые для ее выполнения. Процесс является субъектом владения ресурсами и единицей работы. ОС выделяет каждому процессу порцию системных ресурсов и гарантирует, что программа каждого процесса будет направляться на исполнение в определенном порядке и своевременно.

ОС содержит блок кода, управляющий созданием и удалением процессов, а также отношениями между ними. Этот код называется структурой процессов (process structure ) и в Windows NT реализован диспетчером процессов (process manager ) .

Его основная задача предоставить набор базовых сервисов процесса, которые подсистемы среды могли бы использовать для эмуляции своих собственных уникальных структур процессов.

В разных ОС процессы реализованы по-разному. Они различаются своим представлением (структурами данных), способами именования и защиты, а также отношениями между собой. Базовые процессы Windows NT имеют ряд характеристик, отличающих их от процессов других ОС:

  • процессы реализованы как объекты, и доступ к ним осуществляется посредством объектных сервисов;
  • в адресном пространстве процесса может исполняться несколько потоков;
  • объект-процесс и объект-поток имеют встроенные возможности синхронизации.

Что такое процесс?

Процесс состоит из:

  • исполняемой программы (код и данные);
  • закрытого адресного пространства (address space ) , т.е. набора адресов виртуальной памяти, который процесс может использовать;
  • системных ресурсов , выделяемых ОС процессу во время выполнения программы (семафоров, файлов и т.д.);
  • по крайней мере, одного потока управления (thread of execution ). Поток – это сущность внутри процесса, которую ядро NT направляет на исполнение. Без него программа процесса не может выполняться.

Адресное пространство

С помощью системы виртуальной памяти (virtual memory) программисты (и создаваемые ими процессы) получают логический образ памяти, который не совпадает с ее физической структурой (см. Рис. 1 Виртуальная и физическая память).

При всяком обращении процесса по виртуальному адресу система виртуальной памяти транслирует этот адрес в физический адрес. Она также предотвращает непосредственный доступ процесса к виртуальной памяти, занятой другими процессами или ОС. Для исполнения кода ОС или доступа к памяти ОС поток должен выполняться в режиме ядра (kernel mode) . Большинство процессов – это процессы пользовательского режима (user mode ).

Поток пользовательского режима получает доступ к ОС, вызывая некоторый системный сервис. Когда поток вызывает сервис, происходит переключение из пользовательского режима в режим ядра. ОС проверяет аргументы, переданные сервису, после чего исполняет сервис. Перед возвратом управления пользовательской программе ОС переключает поток обратно в пользовательский режим.

Системные ресурсы

Кроме закрытого адресного пространства, с каждым процессом связан набор системных ресурсов.

Маркер доступа (Access Token ) присоединяет к процессу ОС. Это объект исполнительной системы, который содержит информацию о правах зарегистрированного в системе пользователя, которого представляет данный процесс. Если процессу требуется получить информацию о своем маркере доступа или изменить некоторые атрибуты маркера, он должен открыть описатель своего объекта-маркера. Подсистема защиты определяет, есть ли у объекта такое право.

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

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

Объект-процесс

Каждый процесс в Windows NT представлен блоком процесса , создаваемым исполнительной системой (EPROCESS ). В блоке EPROCESS содержатся атрибуты процесса и указатели на некоторые структуры данных. Так, у каждого процесса есть один или более потоков, представляемых блоками потоков исполнительной системы (ETHREAD ). Блок EPROCESS и связанные с ним структуры данных хранятся в системном пространстве. Исключение составляет только блок переменных окружения процесса (process environment block, PEB ), он находится в адресном пространстве процесса (см. Рис. 3 Блоки переменных окружения процесса (PEB) и потока (TEB)).

В исполнительной системе процессы – это объекты исполнительной системы, создаваемые и уничтожаемые диспетчером объектов. Объект-процесс, как и другие объекты, содержит заголовок, создаваемый и инициализируемый диспетчером объектов. В заголовке хранятся стандартные атрибуты объекта, такие как дескриптор защиты объекта, имя и каталог объектов, в котором хранится имя, если оно есть.

Диспетчер процессов определяет атрибуты, хранящиеся в теле объектов-процессов, а также предоставляет системные сервисы для чтения и изменения этих атрибутов. Атрибуты и сервисы для объектов-процессов показаны на Рис. 4 Блоки процесса исполнительной системы (EPROCESS) и ядра (KPROCESS). Объект процесс исполнительной системы включает объект процесс ядра (содержит указатель на объект процесс ядра). Ядро управляет объектом процесс ядра, а исполнительная система управляет объектом исполнительной системы.

Мы рассматриваем основные атрибуты этих объектов по отдельности, для того чтобы лучше понять какие задачи решаются ядром, а какие исполнительной системой. При выполнении лабораторных работ эта детализация не существенна и надо будет использовать Win32 API функции для работы с объектом процесс исполнительной системы.

Рассмотрим основные атрибуты:

  • идентификатор процесса – уникальное значение, идентифицирующее процесс в ОС;
  • базовый приоритет — базовый приоритет потоков процесса;
  • привязка к процессорам (процессорное сродство) – набор процессоров, на которых потоки процесса могут исполняться по умолчанию;
  • размеры квот – максимальный объем резидентной и нерезидентной системной памяти, пространства в файле подкачки и процессорного времени, выделяемый пользовательскому процессу;
  • статус завершения – причина завершения процесса.

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

Базовый приоритет процесса помогает ядру NT регулировать приоритет потоков в системе. Приоритеты потоков изменяются, но всегда остаются в диапазоне базовых приоритетов их процессов.

Большинство сервисов объекта-процесса мы будем использовать при выполнении лабораторных работ. Например, сервис завершения процесса останавливает исполнение всех его потоков, закрывает все открытые описатели объектов и уничтожает виртуальное адресное пространство процесса.

Что такое поток?

Поток – это единица исполнения, отдельный счетчик команд или подлежащая планированию сущность внутри процесса.

В то время как процесс – это логическое представление работы, которую должна выполнить ОС, поток отображает одну из, возможно, многих необходимых подзадач. Предположим, что пользователь запустил приложение для работы с базой данных. ОС представляет этот вызов приложения как один процесс. Пусть теперь пользователь запросил генерацию отчета по данным из базы и сохранение этого отчета в файле. Пока идет выполнение этой длительной операции, пользователь ввел новый запрос к базе данных. ОС представляет каждый из запросов – генерацию отчета и новый запрос к базе – как отдельные потоки внутри процесса приложения для работы с базой данных. Эти потоки могут выполняться процессором независимо друг от друга, т.е. обе операции можно выполнять в одно и то же время (параллельно).

Основные составляющие потока в исполнительной системе NT:

  • Уникальный идентификатор, называемый идентификатором клиента
  • Содержимое набора регистров, отражающее состояние процессора
  • Два стека: один используется потоком при работе в пользовательском режиме, а другой — в режиме ядра
  • Собственная область памяти, предназначенная для использования подсистемами, библиотеками периода выполнения и динамически подключаемыми библиотеками (DLL).

Регистры, стек, и собственная область памяти называются контекстом (context) потока. Фактически данные, составляющие контекст потока, определяются типом процессора.

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

Многозадачность и многопроцессорная обработка

ОС вытесняющей многозадачностью должна использовать тот или иной алгоритм, позволяющий ей распределять процессорное время между потоками. Каждые 20 мс Windows просматривает все существующие объекты потоки и отмечает те из них, которые могут получить процессорное время. Далее она выбирает один из таких объектов и загружает в регистры процессора значение его контекста. Эта операция называется переключением контекста (context switching). Поток выполняет код и манипулирует данными в адресном пространстве своего процесса. Примерно через 20 мс Windows сохранит значения регистров процессора в контексте потока и приостановит его выполнение. Далее система просмотрит остальные объекты потоки, подлежащие выполнению, выберет один из них, загрузит его контекст в регистры процессора, и все повторится. Этот цикл операций – выбор потока, загрузка его контекста, выполнение и сохранение контекста – начинается с момента запуска системы и продолжается до ее выключения (см. Рис. 5 Состояния потоков).

Система планирует выполнение только тех потоков, которые могут получить процессорное время. У некоторых объектов-потоков значение счетчика простоев (suspend count) больше 0, это значит, что соответствующие потоки приостановлены и не получают процессорного времени. Кроме приостановленных, существуют и другие потоки, не участвующие в распределении процессорного времени, — они ожидают каких-либо событий.

Поток получает доступ к процессору на 20 мс, после чего планировщик переключает процессор на выполнение другого потока. Но так происходит, только если у всех потоков один приоритет. На самом деле в системе существуют потоки с разными приоритетами, а это меняет порядок распределения процессорного времени.

Каждому потоку присваивается уровень приоритета – от 0 (самый низкий) до 31 (самый высокий). Решая, какому потоку выделить процессорное время, система сначала рассматривает только потоки с приоритетом 31 и подключает их к процессору по принципу карусели. Если поток с приоритетом 31 не исключен из планирования, он получает квант времени, по истечении которого система проверяет, есть ли еще один такой поток. Если есть, то и он получает свой квант процессорного времени.

Пока в системе имеются планируемые потоки с приоритетом 31, ни один поток с более низким приоритетом процессорного времени не получит. Такая ситуация называется голоданием (starvation). Она наблюдается, когда потоки с более высоким приоритетом так интенсивно используют процессор, что остальным ничего не достается.

Кроме того, потоки с более высоким приоритетом вытесняют потоки с более низким приоритетом. Допустим, процессор исполняет поток с приоритетом 5, и тут система обнаруживает, что поток с более высоким приоритетом готов к выполнению. Тогда система остановит поток с более низким приоритетом – даже если не истек отведенный ему квант процессорного времени – подключит к процессору поток с более высоким приоритетом и выдаст ему полный квант времени.

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

  • поток исполняется до тех пор, пока его исполнение не будет прервано или ему не придется ждать освобождения некоторого ресурса;
  • cохраняется контекст потока;
  • загружается контекст другого потока;

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

Переключение процессора с исполнения одного потока на исполнение другого потока называется переключением контекста (context switching). В Windows NT оно осуществляется ядром.

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

Вытесняющая многозадачность (preemptive multitasking) – это разновидность многозадачности, при которой ОС не ждет, пока поток добровольно предоставит процессор другим потокам. Вместо этого ОС прерывает поток, после того как он выполнялся в течение заранее заданного периода времени, так называемого кванта времени (time quantum), или когда готов к выполнению поток с большим приоритетом. Вытеснение предотвращает монополизацию процессора одним потоком и предоставляет другим потокам их долю процессорного времени. Windows NT – это система с вытесняющей многозадачностью. В невытесняющих системах, поток должен был добровольно передавать управление процессором. Плохие программы могут захватить процессор и нарушить работу других приложений или всей системы.



© 2024 beasthackerz.ru - Браузеры. Аудио. Жесткий диск. Программы. Локальная сеть. Windows