Эта глава является вводной и, пожалуй, самой главной. Любой предмет имеет свои основные понятия и положения. Не является исключением и дисциплина «Операционные системы». К основным понятиям, без которых практически невозможно по-настоящему изучить эту дисциплину, понять основные принципы организации вычислений, взаимодействия прикладных программ с операционной системой и пользователей с компьютерами, следует, прежде всего, отнести понятия вычислительных процессов и ресурсов, системной программы, супервизора, операционной среды, прерываний. Мы также рассмотрим относительно новые понятия, к которым относятся поток выполнения и задача; они дополняют понятие вычислительного процесса и позволяют более эффективно организовать работу компьютера. Поскольку абсолютное большинство операционных систем обеспечивают возможность параллельного выполнения нескольких программ, мы познакомимся с понятием мультипрограммирования. Завершается глава обзором основных общепринятых классификаций.
Операционные системы относятся к системному программному обеспечению. Как известно, все программное обеспечение разделяется на системное и прикладное. К системному программному обеспечению принято относить такие программы и комплексы программ, которые являются общими, без которых невозможно выполнение или создание других программ. История появления и развития системного программного обеспечения началась с того момента, когда люди осознали, что любая программа требует операций ввода-вывода данных. Это произошло в далекие 50-е годы прошлого столетия. Собственно операционные системы появились чуть позже.
Действительно, если мы не будем иметь возможности изменять исходные данные и получать результаты вычислений, то зачем вообще эти вычисления? Очевидно, что исходные данные могут вводиться различными способами. На практике используются самые разнообразные устройства и методы. Например, мы можем вводить исходные значения с клавиатуры, задавать нужные действия или функции с помощью указателя мыши, считывать записи из файла, снимать оцифрованные значения с датчиков и т. д. Часть исходных данных может быть передана в программу через область памяти, в которую предварительно другая программа занесла свои результаты вычислений. Способов много. Главное — выполнить в программе некоторые действия, связанные с получением исходных данных.
Аналогично, и вывод результатов может быть организован, например, на соответствующие устройства и в форме, удобной для восприятия ее человеком. Либо результаты расчетов будут отправляться программой на какие-нибудь исполнительные устройства, которые управляются компьютером. Наконец, мы можем организовать запись полученных значений на некие устройства хранения данных (с целью их дальнейшей обработки).
Программирование операций ввода-вывода относится к одной из самых трудоемких областей создания программного обеспечения. Здесь речь идет не об использовании операторов типа READ или WRITE в языках высокого уровня. Речь идет о необходимости создать подпрограмму в машинном виде, уже готовую к выполнению на компьютере, а не написанную с помощью некоторой системы программирования (систем программирования тогда еще не было), подпрограмму, вместо обычных вычислений управляющую тем устройством, которое должно участвовать в операциях ввода исходных данных или вывода результатов. При наличии такой подпрограммы программист может обращаться к ней столько раз, сколько операций ввода-вывода с этим устройством ему требуется. Для выполнения этой работы программисту недостаточно хорошо знать архитектуру вычислительного комплекса и уметь создавать программы на языке ассемблера. Он должен отлично знать и интерфейс, с помощью которого устройство подключено к центральной части компьютера, и алгоритм функционирования устройства управления устройства ввода-вывода.
Очевидно, что имело смысл создать набор подпрограмм управления операциями ввода-вывода и использовать его в своих программах, чтобы не заставлять программистов каждый раз заново программировать все эти операции. С этого и началась история системного программного обеспечения. Впоследствии набор подпрограмм ввода-вывода стали организовывать в виде специальной библиотеки ввода-вывода, а затем появились и сами операционные системы. Основной причиной их появления было желание автоматизировать процесс подготовки вычислительного комплекса к выполнению программы.
В 50-е годы взаимодействие пользователей с вычислительным комплексом было совершенно иным, чем нынче. Программист-кодер (от англ, coder — кодировщик) — специально подготовленный специалист, знающий архитектуру компьютера и язык(и) программирования, — по заказу составлял текст программы, часто по уже готовому алгоритму, разработанному программистом-алгоритмистом. Текст этой программы затем отдавался оператору, который набирал его на специальных устройствах и переносил на соответствующие носители. Чаще всего в качестве носителей использовались перфокарты или перфолента. Далее колода с перфокартами (перфолента) передавалась в вычислительный зал, где для вычислений по этой программе требовалось выполнить следующие действия.
Оператор вычислительного комплекса с пульта вводил в рабочие регистры центрального процессора и в оперативную память компьютера ту первоначальную программу, которая позволяла считать в память программу для трансляции исходных кодов и получения машинной (двоичной) программы (проще говоря, транслятор, который тоже хранился на перфокартах или перфоленте).
Транслятор считывал исходную программу, осуществлял лексический разбор исходного текста, и промежуточные результаты процесса трансляции зачастую так же выводили на перфокарты (перфоленту). Трансляция — сложный процесс, часто требующий нескольких проходов. Порой для выполнения очередного прохода приходилось в память компьютера загружать с перфокарт и следующую часть транслятора, и промежуточные результаты трансляции. Ведь результат трансляции выводился также на носители информации, поскольку объем оперативной памяти был небольшим, а задача трансляции — это очень сложная задача.
Оператор загружал в оперативную память компьютера полученные двоичные коды оттранслированной программы и подгружал двоичные коды тех системных подпрограмм, которые реализовывали управление операциями ввода-вывода. После этого готовая программа, расположенная в памяти, могла сама считывать исходные данные и осуществлять необходимые вычисления.
В случае обнаружения ошибок на одном из этих этапов или после анализа полученных результатов весь цикл необходимо было повторить.
Для автоматизации труда программиста (кодера) стали разрабатывать специальные алгоритмические языки высокого уровня, а для автоматизации труда оператора вычислительного комплекса была разработана специальная управляющая программа, загрузив которую в память один раз оператор мог ее далее использовать неоднократно и более не обращаться к процедуре программирования ЭВМ через пульт оператора. Именно эту управляющую программу и стали называть операционной системой. Со временем на нее стали возлагать все больше и больше задач, она стала расти в объеме. Прежде всего разработчики стремились к тому, чтобы операционная система как можно более эффективно распределяла вычислительные ресурсы компьютера, ведь в 60-е годы операционные системы уже позволяли организовать параллельное выполнение нескольких программ. Помимо задач распределения ресурсов появились задачи обеспечения надежности вычислений. К началу 70-х годов диалоговый режим работы с компьютером стал преобладающим, и у операционных систем стремительно начали развиваться интерфейсные возможности. Напомним, что термином интерфейс (interface) обозначают целый комплекс спецификаций, определяющих конкретный способ взаимодействия пользователя с компьютером.
На сегодняшний день можно констатировать, что операционная система (ОС) представляет собой комплекс системных1 управляющих и обрабатывающих программ, которые, с одной стороны, выступают как интерфейс между аппаратурой компьютера и пользователем с его задачами, а с другой стороны, предназначены для наиболее эффективного расходования ресурсов вычислительной системы и организации надежных вычислений. Можно попробовать перечислить основные функции операционных систем.
Прием от пользователя (или от оператора системы) заданий, или команд, сформулированных на соответствующем языке, и их обработка. Задания могут передаваться в виде текстовых директив (команд) оператора или в форме указаний, выполняемых с помощью манипулятора (например, с помощью мыши). Эти команды связаны, прежде всего, с запуском (приостановкой, остановкой) программ, с операциями над файлами (получить перечень файлов в текущем каталоге, создать, переименовать, скопировать, переместить тот или иной файл и др.), хотя имеются и иные команды.
Загрузка в оперативную память подлежащих исполнению программ.
Распределение памяти, а в большинстве современных систем и организация виртуальной памяти.
Запуск программы (передача ей управления, в результате чего процессор исполняет программу).
Идентификация всех программ и данных.
Прием и исполнение различных запросов от выполняющихся приложений. Операционная система умеет выполнять очень большое количество системных функций (сервисов), которые могут быть запрошены из выполняющейся программы. Обращение к этим сервисам осуществляется по соответствующим правилам, которые и определяют интерфейс прикладного программирования (Application Program Interface, API) этой операционной системы.
Обслуживание всех операций ввода-вывода.
Обеспечение работы систем управлений файлами (СУФ) и/или-систем управления базами данных (СУБД), что позволяет резко увеличить эффективность всего программного обеспечения.
Обеспечение режима мультипрограммирования, то есть организация параллельного выполнения двух или более программ на одном процессоре, создающая видимость их одновременного исполнения.
Планирование и диспетчеризация задач в соответствии с заданными стратегией и дисциплинами обслуживания.
Организация механизмов обмена сообщениями и данными между выполняющимися программами.
Для сетевых операционных систем характерной является функция обеспечения взаимодействия связанных между собой компьютеров.
а Защита одной программы от влияния другой, обеспечение сохранности данных, защита самой операционной системы от исполняющихся на компьютере приложений.
Аутентификация и авторизация пользователей (для большинства диалоговых операционных систем). Под аутентификацией понимается процедура проверки имени пользователя и его пароля на соответствие тем значениям, которые хранятся в его учетной записи2. Очевидно, что если входное имя (login3) пользователя и его пароль совпадают, то, скорее всего, это и будет тот самый пользователь. Термин авторизация означает, что в соответствии с учетной записью пользователя, который прошел аутентификацию, ему (и всем запросам, которые будут идти к операционной системе от его имени) назначаются определенные права (привилегии), определяющие, что он может, а что не может делать на компьютере.
Удовлетворение жестким ограничениям на время ответа в режиме реального времени (характерно для операционных систем реального времени).
Обеспечение работы систем программирования, с помощью которых пользователи готовят свои программы.
Предоставление услуг на случай частичного сбоя системы.
Операционная система изолирует аппаратное обеспечение компьютера от прикладных программ пользователей. И пользователь, и его программы взаимодействуют с компьютером через интерфейсы операционной системы. Это можно проиллюстрировать, например, рис. 1.1.
Итак, операционная система выполняет функции управления вычислениями в компьютере, распределяет ресурсы вычислительной системы между различными вычислительными процессами и образует ту программную среду, в которой выполняются прикладные программы пользователей. Такая среда называется операционной. Последнее следует понимать в том плане, что при запуске программы она будет обращаться к операционной системе с соответствующими запросами на выполнение определенных действий, или функций. Эти функции операционная система выполняет, запуская специальные системные программные модули, входящие в ее состав.
Итак, при создании двоичных машинных программ прикладные программисты могут вообще не знать многих деталей управления конкретными ресурсами вычислительной системы, а должны только обращаться к некоторой программной подсистеме с соответствующими вызовами и получать от нее необходимые функции и сервисы. Эта программная подсистема и есть операционная система, а набор ее функций и сервисов, а также правила обращения к ним как раз и образуют то базовое понятие, которое мы называем операционной средой. Таким образом, можно сказать, что термин «операционная среда» означает, прежде всего, соответствующие интерфейсы, необходимые программам и пользователям для обращения к управляющей (супервизорной) части операционной системы с целью получить определенные сервисы.
Системных функций бывает много, они определяют те возможности, которые операционная система предоставляет выполняющимся под ее управлением приложениям. Такого рода системные запросы (вызовы системных операций, или функций) либо явно прописываются в тексте программы программистами, либо подставляются автоматически самой системой программирования на этапе трансляции исходного текста разрабатываемой программы. Каждая операционная система имеет свое множество системных функций; они вызываются соответствующим образом, по принятым в системе правилам. Совокупность системных вызовов и правил, по которым их следует использовать, как раз и определяет уже упомянутый нами интерфейс прикладного программирования (API). Очевидно, что программа, созданная для работы в некоторой операционной системе, скорее всего не будет работать в другой операционной системе, поскольку API у этих операционных систем, как правило, различаются. Стараясь преодолеть это ограничение, разработчики операционных систем стали создавать так называемые программные среды. Программную (системную) среду следует понимать как некоторое системное программное окружение, позволяющее выполнить все системные запросы от прикладной программы. Та системная программная среда, которая непосредственно образуется кодом операционной системы, называется основной, естественной, или нативной (native). Помимо основной операционной среды в операционной системе могут быть организованы (путем эмуляции иной операционной среды) дополнительные программные среды. Если в операционной системе организована работа с различными операционными средами, то в такой системе можно выполнять программы, созданные не только для данной, но и для других операционных систем. Можно сказать, что программы создаются для работы в некоторой заданной операционной среде. Например, можно создать программу для работы в среде DOS. Если такая программа все функции, связанные с операциями ввода-вывода и с запросами памяти, выполняет не сама, а за счет обращения к системным функциям DOS, то она будет (в абсолютном большинстве случаев) успешно выполняться и в MS DOS, и в PC DOS, и в Windows 9x, и в Windows 2000, и в OS/2, и даже в Linux.
Итак, параллельное существование терминов «операционная система» и «операционная среда» вызвано тем, что операционная система (в общем случае) может поддерживать несколько операционных сред. Почти все современные 32-разрядные операционные системы, созданные для персональных компьютеров, поддерживают по нескольку операционных сред. Так, операционная система OS/2 Warp, которая в свое время была одной из лучших в этом отношении, может выполнять следующие программы:
основные программы, созданные с учетом соответствующего «родного» 32-разрядного программного интерфейса этой операционной системы;
16-разрядные программы, созданные для систем OS/2 первого поколения;
16-разрядные приложения, разработанные для выполнения в операционной среде MS DOS или PC DOS;
16-разрядные приложения, созданные для операционной среды Windows 3.x;
саму операционную оболочку Windows 3.x и уже в ней — созданные для нее программы.
А операционная система Windows XP позволяет выполнять помимо основных приложений, созданных с использованием Win32 API, 16-разрядные приложения для Windows 3.x, 16-разрядные DOS-приложения, 16-разрядные приложения для первой версии OS/2.
Операционная среда может включать несколько интерфейсов: пользовательские и программные. Если говорить о пользовательских, то, например, система Linux имеет для пользователя как интерфейсы командной строки (можно использовать различные «оболочки» — shell), наподобие Norton Commander, например Midnight Commander, так и графические интерфейсы, например X-Window с различными менеджерами окон — KDE, Gnome и др. Если же говорить о программных интерфейсах, то в тех же операционных системах с общим названием Linux программы могут обращаться как к операционной системе за соответствующими сервисами и функциями, так и к графической подсистеме (если она используется). С точки зрения архитектуры процессора (и персонального компьютера в целом) двоичная программа, созданная для работы в среде Linux, использует те же команды и форматы данных, что и программа, созданная для работы в среде Windows NT. Однако в первом случае мы имеем обращение к одной операционной среде, а во втором -к другой. И программа, созданная непосредственно для Windows, не будет выполняться в Linux; однако если в операционной системе Linux организовать полноценную операционную среду Windows, то наша Windows-программа может быть выполнена. Завершая этот раздел, можно еще раз сказать, что операционная среда — это то системное программное окружение, в котором могут выполняться программы, созданные по правилам работы этой среды.
Прерывания представляют собой механизм, позволяющий координировать параллельное функционирование отдельных устройств вычислительной системы и реагировать на особые состояния, возникающие при работе процессора, то есть прерывание — это принудительная передача управления от выполняемой программы к системе (а через нее — к соответствующей программе обработки прерывания), происходящая при возникновении определенного события.
Идея прерывания была предложена также очень давно — в середине 50-х годов, — и можно без преувеличения сказать, что она внесла наиболее весомый вклад в развитие вычислительной техники. Основная цель введения прерываний — реализация асинхронного режима функционирования и распараллеливание работы отдельных устройств вычислительного комплекса.
Механизм прерываний реализуется аппаратно-программными средствами. Структуры систем прерывания (в зависимости от аппаратной архитектуры) могут быть самыми разными, но все они имеют одну общую особенность — прерывание непременно влечет за собой изменение порядка выполнения команд процессором.
Механизм обработки прерываний независимо от архитектуры вычислительной системы подразумевает выполнение некоторой последовательности шагов.
Установление факта прерывания (прием сигнала запроса на прерывание) и идентификация прерывания (в операционных системах идентификация прерывания иногда осуществляется повторно, на шаге 4).
Запоминание состояния прерванного процесса вычислений. Состояние процесса выполнения программы определяется, прежде всего, значением счетчика команд (адресом следующей команды, который, например, в i80x86 определяется регистрами CS и IP — указателем команды [1,8, 48]), содержимым регистров процессора, и может включать также спецификацию режима (например, режим пользовательский или привилегированный) и другую информацию.
Управление аппаратно передается на подпрограмму обработки прерывания. В простейшем случае в счетчик команд заносится начальный адрес подпрограммы обработки прерываний, а в соответствующие регистры — информация из слова состояния. В более развитых процессорах, например в 32-разрядных микропроцессорах фирмы Intel (начиная с i80386 и включая последние процессоры Pentium IV) и им подобных, осуществляются достаточно сложная процедура определения начального адреса соответствующей подпрограммы обработки прерывания и не менее сложная процедура инициализации рабочих регистров процессора (подробно эти вопросы рассматриваются в разделе «Система прерываний 32-разрядных микропроцессоров i80x86» главы 4).
Сохранение информации о прерванной программе, которую не удалось спасти на шаге 2 с помощью аппаратуры. В некоторых процессорах предусматривается запоминание довольно большого объема информации о состоянии прерванных вычислений.
Собственно выполнение программы, связанной с обработкой прерывания. Эта работа может быть выполнена той же подпрограммой, на которую было передано управление на шаге 3, но в операционных системах достаточно часто она реализуется путем последующего вызова соответствующей подпрограммы.
Восстановление информации, относящейся к прерванному процессу (этап, обратный шагу 4).
Возврат на прерванную программу.
Шаги 1-3 реализуются аппаратно, шаги 4-7 — программно.
На рис. 1.2 показано, что при возникновении запроса на прерывание естественный ход вычислений нарушается и управление передается на программу обработки возникшего прерывания. При этом средствами аппаратуры сохраняется (как правило, с помощью механизмов стековой памяти) адрес той команды, с которой следует продолжить выполнение прерванной программы. После выполнения программы обработки прерывания управление возвращается на прерванную ранее программу посредством занесения в указатель команд сохраненного адреса команды, которую нужно было бы выполнить, если бы не возникло прерывание. Однако такая схема используется только в самых простых программных средах. В мультипрограммных операционных системах обработка прерываний происходит по более сложным схемам, о чем будет более подробно написано ниже.
Итак, главные функции механизма прерываний — это:
распознавание или классификация прерываний;
передача управления соответствующему обработчику прерываний;
корректное возвращение к прерванной программе.
Переход от прерываемой программы к обработчику и обратно должен выполняться как можно быстрей. Одним из самых простых и быстрых методов является использование таблицы, содержащей перечень всех допустимых для компьютера прерываний и адреса соответствующих обработчиков. Для корректного возвращения к прерванной программе перед передачей управления обработчику прерываний содержимое регистров процессора запоминается либо в памяти с прямым доступом, либо в системном стеке (system stack).
Прерывания, возникающие при работе вычислительной системы, можно разделить на два основных класса: внешние (их иногда называют асинхронными) и внутренние (синхронные).
Внешние прерывания вызываются асинхронными событиями, которые происходят вне прерываемого процесса, например:
прерывания от таймера;
прерывания от внешних устройств (прерывания по вводу-выводу);
прерывания по нарушению питания;
прерывания с пульта оператора вычислительной системы;
прерывания от другого процессора или другой вычислительной системы.
Внутренние прерывания вызываются событиями, которые связаны с работой процессора и являются синхронными с его операциями. Примерами являются следующие запросы на прерывания:
при нарушении адресации (в адресной части выполняемой команды указан запрещенный или несуществующий адрес, обращение к отсутствующему сегменту или странице при организации механизмов виртуальной памяти);
при наличии в поле кода операции незадействованной двоичной комбинации;
при делении на ноль;
вследствие переполнения или исчезновения порядка;
от средств контроля (например, вследствие обнаружения ошибки четности, ошибок в работе различных устройств).
Могут еще существовать прерывания в связи с попыткой выполнить команду, которая сейчас запрещена. Во многих компьютерах часть команд должна выполняться только кодом самой операционной системы, но не прикладными программами. Это делается с целью повышения защищенности выполняемых на компьютере вычислений. Соответственно в аппаратуре предусмотрены различные режимы работы, и пользовательские программы выполняются в режиме, в котором некоторое подмножество команд, называемых привилегированными, не исполняется. К привилегированным командам помимо команд ввода-вывода относятся и команды переключения режима работа центрального процессора, и команды инициализации некоторых системных регистров процессора. При попытке использовать команду, запрещенную в данном режиме, происходит внутреннее прерывание, и управление передается самой операционной системе.
Наконец, существуют собственно программные прерывания. Эти прерывания происходят по соответствующей команде прерывания, то есть по этой команде процессор осуществляет практически те же действия, что и при обычных внутренних прерываниях. Этот механизм был специально введен для того, чтобы переключение на системные программные модули происходило не просто как переход на подпрограмму, а точно таким же образом, как и обычное прерывание. Этим, прежде всего, обеспечивается автоматическое переключение процессора в привилегированный режим с возможностью исполнения любых команд.
Сигналы, вызывающие прерывания, формируются вне процессора или в самом процессоре, они могут возникать одновременно. Выбор одного из них для обработки осуществляется на основе приоритетов, приписанных каждому типу прерывания. Так, со всей очевидностью, прерывания от схем контроля процессора должны обладать наивысшим приоритетом (действительно, если аппаратура работает неправильно, то не имеет смысла продолжать обработку информации). На рис. 1.3 изображен обычный порядок (приоритеты) обработки прерываний в зависимости от типа прерываний. Учет приоритета может быть встроен в технические средства, а также определяться операционной системой, то есть кроме аппаратно реализованных приоритетов прерывания большинство вычислительных машин и комплексов допускают программно-аппаратное управление порядком обработки сигналов прерывания. Второй способ, дополняя первый, позволяет применять различные дисциплины обслуживания прерываний.
Наличие сигнала прерывания не обязательно должно вызывать прерывание исполняющейся программы. Процессор может обладать средствами защиты от прерываний: отключение системы прерываний, маскирование (запрет) отдельных сиг- налов прерывания. Программное управление этими средствами (существуют специальные команды для управления работой системы прерываний) позволяет операционной системе регулировать обработку сигналов прерывания, заставляя процессор обрабатывать их сразу по приходу; откладывать обработку на некоторое время; полностью игнорировать прерывания. Обычно операция прерывания выполняется только после завершения выполнения текущей команды. Поскольку сигналы прерывания возникают в произвольные моменты времени, то на момент прерывания может существовать несколько сигналов прерывания, которые могут быть обработаны только последовательно. Чтобы обработать сигналы прерывания в разумном порядке, им (как уже отмечалось) присваиваются приоритеты. Сигнал с более высоким-приоритетом обрабатывается в первую очередь, обработка остальных сигналов прерывания откладывается.
Программное управление специальными регистрами маски (маскирование сигналов прерывания) позволяет реализовать различные дисциплины обслуживания.
С относительными приоритетами, то есть обслуживание не прерывается даже при наличии запросов с более высокими приоритетами. После окончания обслуживания данного запроса обслуживается запрос с наивысшим приоритетом. Для организации такой дисциплины необходимо в программе обслуживания данного запроса наложить маски на все остальные сигналы прерывания или просто отключить систему прерываний.
С абсолютными приоритетами, то есть всегда обслуживается прерывание с наивысшим приоритетом. Для реализации этого режима необходимо на время обработки прерывания замаскировать все запросы с более низким приоритетом. При этом возможно многоуровневое прерывание, то есть прерывание программ обработки прерываний. Число уровней прерывания в этом режиме изменяется и зависит от приоритета запроса.
По принципу стека, или, как иногда говорят, по дисциплине LCFS (Last Come First Served — последним пришел, первым обслужен), то есть запросы с более низким приоритетом могут прерывать обработку прерывания с более высоким приоритетом. Дли этого необходимо не накладывать маску ни на один из сигналов прерывания и не выключать систему прерываний.
Следует особо отметить, что для правильной реализации последних двух дисциплин нужно обеспечить полное маскирование системы прерываний при выполнении шагов 1-4 и 6-7. Это необходимо для того, чтобы не потерять запрос и правильно его обслужить. Многоуровневое прерывание должно происходить на этапе собственно обработки прерывания, а не на этапе перехода с одного процесса вычислений на другой.
Управление ходом выполнения задач со стороны операционной системы заключается в организации реакций на прерывания, в организации обмена информацией (данными и программами), в предоставлении необходимых ресурсов, в динамике выполнения задачи и в организации сервиса. Причины прерываний определяет операционная система (модуль, который называют супервизором прерываний}, она же и выполняет действия, необходимые при данном прерывании и в данной ситуации. Поэтому в состав любой операционной системы реального времени прежде всего входят программы управления системой прерываний, контроля состояний задач и событий, синхронизации задач, средства распределения памяти и управления ею, а уже потом средства организации данных (с помощью файловых систем и т. д. Следует однако заметить, что современная операционная система реального времени должна вносить в аппаратно-программный комплекс нечто большее, нежели просто обеспечение быстрой реакции на прерывания.
Как мы уже знаем, при появлении запроса на прерывание система прерываний идентифицирует сигнал и, если прерывания разрешены, то управление передается на соответствующую подпрограмму обработки. Из рис. 1.2 видно, что в подпрограмме обработки прерывания имеется две служебные секции. Это — первая секция, в которой осуществляется сохранение контекста прерываемых вычислений, который не смог быть сохранен на шаге 2, и последняя, заключительная секция, в которой, наоборот, осуществляется восстановление контекста. Для того чтобы система прерываний не среагировала повторно на сигнал запроса на прерывание, она обычно автоматически «закрывает» (отключает) прерывания, поэтому необходимо потом в подпрограмме обработки прерываний вновь включать систему прерываний. В соответствии с рассмотренными режимами обработки прерываний (с относительными и абсолютными приоритетами и по правилу LCFS) установка этих режимов осуществляется в конце первой секции подпрограммы обработки. Таким образом, на время выполнения центральной секции (в случае работы в режимах с абсолютными приоритетами и по дисциплине LCFS) прерывания разрешены. На время работы заключительной секции подпрограммы обработки система прерываний вновь должна быть отключена и после восстановления контекста опять включена. Поскольку эти действия необходимо выполнять практически в каждой подпрограмме обработки прерываний, во многих операционных системах первые секции подпрограмм обработки прерываний выделяются в уже упоминавшийся специальный системный программный модуль, называемый супервизором прерываний.
Супервизор прерываний прежде всего сохраняет в дескрипторе текущей задачи рабочие регистры процессора, определяющие контекст прерываемого вычислительного процесса. Далее он определяет ту подпрограмму, которая должна выполнить действия, связанные с обслуживанием настоящего (текущего) запроса на прерывание. Наконец, перед тем, как передать управление на эту подпрограмму, супервизор прерываний устанавливает необходимый режим обработки прерывания. После выполнения подпрограммы обработки прерывания управление вновь передается ядру операционной системы. На этот раз уже на тот модуль, который занимается диспетчеризацией задач (см. раздел «Планирование и диспетчеризация процессов и задач» в главе 2). И уже диспетчер задач, в свою очередь, в соответствии с принятой дисциплиной распределения процессорного времени (между выполняющимися вычислительными процессами) восстановит контекст той задачи, которой будет решено выделить процессор. Рассмотренную нами схему иллюстрирует рис. 1.4.
Как мы видим из рисунка, здесь отсутствует возврат в прерванную ранее программу непосредственно из самой подпрограммы обработки прерывания. Для прямого возврата достаточно адрес возврата сохранить в стеке, что и делает аппаратура процессора. При этом стек легко обеспечивает возможность возврата в случае вложенных прерываний, поскольку он всегда реализует дисциплину LCFS.
Однако если бы контекст вычислительных процессов сохранялся просто в стеке, как это обычно реализуется аппаратурой, а не в специальных структурах данных, называемых дескрипторами, о чем будет подробно изложено чуть позже, то у нас не было бы возможности гибко подходить к выбору той задачи, которой нужно передать процессор после завершения работы подпрограммы обработки прерывания. Естественно, что это только общий принцип. В конкретных процессорах и в конкретных операционных системах могут существовать некоторые отступления от рассмотренной схемы и/или дополнения. Например, в современных процессорах часто имеются специальные аппаратные возможности для сохранения контекста прерываемого вычислительного процесса непосредственно в его дескрипторе, то есть дескриптор процесса (по крайней мере его часть) становится структурой данных, которую поддерживает аппаратура.
Для полного понимания принципов создания и механизмов реализации рассматриваемых далее современных операционных систем необходимо знать архитектуру и, в частности, особенности системы прерывания персональных компьютеров. Этот вопрос более подробно рассмотрен в главе 4, посвященной архитектуре микропроцессоров i80x86.
Понятие последовательного4 вычислительного процесса, или просто процесса, является одним из основных при рассмотрении операционных систем. Как понятие процесс является определенным видом абстракции, и мы будем придерживаться следующего неформального определения, приведенного в [47]. Последовательный процесс, иногда называемый задачей5 (task), — это отдельная программа с ее данными, выполняющаяся на последовательном процессоре. Напомним, что под последовательным мы понимаем такой процессор, в котором текущая команда выполняется после завершения предыдущей. В современных процессорах мы сталкиваемся с ситуациями, когда возможно параллельное выполнение нескольких команд. Это делается для повышения скорости вычислений. В этих процессорах параллелизм достигается двумя основными способами — организацией конвейерного механизма выполнения команды и созданием нескольких конвейеров. Однако в подобных процессорах аппаратными решениями обязательно достигается логическая последовательность в выполнении команд, предусмотренная программой. Необходимость этого объясняется в главе 7, посвященной организации параллельных вычислительных процессов.
Концепция процесса предполагает два аспекта: во-первых, он является носителем данных и, во-вторых, он собственно и выполняет операции, связанные с обработкой этих данных.
В качестве примеров процессов (задач) можно назвать прикладные программы пользователей, утилиты и другие системные обрабатывающие программы. Процессом может быть редактирование какого-либо текста, трансляция исходной программы, ее компоновка, исполнение. Причем трансляция какой-нибудь исходной программы является одним процессом, а трансляция следующей исходной программы — другим процессом, поскольку транслятор как объединение программных модулей здесь выступает как одна и та же программа, но данные, которые он обрабатывает, являются разными.
Концепция процесса преследует цель выработать механизмы распределения и управления ресурсами. Понятие ресурса, так же как и понятие процесса, является, пожалуй, основным при рассмотрении операционных систем. Термин ресурс обычно применяется по отношению к многократно используемым, относительно стабильным и часто недостающим объектам, которые запрашиваются, задействуются и освобождаются в период их активности. Другими словами, ресурсом называется всякий объект, который может распределяться внутри системы.
Ресурсы могут быть разделяемыми, когда несколько процессов используют их одновременно (в один и тот же момент времени) или параллельно (попеременно в течение некоторого интервала времени), а могут быть и неделимыми (рис. 1.5).
При разработке первых систем ресурсами считались процессорное время, память, каналы ввода-вывода и периферийные устройства [22, 53]. Однако очень скоро понятие ресурса стало гораздо более универсальным и общим. Различного рода программные и информационные ресурсы также могут быть определены для системы как объекты, которые могут разделяться и распределяться и доступ к которым необходимо соответствующим образом контролировать. В настоящее время понятие ресурса превратилось в абстрактную структуру с целым рядом атрибутов, характеризующих способы доступа к этой структуре и ее физическое представление в системе. Более того, помимо системных ресурсов, о которых мы сейчас говорили, ресурсами стали называть и такие объекты, как сообщения и синхросигналы, которыми обмениваются задачи.
В первых вычислительных системах любая программа могла выполняться только после полного завершения предыдущей. Поскольку эти первые вычислительные системы были построены в соответствии с принципами, изложенными в известной работе Яноша Джона фон Неймана, все подсистемы и устройства компьютера управлялись исключительно центральным процессором. Центральный процессор осуществлял и выполнение вычислений, и управление операциями ввода-вывода данных. Соответственно, пока осуществлялся обмен данными между оперативной памятью и внешними устройствами, процессор не мог выполнять вычисления.
Введение в состав вычислительной машины специальных контроллеров позволило совместить во времени (распараллелить) операции вывода полученных данных и последующие вычисления на центральном процессоре. Однако все равно процессор продолжал часто и долго простаивать, дожидаясь завершения очередной операции ввода-вывода. Поэтому было предложено организовать так называемый мультипрограммный, или мультизадачный, режим работы вычислительной системы.
Вкратце суть мультипрограммного режима работы заключается в том, что пока одна программа (один вычислительный процесс, как мы теперь говорим) ожидает завершения очередной операции ввода-вывода, другая программа (а точнее, другая задача) может быть поставлена на решение (рис. 1.6). Это позволяет более полно использовать имеющиеся ресурсы (например, центральный процессор начинает меньше простаивать, как это видно из рисунка) и уменьшить общее (суммарное) время, необходимое для решения некоторого множества задач.
На рисунке в качестве примера изображена такая гипотетическая ситуация, при которой благодаря совмещению во времени двух вычислительных процессов общее время их выполнения получается меньше, чем если бы их выполняли по очереди (запуск одного начинался бы только после полного завершения другого). Из этого же рисунка видно, что время выполнения каждого процесса в общем случае больше, чем если бы мы выполняли каждый из них как единственный.
При мультипрограммировании повышается пропускная способность системы, но отдельный процесс никогда не может быть выполнен быстрее, чем если бы он выполнялся в однопрограммном режиме (всякое разделение ресурсов замедляет работу одного из участников за счет дополнительных затрат времени на ожидание освобождения ресурса).
Мультипрограммирование стало применяться все чаще и шире в 60-х годах XX века, когда крупные компании получили, наконец, возможность приобретать в собственность вычислительную технику и использовать ее для решения своих задач. До этого времени вычислительная техника была доступна, прежде всего, для военных целей и решения отдельных задач общегосударственного масштаба. А поскольку стоимость компьютеров в то время была чрезвычайно большой, то компании, вложив свои капиталы в вычислительную технику, захотели за счет продажи машинного времени не только покрыть те расходы, которые сопутствовали ее приобретению и использованию, но и зарабатывать дополнительные деньги, то есть получать прибыль. Машинное время стали активно продавать, сдавая в аренду имеющиеся компьютеры, и потенциальная возможность решать в единицу времени большее количество задач, возможно от разных клиентов, стала выступать основным стимулом в развитии способов организации вычислении и операционных систем.
Задачи пользователей ставились в очередь на решение, и распределение времени центрального процессора и других ресурсов компьютера между несколькими выполняющимися вычислительными процессами позволяло организовать параллельное выполнение сразу нескольких задач. Эти задачи могли относиться и к одному пользователю, и к нескольким. Однако ставил их на решение оператор вычислительной системы.
Приблизительно в то же время, может быть чуть позже, стали активно развиваться всевозможные устройства ввода и вывода данных. Не стояло на месте и системное программное обеспечение. Появилась возможность пользователю самому вводить исходные данные и тут же получать результаты вычислений, причем в удобном для него виде. Упрощение пользовательского интерфейса и развитие интерфейсных функций операционных систем позволило реализовать диалоговый режим работы. Как известно, диалоговый режим предполагает, что пользователь может сам, без посредника, взаимодействовать с компьютером — готовить и запускать свои программы, вводить исходные данные, получать результаты, приостанавливать вычисления и вновь их возобновлять и т. д.
Очевидно, что диалоговый режим работы может быть реализован и без мультипрограммирования. Наглядное тому доказательство — многочисленные дисковые операционные системы, начиная от СР-М и кончая PC-DOS 7.0, которые долгие годы устанавливались на персональные компьютеры и обеспечивали только одно-программный режим. Однако эти однопрограммные диалоговые системы появи- лись гораздо позже мультипрограммных. Как это ни кажется странным, им предшествовали многочисленные и разнообразные операционные системы, позволяющие одновременно работать с компьютером большому количеству пользователей и параллельно решать множество задач. Основная причина тому — стоимость компьютера. Только с удешевлением компьютеров появилась возможность иметь свой персональный компьютер, и первое время считалось, что однопрограммного режима работы вполне достаточно. Главным для персональных компьютеров до сих пор считается удобство работы, причем именно в диалоговом режиме, простота интерфейса и его интуитивная понятность.
Совмещение диалогового режима работы с компьютером и режима мультипрограммирования привело к появлению мулътитерминалъных, или многопользовательских, систем. Организовать параллельное выполнение нескольких задач можно разными способами (более подробно об этом см. в главе 2). Если это осуществляется таким образом, что на каждую задачу поочередно выделяется некий квант времени, после чего процессор передается другой задаче, готовой к продолжению вычислений, то такой режим принято называть режимом разделения времени (time sharing). Системы разделения времени активно развивались в 60-70 годы, и сам термин означал именно мультитерминальную и мультипрограммную систему.
Итак, операционная система может поддерживать мультипрограммирование (многопроцессность). В этом случае она должна стараться эффективно использовать имеющиеся ресурсы путем организации к ним очередей запросов, составляемых тем или иным способом. Это требование достигается поддерживанием в памяти более одного вычислительного процесса, ожидающего процессор, и более одного процесса, готового использовать другие ресурсы, как только последние станут доступными.
Общая схема выделения ресурсов такова. При необходимости использовать какой-либо ресурс (оперативную память, устройство ввода-вывода, массив данных и т. п.) вычислительный процесс (задача) путем обращения к супервизору6 (supervisor) операционной системы посредством специальных вызовов (команд, директив) сообщает о своем требовании. При этом указывается вид ресурса и, если надо, его объем. Например, при запросе оперативной памяти указывается количество адресуемых ячеек, необходимое для дальнейшей работы.
Команда обращения к операционной системе передает ей управление, переводя процессор в привилегированный режим работы (см. раздел «Прерывания»), если такой существует. Большинство компьютеров имеют два (и более) режима работы: привилегированный (режим супервизора) и пользовательский. Кроме того, могут быть режимы для эмуляции какой-нибудь другой ЭВМ или для организации виртуальной машины, защищенной от остальных вычислений, осуществляемых на этом же компьютере, и т. д. Мы уже говорили об этом, затрагивая вопрос организации прерываний.
Ресурс может быть выделен вычислительному процессу (задаче), обратившемуся к операционной системе с соответствующим запросом, если:
ресурс свободен и в системе нет запросов от задач более высокого приоритета к этому же ресурсу;
текущий запрос и ранее выданные запросы допускают совместное использование ресурсов;
ресурс используется задачей низшего приоритета и может быть временно отобран (разделяемый ресурс).
Получив запрос, операционная система либо удовлетворяет его и возвращает управление задаче, выдавшей данный запрос, либо, если ресурс занят, ставит задачу в очередь к ресурсу, переводя ее в состояние ожидания (блокируя). Очередь к ресурсу может быть организована несколькими способами, но чаще всего она реализуется с помощью списковой структуры.
После окончания работы с ресурсом задача опять с помощью специального вызова супервизора (посредством соответствующей команды) сообщает операционной системе об отказе от ресурса, либо операционная система забирает ресурс сама, если управление возвращается супервизору после выполнения какой-либо системной функции. Супервизор операционной системы, получив управление по этому обращению, освобождает ресурс и проверяет, имеется ли очередь к освободившемуся ресурсу. Если очередь есть, то в зависимости от принятой дисциплины обслуживания7 и приоритетов заявок он выводит из состояния ожидания задачу, ждущую ресурс, и переводит ее в состояние готовности к выполнению, после чего либо передает управление ей, либо возвращает управление задаче, только что освободившей ресурс.
При выдаче запроса на ресурс задача может указать, хочет ли она владеть ресурсом монопольно или допускает совместное использование с другими задачами. Например, с файлом можно работать монопольно, а можно и совместно с другими задачами.
Если в системе имеется некоторая совокупность ресурсов, то управлять их использованием можно на основе некоторой стратегии. Стратегия подразумевает четкую формулировку целей, следуя которым можно добиться эффективного распределения ресурсов.
При организации управления ресурсами всегда требуется принять решение о том, что в данной ситуации выгоднее: быстро обслуживать отдельные наиболее важные запросы, предоставлять всем процессам равные возможности или обслуживать максимально возможное количество процессов и наиболее полно использовать ресурсы [46].
Необходимо отличать системные управляющие вычислительные процессы, представляющие работу супервизора операционной системы и занимающиеся распределением и управлением ресурсов, от всех других процессов: задач пользователей и системных обрабатывающих процессов. Последние, хоть и относятся к операционной системе, но не входят в ядро операционной системы и требуют общих ресурсов для своей работы, которые получают от супервизора. Для системных управляющих процессов, в отличие от обрабатывающих, в большинстве операционных систем ресурсы распределяются изначально и однозначно. Эти вычислительные процессы сами управляют ресурсами системы, в борьбе за которые конкурируют все остальные процессы. Поэтому исполнение системных управляющих программ не принято называть процессами, и термин «задача» следует употреблять только по отношению к процессам пользователей и к системным обрабатывающим процессам. Однако это справедливо не для всех операционных систем. Например, в так называемых «микроядерных» операционных системах (см. главу 9) большинство управляющих программных модулей самой операционной системы и даже драйверы имеют статус высокоприоритетных процессов, для выполнения которых необходимо выделить соответствующие ресурсы. В качестве примера можно привести хорошо известную операционную систему реального времени QNX фирмы Quantum Software Systems. Аналогично и в UNIX-системах, которые хоть и не относятся к микроядерным, выполнение системных программных модулей тоже имеет статус системных процессов, получающих ресурсы для своего исполнения.
Очевидно, что если некий вычислительный процесс (назовем его первым) в данный конкретный момент времени не исполняется, поскольку процессор занят исполнением какого-то другого процесса, то операционная система должна знать, что вычисления в первом процессе приостановлены. Информация об этом заносится в специальную информационную структуру, сопровождающую каждый вычислительный процесс. Таких приостановленных процессов может быть несколько. Они могут образовывать очередь задач, которые возобновят свои вычисления, как только им будет предоставлен процессор. Некоторые процессы, при своем выполнении требующие ввода или вывода данных, на время выполнения этих запросов могут освобождать процессор. Такие события тоже должны для операционной системы помечаться соответствующим образом. Говорят, что процесс может находиться в одном из нескольких состояний. Информация о состоянии процесса содержится в упомянутой выше информационной структуре, доступной супервизору.
Если обобщать и рассматривать не только традиционные системы общего назначения и привычные всем нам современные мультизадачные операционные системы для персональных компьютеров, но и операционные системы реального времени, то можно сказать, что процесс может находиться в активном и пассивном (не активном) состоянии. В активном состоянии процесс может конкурировать за ресурсы вычислительной системы, а в пассивном состоянии он известен системе, но за ресурсы не конкурирует (хотя его существование в системе и сопряжено с предоставлением ему оперативной и/или внешней памяти). В свою очередь, активный процесс может быть в одном из следующих состояний:
выполнения ~ все затребованные процессом ресурсы выделены (в этом состоянии в каждый момент времени может находиться только один процесс, если речь идет об однопроцессорной вычислительной системе);
готовности к выполнению — ресурсы могут быть предоставлены, тогда процесс перейдет в состояние выполнения;
блокирования, или ожидания, — затребованные ресурсы не могут быть предоставлены, или не завершена операция ввода-вывода.
В большинстве операционных систем последнее состояние, в свою очередь, подразделяется на множество состояний ожидания, соответствующих определенному виду ресурса, из-за отсутствия которого процесс переходит в заблокированное состояние.
В обычных операционных системах, как правило, процесс появляется при запуске какой-нибудь программы. Операционная система организует (порождает, или выделяет) для нового процесса уже упомянутую выше информационную структуру — так называемый дескриптор процесса, и процесс (задача) начинает выполняться. Поэтому пассивного состояния в большинстве систем не существует. В операционных системах реального времени (ОСРВ) ситуация иная. Обычно при проектировании системы реального времени состав выполняемых ею программ (задач) известен заранее. Известны и многие их параметры, которые необходимо учитывать при распределении ресурсов (например, объем памяти, приоритет, средняя длительность выполнения, открываемые файлы, используемые устройства и проч.). Поэтому для них заранее заводят дескрипторы задач с тем, чтобы впоследствии не тратить драгоценное время на организацию дескриптора и поиски для него необходимых ресурсов. Таким образом, в ОСРВ многие процессы (задачи) могут находиться в состоянии бездействия, что мы и отобразили на рис. 1.7, отделив это состояние от остальных состояний пунктиром.
За время своего существования процесс может неоднократно совершать переходы из одного состояния в другое, обусловленные обращениями к операционной системе с запросами ресурсов и выполнения системных функций, которые предоставляет операционная система, взаимодействием с другими процессами, появле- нием сигналов прерывания от таймера, каналов и устройств ввода-вывода, других устройств. Возможные переходы процесса из одного состояния в другое отображены на рисунке в виде графа состояний. Рассмотрим эти переходы из одного состояния в другое более подробно.
Процесс из состояния бездействия может перейти в состояние готовности в следующих случаях.
По команде оператора (пользователя). Имеет место в тех диалоговых операционных системах, где программа может иметь статус задачи, даже оставаясь пассивной, а не просто быть исполняемым файлом и получать статус задачи только на время исполнения (как это имеет место в большинстве современных операционных систем, в том числе и для персональных компьютеров).
При выборе из очереди планировщиком (характерно для операционных систем, работающих в пакетном режиме).
При вызове из другой задачи (посредством обращения к супервизору один процесс может создать, инициировать, приостановить, остановить, уничтожить другой процесс).
По прерыванию от внешнего инициативного устройства8 (сигнал о свершении некоторого события может запускать соответствующую задачу).
При наступлении запланированного времени запуска программы.
Последние два способа запуска задачи, при которых процесс из состояния бездействия переходит в состояние готовности, наиболее характерны для операционных систем реального времени.
Процесс, который может исполняться, как только ему будет предоставлен процессор (а для диск-резидентных задач в некоторых системах и оперативная память), находится в состоянии готовности. Считается, что такому процессу уже выделены все необходимые ресурсы за исключением процессора.
Из состояния выполнения процесс может выйти по одной из следующих причин.
Процесс завершается, при этом он посредством обращения к супервизору передает управление операционной системе и сообщает о своем завершении. В результате этих действий супервизор либо переводит его в список бездействующих процессов (процесс переходит в пассивное состояние), либо уничтожает. Уничтожается, естественно, не сама программа, а именно задача, которая соответствовала исполнению этой программы. В состояние бездействия процесс может быть переведен принудительно: по команде оператора или путем обращения к супервизору операционной системы из другой задачи с требованием остановить данный процесс. Само собой, что действие по команде оператора реализуется системным процессом, который «транслирует» эту команду в запрос к супервизору с требованием перевести указанный процесс в состояние бездействия.
Процесс переводится супервизором операционной системы в состояние готовности к исполнению в связи с появлением более приоритетной задачи или в связи с окончанием выделенного ему кванта времени.
Процесс блокируется (переводится в состояние ожидания) либо вследствие" запроса операции ввода-вывода (которая должна быть выполнена прежде, чем он сможет продолжить исполнение), либо в силу невозможности предоставить ему ресурс, запрошенный в настоящий момент (причиной перевода в состояние ожидания может быть отсутствие сегмента или страницы в случае организации механизмов виртуальной памяти — см. раздел «Сегментная, страничная и сегментно-страничная организация памяти» в главе 3), либо по команде оператора на приостанов задачи, либо по требованию через супервизор от другой задачи.
При наступлении соответствующего события (завершилась операция ввода-вывода, освободился затребованный ресурс, в оперативную память загружена необходимая страница виртуальной памяти и т. д.) процесс деблокируется и переводится в состояние готовности к исполнению.
Таким образом, движущей силой, меняющей состояния процессов, являются события. Одним из основных видов событий являются уже рассмотренные нами прерывания.
Для того чтобы операционная система могла управлять процессами, она должна располагать всей необходимой для этого информацией. С этой целью на каждый процесс заводится специальная информационная структура, называемая дескриптором процесса (описателем задачи, блоком управления задачей). В общем случае дескриптор процесса, как правило, содержит следующую информацию:
идентификатор процесса (Process Identifier, PID);
тип (или класс) процесса, который определяет для супервизора некоторые правила предоставления ресурсов;
приоритет процесса, в соответствии с которым супервизор предоставляет ресурсы (в рамках одного класса процессов в первую очередь обслуживаются более приоритетные процессы);
переменную состояния, которая определяет, в каком состоянии находится процесс (готов к работе, выполняется, ожидает устройства ввода-вывода и т. д.);
контекст задачи, то есть защищенную область памяти (или адрес такой области), в которой хранятся текущие значения регистров процессора, когда процесс прерывается, не закончив работы;
информацию о ресурсах, которыми процесс владеет и/или имеет право пользоваться (указатели на открытые файлы, информация о незавершенных операциях ввода-вывода и др.);
место (или его адрес) для организации общения с другими процессами;
параметры времени запуска (момент времени, когда процесс должен активизироваться, и периодичность этой процедуры);
в случае отсутствия системы управления файлами адрес задачи на диске в ее исходном состоянии и адрес на диске, куда она выгружается из оперативной памяти, если ее вытесняет другая задача (последнее справедливо для диск-резидентных задач, которые постоянно находятся во внешней памяти на системном магнитном диске и загружаются в оперативную память только на время выполнения).
Описатели задач, как правило, постоянно располагаются в оперативной памяти с целью ускорить работу супервизора, который организует их в списки (очереди) и отображает изменение состояния процесса перемещением соответствующего описателя из одного списка в другой. Для каждого состояния (за исключением состояния выполнения для однопроцессорной системы) операционная система ведет соответствующий список задач, находящихся в этом состоянии. Однако для состояния ожидания обычно имеется не один список, а столько, сколько различных видов ресурсов могут вызывать состояние ожидания. Например, состояний ожидания завершения операции ввода-вывода может быть столько, сколько устройств ввода-вывода имеется в системе.
В некоторых операционных системах количество описателей определяется жестко и заранее (на этапе генерации варианта операционной системы или в конфигурационном файле, который используется при загрузке ОС), в других по мере необходимости система может выделять участки памяти под новые описатели. Например, в уже мало кому известной системе OS/2, которая несколько лет тому назад многими специалистами считалась одной из лучших ОС для персональных компьютеров, максимально возможное количество описателей задач указывается в конфигурационном файле CONFIG.SYS. Например, строка THREADS=1024 в файле CONFIG.SYS означает, что всего в системе может параллельно существовать и выполняться до 1024 задач, включая вычислительные процессы и их потоки.
В ныне широко распространенных системах Windows NT/2000/XP количество описателей нигде в явном виде не задается. Это переменная величина, и она определяется самой операционной системой. Однако посмотреть на текущее количество таких описателей можно. Если, работая в Windows NT/2000/XP, нажать одновременно комбинацию клавиш Ctrl+Shift+Esc, появится окно Диспетчера задач. На вкладке Быстродействие этой программы мы увидим поле с названием Всего дескрипторов и соответствующее число. Тут же указывается количество дескрипторов для управления потоками (задачами) и число полноценных вычислительных процессов. Более подробно о процессах и потоках см. далее.
В операционных системах реального времени чаще всего количество процессов фиксируется, и, следовательно, целесообразно заранее определять (на этапе генерации или конфигурирования ОС) количество дескрипторов. Для использования таких операционных систем в качестве систем общего назначения (что нынче уже нехарактерно)9 обычно количество дескрипторов бралось с некоторым запасом, и появление новой задачи связывалось с заполнением этой информационной структуры. Поскольку дескрипторы процессов постоянно располагаются в оперативной памяти (с целью ускорить работу диспетчера), то их количество не должно быть очень большим.
Для более эффективной обработки данных в системах реального времени целесообразно иметь постоянные задачи, полностью или частично всегда существующие в системе независимо от того, поступило на них требование или нет. Каждая постоянная задача обладает некоторой собственной областью оперативной памяти (ОЗУ-резидентная задача, или просто резидентная задача) независимо от того, выполняется задача в данный момент или нет. Эта область, в частности, может использоваться для хранения данных, полученных задачей ранее. Данные могут храниться в ней и тогда, когда задача находится в состоянии ожидания или даже в состоянии бездействия.
Для аппаратной поддержки работы операционных систем с этими информационными структурами (дескрипторами задач) в процессорах могут быть реализованы соответствующие механизмы. Так, например, в микропроцессорах Intel 80x86 (см. главу 4) имеется специальный регистр TR (Task Register), указывающий местонахождение специальной информационной структуры — сегмента состояния задачи (Task State Segment, TSS), в котором при переключении с задачи на задачу автоматически сохраняется содержимое регистров процессора [1,8, 48].
Поскольку между терминами «процесс» и «задача» со временем появилось существенное различие, мы сейчас подробно рассмотрим этот вопрос.
Хотя понятия мультипрограммного и мультизадачного режимов работы достаточно близки, это все-таки не одно и то же. К сожалению, здесь до сих пор имеется некоторая путаница. Основные причины тому — не только то, что терминология еще не устоялась и что многие фирмы-разработчики по-разному предпочитали называть одни и те же сущности, но и сложность, неоднозначность ситуации.
Мультипрограммный режим предполагает, что операционная система организует параллельное выполнение нескольких вычислительных процессов на одном компьютере. И каждый вычислительный процесс может, в принципе, никак не зависеть от другого вычислительного процесса. Разве что они могут задержать выполнение друг друга из-за необходимости поочередно разделять ресурсы или сильно задерживать выполнение друг друга при владении неразделяемым ресурсом. У них может не быть ни общих файлов, ни общих переменных. Они вообще могут принадлежать разным пользователям. Просто эти процессы, с позиций внешнего наблюдателя, выполняются на одном и том же компьютере в одно и то же время. Хотя могут выполняться и в разное время, и на разных компьютерах. Главное — это то, что мультипрограммный режим обеспечивает для этих процессов их независимость. Каждому процессу операционная система выделяет затребованные ресурсы, он выполняется как бы на отдельной виртуальной машине. Средства защиты системы должны обеспечить невмешательство одного вычислительного процесса в другой вычислительный процесс. И если такую защиту обеспечить невозможно, то система не может считаться надежной. Немало методов и конкретных способов было придумано разработчиками для обеспечения надежных вычислений и предотвращения возможности намеренно или по ошибке повлиять на результаты вычислений в другом процессе.
Однако существует и другая потребность: не разделить вычислительные процессы друг от друга, а наоборот совместить их, обеспечить возможность тесного взаимодействия между осуществляемыми вычислениями. Например, результаты вычислений одного вычислительного процесса могут требоваться для начала или продолжения работы другого. Существует огромное количество ситуаций, когда необходимо обеспечить активное взаимодействие между выполняющимися вычислительными процессами. Если нет возможности получить доступ к переменным другого процесса, ибо операционная система построена надежно и защищает адресные пространства одного вычислительного процесса от вмешательства другого вычислительного процесса, то возникают очень серьезные препятствия на пути передачи каких бы то ни было данных между процессами.
Термин мультизадачный режим работы стали применять как раз для тех случаев, когда необходимо обеспечить взаимодействие между вычислениями. Мультизадачный режим означает, что операционная система позволяет организовать параллельное выполнение вычислений, и имеются специальные механизмы для передачи данных, синхросигналов, каких-либо сообщений между этими взаимодействующими вычислениями. Это можно сделать за счет того, что такие вычисления не должны системой изолироваться друг от друга. Операционная система не должна для них в обязательном порядке задействовать все механизмы защиты вычислений от невмешательства друг в друга. При мультизадачном режиме разработчик программы должен позаботиться о разделении ресурсов между его задачами. Операционная система будет всего лишь разделять процессорное время между задачами.
Понятие процесса было введено для реализации идей мультипрограммирования. Термин задача тоже, к сожалению, в большинстве случаев применялся для того же. В свое время различали термины «мультизадачное^» и «мультипрограммирование», но потом они стали заменять друг друга, и это вносило немалую путаницу. Таким образом, для реализации мультизадачности в ее исходном толковании необходимо было ввести соответствующую сущность. Такой сущностью стали легковесные (thin) процессы, или, как их теперь преимущественно называют, потоки выполнения10, нити, или треды (threads).
Когда говорят о процессах (process), то тем самым хотят отметить, что операционная система поддерживает их обособленность: у каждого процесса имеется свое виртуальное адресное пространство, каждому процессу назначаются свои ресурсы — файлы, окна, семафоры и т. д. Такая обособленность нужна для того, чтобы защитить один процесс от другого, поскольку они, совместно используя все ресурсы вычислительной системы, конкурируют друг с другом за доступ к ресурсам. В общем случае процессы просто никак не связаны между собой и могут принадлежать даже разным пользователям, разделяющим одну вычислительную систему. Другими словами, в случае процессов операционная система считает их совершенно несвязанными и независимыми. При этом именно операционная система берет на себя роль арбитра в спорах конкурирующих процессов за ресурсы. Она же и обеспечивает защиту выполняющихся вычислений.
Однако желательно иметь еще и возможность задействовать внутренний параллелизм, который может быть в самих процессах. Такой внутренний параллелизм встречается достаточно часто и позволяет ускорить вычисления. Например, некоторые операции, выполняемые приложением, могут требовать для своего исполнения достаточно длительное использование центрального процессора. В этом случае при интерактивной работе с приложением пользователь вынужден долго ожидать завершения заказанной операции и не может управлять приложением до тех пор, пока операция не выполнится до самого конца. Такие ситуации встречаются достаточно часто, например, при работе с графическими редакторами при обработке больших изображений с высокой степенью детализации. Если же программные модули, исполняющие такие длительные операции, оформлять в виде самостоятельных «подпроцессов» (легковесных процессов, потоков выполнения, или задач), которые могут выполняться параллельно с другими подпроцессами (потоками, задачами), то у пользователя появляется возможность параллельно выполнять несколько операций в рамках одного приложения (процесса). Легковесными эти процессы называют потому, что операционная система не должна для них организовывать полноценную виртуальную машину, то есть эти задачи, прежде всего, не имеют своих собственных ресурсов, а развиваются в том же виртуальном адресном пространстве, могут пользоваться теми же файлами, виртуальными устройствами и иными ресурсами, выделенными ОС данному процессу. Единственное, что они имеют свое — это процессорный ресурс. В однопроцессорной системе потоки выполнения (задачи) разделяют между собой процессорное время так же, как это делают обычные процессы, а в мультипроцессорной системе они могут выполняться одновременно, если не встречают конкуренции из-за обращения к иным ресурсам.
Главное, что обеспечивает многопоточность — это возможность параллельно выполнять несколько видов операций в одной прикладной программе. Параллельные вычисления (а следовательно, и более эффективное использование ресурсов центрального процессора, и меньшее суммарное время выполнения задач) гораздо удобнее реализовать не на уровне процессов, но на уровне задач (потоков, тредов). И программа, разработанная с использованием механизма потоков, представляемая как некоторое множество задач в рамках одного процесса, может быть выполнена быстрее за счет параллельного функционирования ее отдельных частей. Особенно это выгодно при наличии нескольких процессоров, ибо каждая задача может выполняться на отдельном процессоре. Например, если электронная таблица, текстовый процессор или графический редактор были разработаны с учетом возможностей многопоточной обработки, то пользователь может запросить пересчет своего рабочего листа, слияние нескольких документов или преобразование изображения и одновременно продолжать заполнять таблицу, открывать для редактирования следующий документ, изменять другое изображение. Особенно эффективно можно использовать многопоточность для выполнения распределенных приложений. Например, многопоточный сервер может параллельно выполнять запросы сразу нескольких клиентов. Как известно, операционная система OS/2 была одной из первых систем, используемых в персональных компьютерах, которая поддерживала многопоточность. В середине 90-х годов для этой операционной системы было создано большое количество приложений, в которых наличие механизмов многопоточной обработки реально приводило к существенному повышению скорости вычислений. Для систем Windows, с которыми мы все имеем дело, ярко выраженной многопоточностью обладают такие продукты, как SQL Server, Oracle. И хотя те же Word, Excel, Internet Explorer также при своей работе образуют потоки, явного параллелизма в этих программах почти не поддерживается. Поэтому при увеличении числа процессоров в компьютере такие программы не начинают выполняться быстрее.
Итак, сущность «поток выполнения» была введена для того, чтобы именно с помощью этих единиц распределять процессорное время между возможными работами. Сущность «процесс» предполагает, что при диспетчеризации нужно учитывать все ресурсы, закрепленные за ним. При манипулировании задачами-потоками можно менять только контекст задачи, если мы переключаемся с одной задачи на другую в рамках одного процесса. Все остальные вычислительные ресурсы при этом не затрагиваются. Каждый процесс всегда состоит, по крайней мере, из одного потока выполнения, и только если имеется внутренний параллелизм, программист может «расщепить» один поток на несколько параллельных. Потребность в потоках возникла еще в однопроцессорных вычислительных системах, поскольку они позволяли организовать вычисления более эффективно. Для использования достоинств многопроцессорных систем с общей памятью потоки уже просто необходимы, так как позволяют не только реально ускорить выполнение тех задач, которые допускают их естественное распараллеливание, но и загрузить процессорные элементы работой, с тем чтобы они не простаивали. Заметим, однако, что желательно, чтобы можно было свести к минимуму взаимодействие потоков между собой, ибо ускорение от одновременного выполнения параллельных потоков может быть сведено к минимуму из-за задержек синхронизации и обмена данными.
Каждый поток выполняется строго последовательно и имеет свой собственный программный счетчик и стек. Потоки, как и процессы, могут порождать потоки-потомки, поскольку любой процесс состоит по крайней мере из одного потока. Подобно традиционным процессам (то есть процессам, состоящим из одного потока), каждый поток может находиться в одном из активных состояний. Пока один поток заблокирован (или просто находится в очереди готовых к исполнению задач), другой поток того же процесса может выполняться. Потоки разделяют процессорное время так же, как это делают обычные процессы, в соответствии с различными вариантами диспетчеризации.
Как уже упоминалось, иногда потоки выполнения называют легковесными процессами. Как мы уже знаем, все потоки имеют одно и то же виртуальное адресное пространство своего процесса. Это означает, что они разделяют одни и те же глобальные переменные. Поскольку каждый поток может иметь доступ к каждому виртуальному адресу, один поток может использовать стек другого потока. Между потоками нет полной защиты, потому что, во-первых, это невозможно, а во-вторых, не нужно. Все потоки одного процесса всегда решают общую задачу одного пользователя, и механизм потоков используется здесь для более быстрого решения задачи путем ее распараллеливания. При этом программисту очень важно получить в свое распоряжение удобные средства организации взаимодействия частей одной программы. Повторим, что кроме разделения адресного пространства, все потоки разделяют также набор открытых файлов, устройства, выделенные процессу, имеют одни и те же наборы сигналов, семафоры и т. п. А что у потоков является их собственным? Собственными являются программный счетчик, стек, рабочие регистры процессора, потоки-потомки, состояние.
Вследствие того, что потоки, относящиеся к одному процессу, выполняются в одном и том же виртуальном адресном пространстве, между ними легко организовать тесное взаимодействие, в отличие от процессов, для которых нужны специальные механизмы обмена сообщениями и данными. Более того, программист, создающий многопоточное приложение, может заранее продумать работу множества потоков процесса таким образом, чтобы они могли взаимодействовать наиболее выгодным способом, а не конкурировать за ресурсы тогда, когда этого можно избежать.
Для того чтобы можно было эффективно организовать параллельное выполнение рассмотренных сущностей (процессов и потоков), в архитектуру современных процессоров включены средства для работы со специальной информационной структурой, описывающей ту или иную сущность. Для этого уже на уровне архитектуры микропроцессора используется понятие задача (task). Оно как бы объединяет в себе и обычный процесс, и поток выполнения (тред). Это понятие и поддерживаемая для него на уровне аппаратуры информационная структура позволяют в дальнейшем при разработке операционной системы строить соответствующие дескрипторы как для задач, так и для процессов. И отличаться эти дескрипторы будут прежде всего тем, что дескриптор задачи может хранить только контекст приостановленного вычислительного процесса, тогда как дескриптор процесса должен содержать поля, описывающие тем или иным способом ресурсы, выделенные этому процессу. Для хранения контекста задачи в микропроцессорах с архитектурой 132 имеется специальный сегмент состояния задачи (Task State Segment, TSS). А для отображения информации о процессе используется уже иная информационная структура, однако она включает в себя TSS. Другими словами, сегмент состояния задачи, подробно рассматриваемый в разделе «Адресация в 32-разрядных микропроцессорах i80x86 при работе в защищенном режиме» главы 4, используется как основа для дескриптора процесса. Таким образом, дескриптор процесса больше по размеру, чем TSS, и включает в себя такие традиционные поля, как идентификатор процесса, его имя, тип, приоритет и проч.
Каждый поток (в случае использования так называемой «плоской» модели памяти — см. раздел «Сегментная, страничная и сегментно-страничная организация памяти» в главе 3) может быть оформлен в виде самостоятельного сегмента, что приводит к тому, что простая (не многопоточная) программа будет иметь всего один сегмент кода в виртуальном адресном пространстве. Теперь, если вернуться к уже упомянутому файлу CONFIG.SYS, в котором для операционной системы OS/2 указываются наиболее важные параметры, определяющие ее работу, стоит заметить, что в этом файле строка THREADS=1024 указывает на количество не процессов, а именно задач. И под задачей в данном случае понимается как процесс, так и поток этого процесса.
К большому сожалению, практически невозможно использовать термины «задача» и «процесс» с однозначным толкованием, чтобы под задачей обязательно понимать поток, в то время как термин «процесс» означал бы множество потоков. Значение этих терминов по-прежнему сильно зависит от контекста. И это характерно практически для каждой книги, в том числе и для учебной литературы. Грешен этим и автор. Остается надеяться, что со временем все же ситуация изменится, и толкование этих слов будет более четким и строгим.
В завершение можно привести несколько советов по использованию потоков выполнения при создании приложений, заимствованных из [28].
В случае однопроцессорной системы множество параллельных потоков часто не ускоряет работу приложения, поскольку в каждый отдельно взятый промежуток времени возможно выполнение только одного потока. Кроме того, чем больше у вас потоков, тем больше нагрузки на систему, относящиеся к переключению между ними. Мультизадачность из более двух постоянно работающих потоков в вашем проекте не сделает программу быстрее, если каждый из потоков не будет требовать частого ввода-вывода.
Вначале нужно понять, для чего необходим поток. Поток, осуществляющий обработку, может помешать системе быстро реагировать на запросы ввода-вывода. Потоки позволяют программе отзываться на просьбы пользователя и устройств, но при этом (в том числе) сильно загружают процессор. Потоки позволяют компьютеру одновременно обслуживать множество устройств, и созданный вами поток, отвечающий за обработку специфического устройства, как минимум может потребовать столько времени, сколько системе необходимо для обработки запросов от всех устройств.
Потокам можно назначать разные приоритеты для того, чтобы наименее значимые процессы выполнялись в фоновом режиме. Это путь честного разделения ресурсов процессора. Однако необходимо осознать тот факт, что процессор один на всех, а потоков много. Если в вашей программе главная процедура передает нечто для обработки в низкоприоритетный поток, то сама программа становится просто неуправляемой.
Потоки хорошо работают, когда они независимы. Но они начинают работать непродуктивно, когда вынуждены часто синхронизироваться для доступа к общим ресурсам. Взаимные блокировки и критические секции отнюдь не добавляют скорости работы системы, хотя без использования этих механизмов взаимодействующие вычисления организовывать нельзя.
Помните, что память виртуальна. Механизм виртуальной памяти (см. раздел «Память и отображения, виртуальное адресное пространство» в главе 3) следит за тем, какая часть виртуального адресного пространства должна находиться в оперативной памяти, а какая должна быть сброшена в файл подкачки. Потоки усложняют ситуацию, если они обращаются в одно и то же время к разным адресам виртуального адресного пространства приложения. Это значительно увеличивает нагрузку на систему, особенно при небольшом объеме кэшпамяти. Помните, что реально память не всегда «свободна», как это пишут в информационных окошках «О системе». Всегда отождествляйте доступ к памяти с доступом к файлу на диске и создавайте приложение с учетом вышесказанного.
Всякий раз, когда любой из ваших потоков пытается воспользоваться общим ресурсом вычислительного процесса, которому он принадлежит, вы обязаны каким-то образом легализовать и защитить вашу деятельность. Хорошим средством для этого являются критические секции, семафоры и очереди сообщений (см. главу 7). Если вы протестировали ваше приложение и не обнаружили ошибок синхронизации, то это еще не значит, что их там нет. Пользователь может создавать самые непредсказуемые ситуации. Это очень ответственный момент в разработке многопоточных приложений.
Не возлагайте на поток несколько функций. Сложные функциональные отношения затрудняют понимание общей структуры приложения, его алгоритм. Чем проще и однозначнее каждая из рассматриваемых ситуаций, тем больше вероятность, что ошибок удастся избежать.
Рассмотрим кратко основные виды ресурсов вычислительной системы и способы их разделения (см. рис. 1.5). Прежде всего, одним из важнейших ресурсов является сам процессор11, точнее — процессорное время. Процессорное время делится попеременно (параллельно). Имеется множество методов разделения этого ресурса (см. раздел «Планирование и диспетчеризация процессов и задач» в главе 2).
Вторым видом ресурсов вычислительной системы можно считать память. Оперативная память может делиться и одновременно (то есть в памяти одновременно может располагаться несколько задач или, по крайней мере, текущих фрагментов, участвующих в вычислениях), и попеременно (в разные моменты оперативная память может предоставляться для разных вычислительных процессов). Память — очень интересный вид ресурса. Дело в том, что в каждый конкретный момент времени процессор при выполнении вычислений обращается к очень ограниченному числу ячеек оперативной памяти. С этой точки зрения желательно память выделять для возможно большего числа параллельно исполняемых задач. С другой стороны, как правило, чем больше оперативной памяти может быть выделено для конкретного текущего вычислительного процесса, тем лучше будут условия его выполнения. Поэтому проблема эффективного разделения оперативной памяти между параллельно выполняемыми вычислительными процессами является одной из самых актуальных. Достаточно подробно вопросы распределения памяти между параллельно выполняющимися процессами рассмотрены в главе 3.
Внешняя память тоже является ресурсом, который часто необходим для выполнения вычислений. Когда говорят о внешней памяти (например, памяти на магнитных дисках), то собственно память и доступ12 к ней считаются разными видами ресурса. Каждый из этих ресурсов может предоставляться независимо от другого. Но для полноценной работы с внешней памятью необходимо иметь оба этих ресурса. Собственно внешняя память может разделяться и одновременно, а вот доступ к ней всегда разделяется попеременно.
Если говорить о внешних устройствах, то они, как правило, могут разделяться параллельно, если используются механизмы прямого доступа. Если же устройство работает с последовательным доступом, то оно не может считаться разделяемым ресурсом. Простыми и наглядными примерами внешних устройств, которые не могут быть разделяемыми, являются принтер и накопитель на магнитной ленте. Действительно, если допустить, что принтер можно разделять между двумя процессами, которые смогут его использовать (управлять его работой) попеременно, то результаты печати, скорее всего, окажутся негодными — фрагменты выведенного текста могут перемешаться таким образом, что будет не понятно, что есть что. Аналогично и для накопителя на магнитной ленте. Если один процесс начнет что-то читать или писать, а второй при этом запросит перемотку ленты на ее начало, то оба вычислительных процесса не смогут выполнить свои вычисления. Здесь следует заметить, что при работе с устройствами печати мы, тем не менее, явно наблюдаем возможность печатать из разных программ, выполняющихся параллельно. Однако необходимо знать, что это реализуется за счет того, что каждый вычислительный процесс получает свой виртуальный принтер, который он ни с кем не разделяет. А операционная система, получив задания на печать от выполняющихся задач, сама упорядочивает эти задания и передает очередное задание на принтер только после полного завершения предыдущего задания.
Очень важным видом ресурсов являются программные модули. Прежде всего, мы будем рассматривать системные программные модули, поскольку именно они обычно считаются программными ресурсами и поэтому в принципе могут распределяться между выполняющимися процессами.
Как известно, программные модули могут быть однократно используемыми и многократно (или повторно) используемыми. Однократно используемыми называют такие программные модули, которые могут быть правильно выполнены только один раз, то есть в процессе своего выполнения они могут испортить себя: либо повреждается часть кода, либо исходные данные, от которых зависит ход вычислений. Очевидно, что однократно используемые программные модули являются неделимым ресурсом. Более того, их, как правило, вообще не распределяют как ресурс системы. Системные однократно используемые программные модули, как правило, задействуются только на этапе загрузки операционной системы. При этом следует иметь в виду тот очевидный факт, что собственно двоичные файлы, которые обычно хранятся на системном диске и в которых и записаны эти модули, не портятся, а потому могут быть повторно использованы при следующем запуске операционной системы.
Повторно используемые программные модули, в свою очередь, могут быть непривилегированными, привилегированными и реентерабельными. Все они допускают корректное повторное выполнение программного кода при обращении к нему из другой программы.
Привилегированные программные модули работают в так называемом привилегированном режиме, то есть при отключенной системе прерываний (часто говорят, что прерывания закрыты), когда никакие внешние события не могут нарушить естественный порядок вычислений. Как результат, программный модуль выполняется до своего конца, после чего он может быть вновь вызван на исполнение из другой задачи (другого вычислительного процесса). С позиций стороннего наблюдателя по отношению к вычислительным процессам, которые попеременно (причем, возможно, неоднократно) в течение срока своей «жизни» вызывают некоторый привилегированный программный модуль, такой модуль будет выступать как попеременно разделяемый ресурс. Структура привилегированных программных модулей изображена на рис. 1.8. Здесь в первой секции программного модуля выключается система прерываний. Следовательно, при выполнении вычислений в первой секции ничто не может их прервать, и беспокоиться о промежуточных переменных нет необходимости. В последней секции, напротив, система прерываний включается. Даже если тут же возникнет прерывание и другой процесс запросит этот же привилегированный модуль, все равно все вычисления уже выполнены и ничто не сможет их испортить.
Непривилегированные программные модули — это обычные программные модули, которые могут быть прерваны во время своей работы. Следовательно, такие модули в общем случае нельзя считать разделяемыми, потому что если после прерывания выполнения такого модуля, исполняемого в рамках одного вычислительного процесса, запустить его еще раз по требованию другого вычислительного процесса, то промежуточные результаты для прерванных вычислений могут быть потеряны.
В противоположность этому, реентерабельные программные модули13 допускают повторное многократное прерывание своего исполнения и повторный их запуск по обращению из других задач (вычислительных процессов). Для этого реентерабельные программные модули должны быть созданы таким образом, чтобы было обеспечено сохранение промежуточных результатов для прерываемых вычислений и возврат к ним, когда вычислительный процесс возобновляется с прерванной ранее точки. Это может быть реализовано двумя способами: с помощью статических и динамических методов выделения памяти под сохраняемые значения. Основным и наиболее часто используемым является динамический способ выделения памяти для сохранения всех промежуточных результатов вычисления, относящихся к реентерабельному программному модулю (рис. 1.9).
Основная идея построения и работы реентерабельного программного модуля заключается в том, что в первой (головной) своей части путем обращения из системной привилегированной секции осуществляется запрос на получение в системной области памяти блока ячеек, необходимого для размещения всех текущих (промежуточных) данных. При этом на вершину стека помещается указатель на начало области данных и ее объем. Все текущие переменные реентерабельного программного модуля в этом случае располагаются в системной области памяти. Адресация этих переменных осуществляется относительно вершины стека. Поскольку в конце привилегированной секции система прерываний включается, то во время работы центральной (основной) части реентерабельного модуля возможно ее прерывание. Если прерывания не возникает, то в третьей (заключительной) секции осуществляется запрос на освобождение используемого блока системной области памяти. При освобождении этой области памяти модифицируется значение стека. Если же во время работы центральной секции возникает прерывание, и другой вычислительный процесс обращается к тому же самому реентерабельному программному модулю, то для этого нового процесса вновь заказывается новый блок памяти в системной области памяти, и на вершину стека записывается новый указатель. Очевидно, что возможно многократное повторное вхождение в реентерабельный программный модуль до тех пор, пока в области системной памяти, выделяемой специально для реентерабельной обработки, есть свободные области, объема которых достаточно для выделения нового блока.
Что касается статического способа выделения памяти, то здесь речь может идти, например, о том, что заранее для фиксированного числа вычислительных процессов резервируются области памяти, в которых будут располагаться переменные реентерабельных программных модулей: для каждого процесса — своя область памяти. Чаще всего в качестве таких процессов выступают процессы ввода-вывода, и речь идет о реентерабельных драйверах14.
Кроме реентерабельных программных модулей существуют еще повторно входимые (re-entrance). Этим термином называют программные модули, которые тоже допускают свое многократное параллельное использование, но, в отличие от реентерабельных, их нельзя прерывать. Повторно входимые программные модули состоят из привилегированных секций, и повторное обращение к ним возможно только после завершения какой-нибудь из таких секций. После выполнения очередной такой привилегированной секции управление может быть передано супервизору, который может предоставить возможность выполняться другой задаче, а значит, возможно повторное вхождение в рассматриваемый программный модуль. Другими словами, в повторно входимых программных модулях четко предопределены все допустимые (возможные) точки входа. Следует отметить, что повторно входимые программные модули встречаются гораздо чаще реентерабельных (повторно прерываемых).
Наконец, имеются и информационные ресурсы, то есть в качестве ресурсов могут выступать данные. Информационные ресурсы могут существовать как в виде переменных, находящихся в оперативной памяти, так и в виде файлов. Если процессы используют данные только для чтения, то такие информационные ресурсы можно разделять. Если же процессы могут изменять информационные ресурсы, то необходимо специальным образом организовывать работу с такими данными. Это одна из наиболее сложных проблем, достаточно подробно она обсуждается в главах 9 и 10.
Выше мы уже дали определение операционной системы (ОС). Поэтому просто повторим, что основным предназначением ОС является организация эффективных и надежных вычислений, создание различных интерфейсов для взаимодействия с этими вычислениями и с самой вычислительной системой.
Широко известно высказывание, согласно которому любая наука начинается с классификации. Само собой, что вариантов классификации может быть очень много, здесь все будет зависеть от выбранного признака, по которому один объект мы будем отличать от другого. Однако, что касается ОС, здесь уже давно сформировалось относительно небольшое количество классификаций: по назначению, по режиму обработки задач, по способу взаимодействия с системой и, наконец, по способам построения (архитектурным особенностям системы).
Прежде всего, традиционно различают ОС общего и специального назначения. ОС специального назначения, в свою очередь, подразделяются на ОС для носимых микрокомпьютеров и различных встроенных систем, организации и ведения баз данных, решения задач реального времени и т. п. Еще не так давно операционные системы для персональных компьютеров относили к ОС специального назначения. Сегодня современные мультизадачные ОС для персональных компьютеров уже многими относятся к ОС общего назначения, поскольку их можно использовать для самых разнообразных целей — так велики их возможности.
По режиму обработки задач различают ОС, обеспечивающие однопрограммный и мультипрограммный (мультизадачный) режимы. К однопрограммным ОС относится, например, всем известная, хотя нынче уже практически и не используемая MS DOS. Напомним, что под мультипрограммированием понимается способ организации вычислений, когда на однопроцессорной вычислительной системе создается видимость одновременного выполнения нескольких программ. Любая задержка в решении программы (например, для осуществления операций ввода-вывода данных) используется для выполнения других (таких же либо менее важных) программ. Иногда при этом говорят о мультизадачном режиме, причем, вообще говоря, термины «мультипрограммный режим» и «мультизадачный режим» — это не синонимы, хотя и близкие понятия. Основное принципиальное отличие этих терминов заключается в том, что мультипрограммный режим обеспечивает параллельное выполнение нескольких приложений, и при этом программисты, создающие эти программы, не должны заботиться о механизмах организации их параллельной работы (эти функции берет на себя сама ОС; именно она распределяет между выполняющимися приложениями ресурсы вычислительной системы, осуществляет необходимую синхронизацию вычислений и взаимодействие). Мультизадачный режим, наоборот, предполагает, что забота о параллельном выполнении и взаимодействии приложений ложится как раз на прикладных программистов. Хотя в современной технической и тем более научно-популярной литературе об этом различии часто забывают и тем самым вносят некоторую путаницу. Можно, однако, заметить, что современные ОС для персональных компьютеров реализуют и мультипрограммный, и мультизадачный режимы.
Если принимать во внимание способ взаимодействия с компьютером, то можно говорить о диалоговых системах и системах пакетной обработки. Доля последних хоть и не убывает в абсолютном исчислении, но в процентном отношении она существенно сократилась по сравнению с диалоговыми системами.
При организации работы с вычислительной системой в диалоговом режиме можно говорить об однопользовательских (однотерминальных) и мультитерминальных ОС. В мультитерминальных ОС с одной вычислительной системой одновременно могут работать несколько пользователей, каждый со своего терминала. При этом у пользователей возникает иллюзия, что у каждого из них имеется собственная вычислительная система. Очевидно, что для организации мультитерминального доступа к вычислительной системе необходимо обеспечить мультипрограммный режим работы. В качестве одного из примеров мультитерминальных операционных систем для персональных компьютеров можно назвать Linux. Некая имитация мультитерминальных возможностей имеется и в системе Windows XP. В этой операционной системе каждый пользователь после регистрации (входа в систему) получает свою виртуальную машину. Если необходимо временно предоставить компьютер другому пользователю, вычислительные процессы первого можно не завершать, а просто для этого другого пользователя система создает новую виртуальную машину. В результате компьютер будет выполнять задачи и первого, и второго пользователя. Количество параллельно работающих виртуальных машин определяется имеющимися ресурсами.
Основной особенностью операционных систем реального времени (ОСРВ) является обеспечение обработки поступающих заданий в течение заданных интервалов времени, которые нельзя превышать. Поток заданий в общем случае не является планомерным и не может регулироваться оператором (характер следования событий можно предсказать лишь в редких случаях), то есть задания поступают в непредсказуемые моменты времени и без всякой очередности. В то время как в ОС, не предназначенных для решения задач реального времени, имеются некоторые накладные расходы процессорного времени на этапе инициирования задач (в ходе которого ОС распознает все пожелания пользователей относительно решения своих задач, загружает в оперативную память нужную программу и выделяет другие необходимые для ее выполнения ресурсы), в ОСРВ подобные затраты могут отсутствовать, так как набор задач обычно фиксирован, и вся информация о задачах известна еще до поступления запросов. Для подлинной реализации режима реального времени необходима (хотя этого и недостаточно) организация мультипрограммирования. Мультипрограммирование является основным средством повышения производительности вычислительной системы, а для решения задач реального времени производительность становится важнейшим фактором. Лучшие характеристики по производительности для систем реального времени обеспечиваются однотерминальными ОСРВ. Средства организации мультитерминального режима всегда замедляют работу системы в целом, но расширяют функциональные возможности системы. Одной из наиболее известных ОСРВ для персональных компьютеров является ОС QNX.
По основному архитектурному принципу операционные системы разделяются на микроядерные и макроядерные (монолитные). В некоторой степени это разделение тоже условно, однако можно в качестве яркого примера микроядерной ОС привести ОСРВ QNX, тогда как в качестве монолитной можно назвать Windows 95/98 или ОС Linux. Если ядро ОС Windows мы не можем изменить, нам недоступны его исходные коды и у нас нет программы для сборки (компиляции) этого ядра, то в случае с Linux мы можем сами собрать то ядро, которое нам необходимо, включив в него те программные модули и драйверы, которые мы считаем целесообразным включить именно в ядро (ведь к ним можно обращаться и из ядра).
1Системными принято называть такие программы, которые используются всеми остальными программами.
2Если операционная система не поддерживает механизм учетных записей, как это имеет место, например, в семействе операционных систем Windows 9x компании Microsoft, то пароль сверяется по специальному файлу, где он хранится в зашифрованном виде.
3В 70-е годы пользователи за терминалом писали log in, и это означало процедуру регистрации. Были системы, в которых требовалось набрать команду log on, что означало то же самое.
4Слово «последовательный» в большинстве случаев опускается. Считается, что речь идет о вычислениях, осуществляемых на обычных «последовательных» процессорах, которые выполняют команду за командой, а не параллельно несколько команд за один такт.
5В концепции, которая получила наибольшее распространение в 70-е годы, задача — это совокупность связанных между собой и образующих единое целое программных модулей и данных, требующая ресурсов вычислительной системы для своей реализации. В последующие годы задачей стали называть единицу работы, для выполнения которой предоставляется центральный процессор. Вычислительный процесс может включать в себя несколько задач.
6Супервизор — центральный (главный) управляющий модуль операционной системы. Может состоять из нескольких модулей, например супервизора ввода-вывода, супервизора прерываний, супервизора программ, диспетчера задач и т. д. В последние годы термин «супервизор» применяется все реже и реже.
7Термин «дисциплина обслуживания» следует понимать как некое правило обслуживания, в том числе и учет каких-либо приоритетов при обслуживании. Например, дисциплина «последний пришедший обслуживается первым» определяет обслуживание в порядке, обратном очередности поступления соответствующих запросов.
8Устройство называется инициативным, если по сигналу запроса на прерывание от пего должна запускаться некоторая задача.
9В недалеком прошлом достаточно часто в качестве вычислительных систем общего назначения приобретались мини-ЭВМ и устанавливали на них ОС реального времени.
10Поток выполнения (thread) не следует путать с потоком данных (stream).
11Разговор о процессоре как об одном из ресурсов более характерен для мультипроцессорных систем. В случае однопроцессорных систем чаще говорят о процессорном времени.
12Процесс обращения к данным.
13Реентерабельный — допускающий повторные прерывания (дословный перевод с английского слова «re-enterable»).
14Реентерабельный драйвер может управлять параллельно несколькими однотипными устройствами — более подробно см. в главе 5.