Процессоры, ядра и потоки. Топология систем

  • 29.06.2019
  • Tutorial

В этой статье я попытаюсь описать терминологию, используемую для описания систем, способных исполнять несколько программ параллельно, то есть многоядерных, многопроцессорных, многопоточных. Разные виды параллелизма в ЦПУ IA-32 появлялись в разное время и в несколько непоследовательном порядке. Во всём этом довольно легко запутаться, особенно учитывая, что операционные системы заботливо прячут детали от не слишком искушённых прикладных программ.

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

Предупреждение о знаках ®, ™, в статье

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

Процессор

Конечно же, самый древний, чаще всего используемый и неоднозначный термин - это «процессор».

В современном мире процессор - это то (package), что мы покупаем в красивой Retail коробке или не очень красивом OEM-пакетике. Неделимая сущность, вставляемая в разъём (socket) на материнской плате. Даже если никакого разъёма нет и снять его нельзя, то есть если он намертво припаян, это один чип.

Мобильные системы (телефоны, планшеты, ноутбуки) и большинство десктопов имеют один процессор. Рабочие станции и сервера иногда могут похвастаться двумя или больше процессорами на одной материнской плате.

Поддержка нескольких центральных процессоров в одной системе требует многочисленных изменений в её дизайне. Как минимум, необходимо обеспечить их физическое подключение (предусмотреть несколько сокетов на материнской плате), решить вопросы идентификации процессоров (см. далее в этой статье, а также мою заметку), согласования доступов к памяти и доставки прерываний (контроллер прерываний должен уметь маршрутизировать прерывания на несколько процессоров) и, конечно же, поддержки со стороны операционной системы. Я, к сожалению, не смог найти документального упоминания момента создания первой многопроцессорной системы на процессорах Intel, однако Википедия утверждает , что Sequent Computer Systems поставляла их уже в 1987 году, используя процессоры Intel 80386. Широко распространённой поддержка же нескольких чипов в одной системе становится доступной, начиная с Intel® Pentium.

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


К взлёту готов! Intel® Desktop Board D5400XS

Ядро

Исторически многоядерность в Intel IA-32 появилась позже Intel® HyperThreading, однако в логической иерархии она идёт следующей.

Казалось бы, если в системе больше процессоров, то выше её производительность (на задачах, способных задействовать все ресурсы). Однако, если стоимость коммуникаций между ними слишком велика, то весь выигрыш от параллелизма убивается длительными задержками на передачу общих данных. Именно это наблюдается в многопроцессорных системах - как физически, так и логически они находятся очень далеко друг от друга. Для эффективной коммуникации в таких условиях приходится придумывать специализированные шины, такие как Intel® QuickPath Interconnect. Энергопотребление, размеры и цена конечного решения, конечно, от всего этого не понижаются. На помощь должна прийти высокая интеграция компонент - схемы, исполняющие части параллельной программы, надо подтащить поближе друг к другу, желательно на один кристалл. Другими словами, в одном процессоре следует организовать несколько ядер , во всём идентичных друг другу, но работающих независимо.

Первые многоядерные процессоры IA-32 от Intel были представлены в 2005 году. С тех пор среднее число ядер в серверных, десктопных, а ныне и мобильных платформах неуклонно растёт.

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


Микроснимок четырёхядерного процессора Intel с кодовым именем Nehalem. Выделены отдельные ядра, общий кэш третьего уровня, а также линки QPI к другим процессорам и общий контроллер памяти.

Гиперпоток

До примерно 2002 года единственный способ получить систему IA-32, способную параллельно исполнять две или более программы, состоял в использовании именно многопроцессорных систем. В Intel® Pentium® 4, а также линейке Xeon с кодовым именем Foster (Netburst) была представлена новая технология - гипертреды или гиперпотоки, - Intel® HyperThreading (далее HT).

