Архитектура операционной системы и ее классификации. Ядро (kernel) низкоуровневая основа любой операционной системы, выполняемая аппаратурой в особом привилегированном режиме
Скачать 1.2 Mb.
|
Создание потоковСоздание потока инициируется Win32-функцией CreateThread, которая находится в библиотеке Kernel32.dll. При этом создается объект ядра "поток", хранящий статистическую информацию о создаваемом потоке. В адресном пространстве процесса выделяется память под пользовательский стек потока. Затем инициализируется аппаратный контекст потока (ниже имеется описание соответствующей структуры CONTEXT). Вслед за этим создается блок управления потоком вместе с сопутствующими структурами, формируется стек ядра потока и о создании потока уведомляется подсистема Win32. Наконец, вызывающему потоку возвращается описатель создаваемого потока и передается управление, а новому потоку может быть выделено процессорное время Контекст потока, переключение контекстов Особую роль в структурах данных, описывающих потоки, играет контекст потока. Информацию, входящую в состав контекста, необходимо периодически сохранять и восстанавливать в случае возникновения различных событий, например, при переключении потоков. Обычно сохранению и последующему восстановлению подлежат: программный счетчик, регистр состояния и содержимое остальных регистров процессора; указатели на стек ядра и пользовательский стек; указатели на адресное пространство, в котором выполняется поток (каталог таблиц страниц процесса). Эта информация сохраняется в текущем стеке ядра потока. Контекст отражает состояние регистров процессора на момент последнего исполнения потока и хранится в структуре CONTEXT, определенной в заголовочном файле WinNT.h. Элементы этой структуры соответствуют регистрам процессора, например, для процессоров x86 процессоров в ее состав входят Eax, Ebx, Ecx, Edx и т д.. Win32-функция GetThreadContext позволяет получить текущее состояние контекста, а функция SetThreadContext - задать новое содержимое контекста. Перед этой операцией поток рекомендуется приостановить. Помимо перечисленных в системе имеется много полезных функций, реализующих API для управления потоками. Их полный перечень содержится в MSDN. Заключение Поток представляет собой набор исполняющихся команд для текущего момента исполнения. С одним или несколькими потоками ассоциирован набор ресурсов, которые объединены в рамках процесса. Для описания процесса в системе поддерживается связанная совокупность структур, главной из которых является структура EPROCESS. В свою очередь, структура ETHREAD и связанные с ней структуры необходимы для реализации потоков. В лекции проанализированы функции CreateProcess и CreateThread и этапы создания процессов и потоков. Важными характеристиками потока являются его контекст и состояние. Наблюдение за состоянием потоков предлагается осуществить при помощи инструментальных средств системы. Межпроцессный обмен К основным способам межпроцессного обмена традиционно относят каналы и разделяемую память, для организации которых используют разделяемые ресурсы. Анонимные каналы поддерживают потоковую модель, в рамках которой данные представляют собой неструктурированную последовательность байтов. Именованные каналы, поддерживающие как потоковую модель, так и модель, ориентированную на сообщения, обеспечивают обмен данными не только в изолированной вычислительной среде, но и в локальной сети Введение Из курса ОС известно, что для выполнения таких задач, как совместное использование данных, построение интегрированных многофункциональных приложений и т.д., различным процессам (а также различным потокам) необходимо взаимодействовать между собой. Поскольку процессы изначально задумывались как обособленные сущности, для обеспечения корректного взаимодействия процессов требуются специальные средства и действия операционной системы. Известно также, что в основе межпроцессного (Inter Process Communications, IPC) обмена обычно находится разделяемый ресурс (например, канал или сегмент разделяемой памяти), и, следовательно, ОС должна предоставить средства для генерации, именования, установки режима доступа и атрибутов защиты таких ресурсов. Обычно такой ресурс может быть доступен всем процессам, которые знают его имя и имеют необходимые привилегии. Кроме того, организация связи между процессами всегда предполагает установления таких ее характеристик, как: направление связи. Связь бывает однонаправленная (симплексная) и двунаправленная (полудуплексная для поочередной передачи информации и дуплексная с возможностью одновременной передачи данных в разных направлениях); тип адресации. В случае прямой адресации информация посылается непосредственно получателю, например, процессу P-Send (P, message). В случае непрямой или косвенной адресации информация помещается в некоторый промежуточный объект, например, в почтовый ящик; используемая модель передачи данных - потоковая или модель сообщений (см. ниже); объем передаваемой информации и сведения о том, обладает ли канал буфером необходимого размера; синхронность обмена данными. Если отправитель сообщения блокируется до получения этого сообщения адресатом, то обмен считается синхронным, в противном случае - асинхронным. Кроме перечисленных у каждой связи есть еще ряд особенностей. Способы межпроцессного обмена. Традиционно считается, что основными способами межпроцессного обмена являются каналы и разделяемая память (рис. 7.1), которые базируются на соответствующих объектах ядра. Рис. 7.1. Основные способы межпроцессного обмена В случае разделяемой памяти два или более процессов совместно используют сегмент памяти. Общение происходит с помощью обычных операций копирования или перемещения данных в памяти (средствами обычных языков программирования). Каналы предполагают созданные средствами операционной системы линии связи. Двумя основными моделями передачи данных по каналу являются поток ввода-вывода и сообщения. При передаче в рамках потоковой модели данные представляют собой неструктурированную последовательность байтов и никак не интерпретируются системой. В модели сообщений на передаваемые данные накладывается некоторая структура, обычно их разделяют на сообщения заранее оговоренного формата. Ограниченный объем курса не позволяет рассмотреть другие механизмы межпроцессного обмена, реализованные в ОС Windows, например, сокеты, Clipboard или удаленный вызов процедуры (RPC). Исчерпывающая справочная информация на эту тему имеется в MSDN. Понятие о разделяемом ресурсе Межпроцессный обмен базируется на разделяемых ресурсах, к которым имеет доступ некоторое множество процессов. При этом возникают задачи создания, именования и защиты таких ресурсов. Обычно один из процессов создает ресурс, наделяет его атрибутами защиты и именем, по которому данный ресурс может быть доступен остальным процессам (даже в случае завершения работы процесса-создателя). В качестве примера рассмотрим общение через разделяемую память (рис. 7.2). Рис. 7.2. Адресные пространства процессов, взаимодействующих через сегмент разделяемой памяти В ОС Windows сегмент разделяемой памяти создается с помощью Win32-функции CreateFileMapping (см. рис. 7.3). В случае успешного выполнения данной функции создается ресурс - фрагмент памяти, доступный по имени (параметр lpname ), который базируется на соответствующем объекте ядра - "объекте-файле, отображаемом в память" с присущими любому объекту атрибутами. Процессу-создателю возвращается описатель (handle) ресурса. Другие процессы, желающие иметь доступ к ресурсу, также должны получить его описатель. В данном случае это можно сделать с помощью функции OpenFileMapping, указав имя ресурса в качестве одного из параметров. Рис. 7.3. Создание сегмента разделяемой памяти базируется на разделяемом ресурсе, которому соответствует объект ядра Способы создания и характеристики файлов, отображаемых в память, будут рассмотрены в Части III курса "Система управления памятью", а в рамках данной темы ограничимся сведениями об обмене информации по каналам связи. При этом не надо забывать, что при любом способе общения в рамках одной вычислительной системы всегда будет использоваться элемент общей памяти. Другое дело, что в случае каналов эта память может быть выделена не в адресном пространстве процесса, а в адресном пространстве ядра системы, как это показано на рис. 7.4. Рис. 7.4. Обмен через каналы связи осуществляется через буфер в адресном пространстве ядра системы Каналы связи Основной принцип работы канала состоит в буферизации вывода одного процесса и обеспечении возможности чтения содержимого программного канала другим процессом. При этом часто интерфейс программного канала совпадает с интерфейсом обычного файла и реализуется обычными файловыми операциями read и write. Для обмена могут использоваться потоковая модель и модель обмена сообщениями. Механизм генерации канала предполагает получение процессом-создателем (процессом-сервером) двух описателей (handles) для пользования этим каналом. Один из описателей применяется для чтения из канала, другой - для записи в канал. Один из вариантов использования канала - это его использование процессом для взаимодействия с самим собой. Рассмотрим следующее изображение системы, состоящей из процесса и ядра, после создания канала (рис. 7.5): Рис. 7.5. Общение процесса с самим собой через канал связи Из этого рисунка легко увидеть, что даже если процесс посылает данные самому себе, они проходят через ядро. Следовательно, для организации таких каналов, а также их именования, в ядре должны быть реализованы элементы файловой системы. Очевидно, что обмен процесса с самим собой через канал большого смысла не имеет, поэтому обычно через канал взаимодействуют два (или более) процессов. Процесс, создающий канал, принято называть сервером, а другой процесс - клиентом. Для общения с каналом клиент и сервер должны иметь описатели (дескрипторы, handles) для чтения и записи. Процесс-сервер получает описатель при создании канала. Процесс-клиент может получить описатели в результате наследования, в том случае, когда клиент является потомком сервера. Это типично для общения через так называемые анонимные каналы. Другой способ получения - открытие по имени уже существующего именованного канала неродственным процессом, который в результате также становится обладателем необходимых описателей. Если организация доступа к каналу прошла успешно, то схема взаимодействия может выглядеть так, как показано на рис. 7.6. Рис. 7.6. Общение процессов через канал связи Если нужно организовать однонаправленную связь и принято решение о направлении передачи данных, то можно "закрыть" неиспользуемый конец канала. В примере на рис. 7.7 клиент посылает через канал информацию серверу. Рис. 7.7. Передача информации от клиента серверу через канал связи применением переменной-замка используются Interlocked-функции, поддерживающие атомарность некоторой последовательности операций. Взаимоисключение потоков одного процесса легче всего организовать с помощью примитива CriticalSection. Для более сложных сценариев рекомендуется применять объекты ядра, в частности, семафоры, мьютексы и события. Рассмотрена проблема синхронизации в ядре, основным решением которой можно считать установку и освобождение спин-блокировок Введение. Проблема взаимоисключения Взаимосвязанные потоки, которые обмениваются данными или пользуются одними и теми же устройствами ввода-вывода, должны синхронизировать свою работу. Пренебрежение вопросами синхронизации потоков, выполняющихся в режиме мультипрограммирования, может привести к их неправильной работе или даже к краху системы. Проблема синхронизации, которая возникает в подобных случаях, может решаться приостановкой и активизацией потоков, организацией очередей, блокированием и освобождением ресурсов. Предположим, что два потока, фиксирующие какие-либо события, пытаются дать приращение общей переменной Count, счетчику этих событий (рис. 8.1). Рис. 8.1. Два параллельных потока увеличивают значение общей переменной Count Операция Count++ не является атомарной. Код операции Count++ будет преобразован компилятором в машинный код, который выглядит примерно так: (1) MOV EAX, [Count] ; значение из Count помещается в регистр (2) INC EAX ; значение регистра увеличивается на 1 (3) MOV [Count], EAX ; значение из регистра помещается обратно в Count В мультипрограммной системе с разделением времени может наступить неблагоприятная ситуация перемешивания (interleaving'а), когда поток T1 выполняет шаг (1), затем вытесняется потоком T2 , который выполняет шаги (1)-(3), а уже после этого поток T1 заканчивает операцию, выполняя шаги (2)-(3). В этом случае результирующее приращение переменной Count будет равно 1 вместо правильного приращения - 2. Сложность проблемы синхронизации состоит в нерегулярности возникающих ситуаций: в предыдущем примере можно представить и другое, более благоприятное развитие событий. В данном случае все определяется взаимными скоростями потоков и моментами их прерывания. Ситуации, подобные той, когда два или более потоков обрабатывают разделяемые данные и конечный результат зависит от соотношения скоростей процессов, называются гонками (условия состязания, race conditions). Для устранения условий состязания необходимо обеспечить каждому потоку эксклюзивный доступ к разделяемым данным. Такой прием называется взаимоисключением (mutual exclusion). Часть кода потока, выполнение которого может привести к race condition, называется критической секцией (critical section). Например, операции (1)-(3) в примере, приведенном выше, являются критическими секциями обоих потоков. Таким образом, взаимоисключение необходимо обеспечить для критических секций потоков. В общем случае структура процесса, участвующего во взаимодействии, может быть представлена следующим образом [2]: while (some condition) { entry section critical section exit section remainder section } Внешний цикл означает, что нас будут интересовать многочисленные попытки входа в критическую секцию (синхронизация единичных попаданий может быть обеспечена и другими средствами). Наиболее важным с точки зрения синхронизации является пролог ( entry section ), где принимается решение о том, может ли поток быть допущенным в критическую секцию. В эпилоге ( exit section ) обычно открывается шлагбаум для других потоков, а операции, не входящие в критическую секцию, сосредоточены в remainder section. Переменная-замок Одним из возможных не вполне корректных решений проблемы синхронизации является использование переменной-замка. Например, можно сделать условием вхождения в критическую секцию значение 0 некоторой разделяемой переменной lock. Сразу же после проверки это значение меняется на 1 (закрытие замка). При выходе из критической секции замок открывается (значение переменной lock сбрасывается в 0 ). shared int lock = 0; T1 T2 while (some condition) { while(lock); lock = 1; critical section lock = 0; remainder section } К сожалению, предложенное решение не всегда обеспечивает взаимоисключение. Вследствие того, что действие-пролог, состоящее из двух операций while(lock); lock = 1; не является атомарным, существует отличная от нуля вероятность вытеснения потока между этими операциями. При этом управление может перейти ко второму потоку, который, узнав, что переменная lock все еще равна 0, может войти в свою критическую секцию. Таким образом, проблема синхронизации может быть решена за счет обеспечения непрерывности для нескольких операций, среди которых имеются операции опроса текущего значения некоторой переменной и установления для этой переменной нового значения. TSL команды Многие вычислительные архитектуры имеют инструкции, которые могут обеспечить атомарность последовательности операций при входе в критическую секцию. Такие команды называются Test and_Set Lock или TSL командами. Если представить себе такую команду как функцию Синхронизация на основе общих семафоров Мы уже начали рассматривать семафоры Дейкстры как средство синхронизации в обзорной части курса. Здесь мы рассмотрим их более подробно в общем виде. Общий семафор (counting semaphore),по Э. Дейкстре, - это целая переменная S, над которой определены две атомарных семафорных операции wait (S) и signal (S) со следующей семантикой: wait (S): while (S <= 0) do no-op; S--; signal (S): S++; Фактически, если начальное значение общего семафора равно n (> 0), то это число задает количество процессов, которые могут беспрепятственно выполнить над семафором операцию wait. Синхронизация по критическим секциям с помощью общего семафора осуществляется следующим образом: /* общие данные */ semaphore mutex = 1; do { wait (mutex); критическая секция signal (mutex); остальная часть кода } while (1) |