Реализация понятия последовательного процесса в ОС

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

  • идентификатор процесса (так называемый PID — process identificator);
  • тип (или класс) процесса, который определяет для супервизора некоторые пра­вила предоставления ресурсов;
  • приоритет процесса, в соответствии с которым супервизор предоставляет ре­сурсы. В рамках одного класса процессов в первую очередь обслуживаются более приоритетные процессы;
  • переменную состояния, которая определяет, в каком состоянии находится процесс (готов к работе, в состоянии выполнения, ожидание устройства вво­да/вывода и т. д.);
  • защищенную область памяти (или адрес такой зоны), в которой хранятся те­кущие значения регистров процессора, если процесс прерывается, не закон­чив работы. Эта информация называется контекстом задачи;
  • информацию о ресурсах, которыми процесс владеет и/или имеет право поль­зоваться (указатели на открытые файлы, информация о незавершенных опе­рациях ввода/вывода и т. п.);
  • место (или его адрес) для организации общения с другими процессами;
  • параметры времени запуска (момент времени, когда процесс должен активи­зироваться, и периодичность этой процедуры);
  • в случае отсутствия системы управления файлами — адрес задачи на диске в ее исходном состоянии и адрес на диске, куда она выгружается из оператив­ной памяти, если ее вытесняет другая (для диск-резидентных задач, которые постоянно находятся во внешней памяти на системном магнитном диске и за­гружаются в оперативную память только на время выполнения).

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

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

В ОС реального времени чаще всего количество процессов фиксируется и, сле­довательно, целесообразно заранее определять (на этапе генерации или конфи­гурирования ОС) количество дескрипторов.

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

Для аппаратной поддержки работы операционных систем с этими информаци­онными структурами (дескрипторами задач) в процессорах могут быть реали­зованы соответствующие механизмы. Так, например, в микропроцессорах Intel 80x86, начиная с 80286, имеет­ся специальный регистр TR (task register), указывающий местонахождение TSS (сегмента состояния задачи), в котором при переключении с задачи на задачу авто­матически сохраняется содержимое регистров процессора. Как прави­ло, в современных ОС для этих микропроцессоров дескриптор задачи включает в себя TSS. Другими словами, дескриптор задачи больше по размеру, чем TSS, и включает в себя такие традиционные поля, как идентификатор задачи, ее имя, тип, приоритет и т. п.

Процессы и треды

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

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

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

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

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

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

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

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

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

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

Для того чтобы можно было эффективно организовать параллельное выполне­ние рассмотренных сущностей (процессов и тредов), в архитектуру современных процессоров включена возможность работать со специальной информационной структурой, описывающей ту или иную сущность. Для этого уже на уровне архи­тектуры микропроцессора используется понятие «задача» (task). Оно как бы объединяет в себе обычный и «легковесный» процессы. Это понятие и поддер­живаемая для него на уровне аппаратуры информационная структура позволяют в дальнейшем при разработке операционной системы построить соответствую­щие дескрипторы как для процесса, так и для треда. Отличаться эти дескрипто­ры будут прежде всего тем, что дескриптор треда может хранить только контекст приостановленного вычислительного процесса, тогда как дескриптор процесса (process) должен уже содержать поля, описывающие тем или лным способом ре­сурсы, выделенные этому процессу. Другими словами, тот же task state segment (сегмент состояния задачи), подробно рассматриваемый в разделе «Адресация в 32-разрядных микропроцессорах 180x86 при работе в защищенном режиме» главы 3, используется как основа для дескриптора процесса. Каждый тред (в случае использования так называемой «плоской» модели памяти — см. раздел «Поддержка страничного способа организации виртуальной памяти», глава 3 — может быть оформлен в виде самостоятельного сегмента, что приводит к тому, что простая (не много поточная) программа будет иметь всего эдин сегмент кода в виртуальном адресном пространстве.

В завершение можно привести несколько советов по использованию потоков при создании приложений, заимствованных из работы [55].

Наши рекомендации