Ничто не ново под луной. HT - это частный случай того, что в литературе именуется одновременной многопоточностью (simultaneous multithreading, SMT). В отличие от «настоящих» ядер, являющихся полными и независимыми копиями, в случае HT в одном процессоре дублируется лишь часть внутренних узлов, в первую очередь отвечающих за хранение архитектурного состояния - регистры. Исполнительные же узлы, ответственные за организацию и обработку данных, остаются в единственном числе, и в любой момент времени используются максимум одним из потоков. Как и ядра, гиперпотоки делят между собой кэши, однако начиная с какого уровня - это зависит от конкретной системы.

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

Ограничения потоков
В каких случаях наличие «нечестной» многоядерности в виде HT оправдано? Если один поток приложения не в состоянии загрузить все исполняющие узлы внутри ядра, то их можно «одолжить» другому потоку. Это типично для приложений, имеющих «узкое место» не в вычислениях, а при доступе к данным, то есть часто генерирующих промахи кэша и вынужденных ожидать доставку данных из памяти. В это время ядро без HT будет вынуждено простаивать. Наличие же HT позволяет быстро переключить свободные исполняющие узлы к другому архитектурному состоянию (т.к. оно как раз дублируется) и исполнять его инструкции. Это - частный случай приёма под названием latency hiding, когда одна длительная операция, в течение которой полезные ресурсы простаивают, маскируется параллельным выполнением других задач. Если приложение уже имеет высокую степень утилизации ресурсов ядра, наличие гиперпотоков не позволит получить ускорение - здесь нужны «честные» ядра.

Типичные сценарии работы десктопных и серверных приложений, рассчитанных на машинные архитектуры общего назначения, имеют потенциал к параллелизму, реализуемому с помощью HT. Однако этот потенциал быстро «расходуется». Возможно, по этой причине почти на всех процессорах IA-32 число аппаратных гиперпотоков не превышает двух. На типичных сценариях выигрыш от использования трёх и более гиперпотоков был бы невелик, а вот проигрыш в размере кристалла, его энергопотреблении и стоимости значителен.

Другая ситуация наблюдается на типичных задачах, выполняемых на видеоускорителях. Поэтому для этих архитектур характерно использование техники SMT с бóльшим числом потоков. Так как сопроцессоры Intel® Xeon Phi (представленные в 2010 году) идеологически и генеалогически довольно близки к видеокартам, на них может быть четыре гиперпотока на каждом ядре - уникальная для IA-32 конфигурация.

Логический процессор

Из трёх описанных «уровней» параллелизма (процессоры, ядра, гиперпотоки) в конкретной системе могут отсутствовать некоторые или даже все. На это влияют настройки BIOS (многоядерность и многопоточность отключаются независимо), особенности микроархитектуры (например, HT отсутствовал в Intel® Core™ Duo, но был возвращён с выпуском Nehalem) и события при работе системы (многопроцессорные сервера могут выключать отказавшие процессоры в случае обнаружения неисправностей и продолжать «лететь» на оставшихся). Каким образом этот многоуровневый зоопарк параллелизма виден операционной системе и, в конечном счёте, прикладным приложениям?

Далее для удобства обозначим количества процессоров, ядер и потоков в некоторой системе тройкой (x , y , z ), где x - это число процессоров, y - число ядер в каждом процессоре, а z - число гиперпотоков в каждом ядре. Далее я буду называть эту тройку топологией - устоявшийся термин, мало что имеющий с разделом математики. Произведение p = xyz определяет число сущностей, именуемых логическими процессорами системы. Оно определяет полное число независимых контекстов прикладных процессов в системе с общей памятью, исполняющихся параллельно, которые операционная система вынуждена учитывать. Я говорю «вынуждена», потому что она не может управлять порядком исполнения двух процессов, находящихся на различных логических процессорах. Это относится в том числе к гиперпотокам: хотя они и работают «последовательно» на одном ядре, конкретный порядок диктуется аппаратурой и недоступен для наблюдения или управления программам.

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


Windows Task Manager показывает 8 логических процессоров; но сколько это в процессорах, ядрах и гиперпотоках?


Linux top показывает 4 логических процессора.

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

Программное определение топологии

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

Информация о топологии системы в целом, а также положении каждого логического процессора в IA-32 доступна с помощью инструкции CPUID. С момента появления первых многопроцессорных систем схема идентификации логических процессоров несколько раз расширялась. К настоящему моменту её части содержатся в листах 1, 4 и 11 CPUID. Какой из листов следует смотреть, можно определить из следующей блок-схемы, взятой из статьи :

Я не буду здесь утомлять всеми подробностями отдельных частей этого алгоритма. Если возникнет интерес, то этому можно посвятить следующую часть этой статьи. Отошлю интересующегося читателя к , в которой этот вопрос разбирается максимально подробно. Здесь же я сначала кратко опишу, что такое APIC и как он связан с топологией. Затем рассмотрим работу с листом 0xB (одиннадцать в десятичном счислении), который на настоящий момент является последним словом в «апикостроении».

APIC ID
Local APIC (advanced programmable interrupt controller) - это устройство (ныне входящее в состав процессора), отвечающее за работу с прерываниями, приходящими к конкретному логическому процессору. Свой собственный APIC есть у каждого логического процессора. И каждый из них в системе должен иметь уникальное значение APIC ID. Это число используется контроллерами прерываний для адресации при доставке сообщений, а всеми остальными (например, операционной системой) - для идентификации логических процессоров. Спецификация на этот контроллер прерываний эволюционировала, пройдя от микросхемы Intel 8259 PIC через Dual PIC, APIC и xAPIC к x2APIC .

В настоящий момент ширина числа, хранящегося в APIC ID, достигла полных 32 бит, хотя в прошлом оно было ограничено 16, а ещё раньше - только 8 битами. Нынче остатки старых дней раскиданы по всему CPUID, однако в CPUID.0xB.EDX возвращаются все 32 бита APIC ID. На каждом логическом процессоре, независимо исполняющем инструкцию CPUID, возвращаться будет своё значение.

Выяснение родственных связей
Значение APIC ID само по себе ничего не говорит о топологии. Чтобы узнать, какие два логических процессора находятся внутри одного физического (т.е. являются «братьями» гипертредами), какие два - внутри одного процессора, а какие оказались и вовсе в разных процессорах, надо сравнить их значения APIC ID. В зависимости от степени родства некоторые их биты будут совпадать. Эта информация содержится в подлистьях CPUID.0xB, которые кодируются с помощью операнда в ECX. Каждый из них описывает положение битового поля одного из уровней топологии в EAX (точнее, число бит, которые нужно сдвинуть в APIC ID вправо, чтобы убрать нижние уровни топологии), а также тип этого уровня - гиперпоток, ядро или процессор, - в ECX.

У логических процессоров, находящихся внутри одного ядра, будут совпадать все биты APIC ID, кроме принадлежащих полю SMT. Для логических процессоров, находящихся в одном процессоре, - все биты, кроме полей Core и SMT. Поскольку число подлистов у CPUID.0xB может расти, данная схема позволит поддержать описание топологий и с бóльшим числом уровней, если в будущем возникнет необходимость. Более того, можно будет ввести промежуточные уровни между уже существующими.

Важное следствие из организации данной схемы заключается в том, что в наборе всех APIC ID всех логических процессоров системы могут быть «дыры», т.е. они не будут идти последовательно. Например, во многоядерном процессоре с выключенным HT все APIC ID могут оказаться чётными, так как младший бит, отвечающий за кодирование номера гиперпотока, будет всегда нулевым.

Отмечу, что CPUID.0xB - не единственный источник информации о логических процессорах, доступный операционной системе. Список всех процессоров, доступный ей, вместе с их значениями APIC ID, кодируется в таблице MADT ACPI .

Операционные системы и топология

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

В Linux информация о топологии содержится в псевдофайле /proc/cpuinfo , а также выводе команды dmidecode . В примере ниже я фильтрую содержимое cpuinfo на некоторой четырёхядерной системе без HT, оставляя только записи, относящиеся к топологии:

Скрытый текст

ggg@shadowbox:~$ cat /proc/cpuinfo |grep "processor\|physical\ id\|siblings\|core\|cores\|apicid" processor: 0 physical id: 0 siblings: 4 core id: 0 cpu cores: 2 apicid: 0 initial apicid: 0 processor: 1 physical id: 0 siblings: 4 core id: 0 cpu cores: 2 apicid: 1 initial apicid: 1 processor: 2 physical id: 0 siblings: 4 core id: 1 cpu cores: 2 apicid: 2 initial apicid: 2 processor: 3 physical id: 0 siblings: 4 core id: 1 cpu cores: 2 apicid: 3 initial apicid: 3

В FreeBSD топология сообщается через механизм sysctl в переменной kern.sched.topology_spec в виде XML:

Скрытый текст

user@host:~$ sysctl kern.sched.topology_spec kern.sched.topology_spec: 0, 1, 2, 3, 4, 5, 6, 7 0, 1, 2, 3, 4, 5, 6, 7 0, 1 THREAD groupSMT group 2, 3 THREAD groupSMT group 4, 5 THREAD groupSMT group 6, 7 THREAD groupSMT group

В MS Windows 8 сведения о топологии можно увидеть в диспетчере задач Task Manager.

У многих процессоров Intel есть поддержка технологии под названием Hyper-threading. Данная технология позволяет выполнять сразу 2 потока команд на одном ядре процессора. Таким образом удается более эффективно использовать вычислительную мощность ядра. Но, технология Hyper-threading используется далеко не во всех процессорах Intel. Технология Hyper-threading присутствует в , а также в некоторых новых Pentium и мобильных Core i5.

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

Из-за такого разброса в характеристиках процессоров создается путаница и пользователи часто не могут разобраться, в каких процессорах сколько потоков используется. В данной статье речь пойдет о том, как узнать сколько потоков на вашем процессоре.

Способ № 1. Диспетчер задач.

Самый простой способ узнать сколько потоков , это посмотреть в «Диспетчере задач». Для можно воспользоваться классической комбинацией клавиш CTRL-SHIFT-DELETE. Если у вас операционная система Windows 7 или более новая, то это можно сделать еще и с помощью CTRL-SHIFT-ESC.

После открытия «Диспетчера задач» нужно перейти на вкладку «Быстродействие».

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

Если у вас Windows 8 или Windows 10, то хронология нагрузки на процессор может отображаться одним графиком. В этом случае вам нужно сначала переключиться на отображение хронологии по логическим процессорам (потокам). Для этого нужно кликнуть правой кнопкой мышки и выбрать «Изменить график – Логические процессоры».

После этого в «Диспетчере задач» появятся отдельные графики для каждого потока вашего процессора. Например, на скриншоте внизу отображается 4 графика, следовательно, в используемом процессоре 4 потока.

Кроме этого, в «Диспетчере задач» Windows 8/10 прямым текстом указывается сколько потоков в процессоре. Сразу под графиками есть блок с информацией. Там в строке «Логические процессоры» указано количество потоков процессора.

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

Способ № 2. Программы.

Пожалуй, самой известной программой для получения информации о характеристиках процессора является программа CPU-Z. Данная программа является бесплатной, и вы можете . Если вы хотите узнать сколько потоков в процессоре, то вам нужно запустить CPU-Z на своем компьютере и посмотреть значение «Threads», которое находится в правом нижнем углу программы на вкладке «CPU». На скриншоте внизу видно, что у процессора 4 потока.

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

Среди прочего с ее помощью можно узнать и количество потоков процессора. Для этого нужно перейти в раздел «CPU» и посмотреть значение «Threads».

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

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

Таким образом, производственные способности просто нивелируются. Это явление получило название bottleneck, что в переводе значит «узкое место» или «узкая шея».

Прежде чем говорить о данной проблеме, стоит уточнить само определение этого термина. Сама технология носит название Hyper-threading, в источниках часто встречается аббревиатура HT.

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

Как узнать сколько потоков у процессора

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

Коротко по сути и маленькая предыстория

Технология Hyper-threading дает возможность хранения двух потоков одновременно. Поэтому при использовании операционной системы Windows, процессор на 2 ядра имеет в своем активе 4 потока. Такие вычислители еще часто называют процессорами, поддерживающими Hyper-treading (гипертрейдинг).

Дорогие и высокопроизводительные процессоры содержат ядра и потоки. Многие считают, что это смежные понятия, однако это не до конца верно. Впервые потоки появились еще в те времена, когда на рынке технологий царствовал Pentium 4.

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

Программ, которые могли корректно использовать данное преимущество было не много, если вообще были. Эта разработка находилась на стадии, своего рода, полевых исследований.

Система сама все о себе знает

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

Чтобы узнать подробную информацию в операционной системе Windows существует «Диспетчер задач», который в том числе покажет, сколько ресурсов компьютера используется в данное время.

Этот инструмент удобен, часто бывает полезен и обладает интуитивно понятным интерфейсом. Для того, чтобы открыть это приложение, нужно одновременно зажать клавиши Ctrl+Alt+Delete .

Так это выглядит на Windows 10 . Пользователи Mac OS найдут на своем компьютере утилиту «Принудительное завершение программ», которое легко вызвать при помощи клавиш cmd alt Esc . Она также дает возможность закрыть программу, которая перестала отвечать.
Еще одна популярная операционная система с открытым исходным кодом, Linux, тоже содержит диспетчер задач, только называется он по-другому – «Системный монитор».

Добраться до него помогут 3 простых шага:

  1. Системные утилиты
  2. Системный монитор

или можно воспользоваться командой

gnome-system-monitor .

Функционал «Системного монитора» полностью соответствуют таковым в «Диспетчере задач» Windows и «Принудительному завершению программ» в операционной системе от компании Apple.

Почему так быстрее

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

Количество потоков всегда вдвое больше, нежели количество ядер (при наличии «на борту» технологии HT). 2 ядра равнозначно 4-ем потокам, 4 ядра равнозначно 8-и потокам. Алгоритм просчета не может бить иным. Авторство разработки принадлежит компании Intel, являющейся лидером в производстве процессоров на массовом потребительском рынке.

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

Пошаговое руководство для новичков

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

1. Документация устройства, в которой подробно указаны характеристики.
2. Интернет, где можно ввести модель ноутбука и посмотреть, что находится у него «под капотом».
3. Или же в этом может помочь уже упомянутый ранее «Диспетчер задач», в котором нужно выбрать пункт меню «Производительность».

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

Проанализировав скриншот выше, становится очевидно, что данная электронно-вычислительная машина, то бишь компьютер, содержит 4 ядра и 8 логических процессов (считай – потоков). Когда значения двух параметров одинаковы, это означает, что данный компьютер не поддерживает технологию HT (Hyper-threading).

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

В чем разница между программой и процессом? Программа – это статическая последовательность команд, а процесс (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 – это система с вытесняющей многозадачностью. В невытесняющих системах, поток должен был добровольно передавать управление процессором. Плохие программы могут захватить процессор и нарушить работу других приложений или всей системы.

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

Процессы

Появление у компьютера операционной системы (ОС) позволило перейти от однопрограммного режима работы к многопрограммному (мультипрограммному) режиму работы. Операционную систему часто называют многозадачной, полагая, что она выполняет одновременно несколько задач. То, что для ОС является задачей, с точки зрения 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#.