ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования Ульяновский государственный технический университет Операционные системы: архитектура и управление процессами Составитель: ст. преп. П. С. Макаров Ульяновск 2008 УДК 681.3 (076) ББК 32.97я7 О-11 Рецензент канд. тех. наук, директор компьютерно-демонстрационного центра УлГТУ Валюх В. В. Одобрено секцией методических пособий научно-методического совета университета О-11 Операционные системы: архитектура и управление процессами : методические рекомендации / сост. П. С. Макаров. – Ульяновск : УлГТУ, 2008. – 77 с. В пособии представлена тема «Архитектура и управление процессами» учебного курса «Операционные системы». Курс предназначен и читается на факультете информационных систем и технологий УлГТУ студентам 2 курса специальности 220100 «Электронные машины, комплексы, системы и сети» как основной курс. Материалы курса представляют собой иллюстрации презентации с комментариями. УДК 681.3 (076) ББК 32.97я7 © П. С. Макаров, составление, 2008 © Оформление. УлГТУ, 2008 2 ВВЕДЕНИЕ Характерной особенностью нашего времени являются процессы информатизации фактически всех сфер человеческой деятельности, которые интенсивно развиваются. Развиваются и средства проведения занятий, методика проведения лекций. Все чаще и больше лекционная часть курса проходит с применением проекционного оборудования, когда студентам демонстрируются слайды и преподаватель дает объяснения, используя данный материал. В курсе «Операционные системы» также используются презентационные технологии. Именно поэтому в данном пособии выбран схожий подход: читателю предъявляется рисунок – слайд лекции и даются комментарии к слайду. По схожему принципу выстроены учебные пособия на авторизированных учебных курсах компании Microsoft Windows. В данной части курса читатель ознакомится с темой «Архитектура и управление процессами». Операционная система − это интерфейс между аппаратной частью и прикладными программами, с одной стороны, и пользователем ЭВМ, с другой. Это наиболее важная функция любого компьютера. Вначале происходит знакомство с основными модулями структуры операционных систем, а именно с монолитными и многоуровневыми системами, с виртуальной машиной, экзоядром и моделью клиент-сервер. Далее рассматриваются: режимы работы системы, механизм системных вызовов, типовые средства аппаратной поддержки ОС, ядро ОС. После этого читатель знакомится с темой процессов и потоков. Результатом изучения данной темы является знание архитектуры операционных систем, знание сопутствующих понятий, а также представление о том, как происходит управление процессами и потоками в операционных системах. 3 Архитектура операционной системы Рис. 1 • • • • • • •• • • •• •••• • • • • • • • • • •• •• •• •• • Æ , . , , . : 1. , . 2. , . 3. , . Рис. 2 4 Модули ОС не реентерабельны. Кроме планировщика типичная ОС включает в себя много других модулей − подсистему ввода/вывода, файловую систему или системы, диспетчер памяти и ряд менее важных. Практически все эти модули работают с разделяемыми ресурсами и не могут быть реентерабельными. Монолитное ядро. Одно из первых решений этой проблемы состояло в том, чтобы запретить переключение задач во время работы нереентерабельных модулей ОС. При этом все модули ОС собираются в конгломерат, называемый ядром (kernel). Ядро можно назвать привилегированной задачей, но оно не является процессом в полном смысле этого слова, потому что планировщик не может отобрать у ядра процессор. В ядре собраны все нереентерабельные модули системы, а часто и многие реентерабельные. Сам планировщик также является частью ядра. Исполняя системный вызов, пользовательская программа передает управление ядру. При входе в ядро планирование процессов прекращается. Ядро или исполняет запрос, или ставит его в очередь на исполнение и передает управление планировщику. Планировщик переставляет текущий процесс в конец очереди (если он остался активным), программирует таймер, задавая в качестве интервала квант времени, и передает управление следующему активному процессу. Процесс может потерять управление двумя способами − по истечении кванта времени или при исполнении системного вызова. В первом случае он всегда остается в очереди активных, во втором может остаться, а может и не остаться, в зависимости от сделанного вызова. Запросы ввода/вывода и синхронизации с другими процессами часто приводят к переводу процесса в состояние ожидания. Ядро может потерять управление только при аппаратном прерывании. Прерывания обрабатываются специальными программами − драйверами устройств. Драйверы являются частью ядра и не имеют права исполнять обычные системные вызовы. Ни один пользовательский процесс не может получить управление, пока какой-то из модулей ядра активен. Такая архитектура называется монолитным ядром. 5 • • • • • • •• • • • • 1. · 2. Рис. 3 Проблемы с задачами РВ. Основным недостатком монолитного ядра являются проблемы, возникающие при реализации систем реального времени. Действительно, процессы РВ должны получать управление в течение гарантированного интервала времени. И этот интервал должен быть, по возможности, небольшим. А время работы некоторых модулей ОС может быть довольно долгим, например, время поиска файла в директории. Поэтому, если мы хотим реализовать ОС реального времени с хорошим временем реакции, мы должны присваивать процессам РВ более высокий приоритет, чем у ядра. Но если процесс имеет более высокий приоритет, чем ядро, то он может получить управление во время работы нереентерабельного модуля системы. Микроядро. Решением данной проблемы является архитектура, в которой нереентерабельный модуль ОС рассматривается как единый (цельный) критический ресурс. Обращения к нереентерабельным модулям, например, к подсистеме ввода/вывода, реализуются при этом не как вызовы, а как установка запроса в очередь, то есть обработка запроса начинается не сразу же после вызова, а, возможно, через некоторое время. Поэтому мы можем поставить запрос в очередь практически в любой момент, в том числе и во время исполнения обработчика запросов. Мы не можем ставить запрос во время других операций над этой же очередью, но время извлечения/вставки элемента из/в очередь равно времени исполнения трех-четырех команд, и на это время можно просто запретить прерывания. 6 Развитие этой идеи и согласование ее с концепцией гармонически взаимодействующих процессов привело в 80-е годы к архитектуре, известной как микроядро (microkernel). Микроядро − это минимальная функционально полная часть операционной системы, которая обычно состоит из планировщика и базовых средств передачи данных между процессами синхронизации. Микроядро, таким образом, реализует базовые функции операционной системы, на которые опираются другие системные службы и приложения. Основной проблемой при конструировании микроядерной ОС является распознавание тех функций системы, которые могут быть вынесены из ядра. Такие важные компоненты ОС как файловые системы, системы управления окнами и службы безопасности становятся периферийными модулями. Модули системы, не включенные в микроядро, являются отдельными процессами, взаимодействующими с ядром и друг с другом. Если пользовательскому процессу нужно открыть файл, он посылает запрос соответствующему системному процессу и ждет ответа. Если системный процесс в это время был активен, ничего страшного, просто придется подождать немного подольше. Такая архитектура снимает все проблемы с реентерабельностью ядра системы и позволяет процессам реального времени и даже обработчикам прерываний исполнять системные вызовы без ограничений. Последнее, в частности, означает, что теперь можно писать драйверы, обращающиеся к другим драйверам или к ядру системы. • • • • • • •• • • •• •••• • Рис. 4 Отличие архитектур. Основное отличие монолитного и микроядра состоит в том, что в системе на основе микроядра системные и пользовательские 7 процессы одинаково планируются и используют одни и те же системные вызовы для обмена данными и синхронизации. В монолитных же ОС процессы ядра и пользовательские процессы по-разному планируются и, чаще всего, используют для взаимодействия различные средства. Таким образом, в микроядерных архитектурах вертикальное распределение функций операционной системы заменяется на горизонтальное. Компоненты, лежащие выше микроядра, используют средства микроядра для обмена сообщениями, но взаимодействуют непосредственно. Достоинства и недостатки. Модули микроядерных ОС хорошо структурированы − диспетчер памяти, файловые системы, драйверы устройств и т.д. − должны взаимодействовать друг с другом в выделенных точках и с использованием хорошо определенного интерфейса, в полном соответствии с концепциями структурного и модульного программирования. Кроме прочего, это свойство микроядерных систем позволяет естественно использовать их в распределенных средах. Еще одним достоинством микроядра является переносимость (поскольку вся машинно-зависимая часть ОС изолирована в микроядре, для переноса системы на новый процессор требуется меньше изменений и эти изменения логически сгруппированы) и масштабируемость (микроядерная организация поддерживает расширения, опирающиеся на ограниченный набор интерфейсов микроядра). К недостаткам микроядерной архитектуры можно отнести тот факт, что пересылка сообщений осуществляется медленнее обычного вызова системных функций. Это заставляет разработчиков предпринимать усилия по оптимизации структуры сообщений, которыми обмениваются модули микроядерной ОС. Примеры реализаций. Первой из известных коммерческих ОС, последовательно реализующих архитектуру микроядра, была система QNX. Эта система имеет микроядро размером около 10 Кбайт, включающее средства планирования и диспетчеризации процессов, межпроцессного взаимодействия, обработки прерываний и сетевые службы сетевого уровня, что обеспечивает передачу сообщений между процессами как в пределах одной машины, так и между машинами в локальной сети. Микроядро QNX состоит из 14 системных вызовов и может полностью поместиться во внутренний кэш процессора. QNX разрабатывалась для приложений реального времени, в том числе и для использования во встраиваемых микропроцессорных системах; но, благодаря компактности и фантастической производительности, эта ОС иногда заменяет системы общего назначения, например, на сильно загруженных серверах телеконференций. Микроядро использует ряд других современных и будущих ОС, например, UNIX System V R4, Workplace OS фирмы IBM, проект HURD и разрабатываемая новая версия BSD UNIX. Существует несколько общедоступных (public domain) реализаций многопроцессорного микроядра, например, разработанное в Университете Беркли 8 микроядро Mach. Системы WPOS и HURD разрабатываются именно на основе этого ядра. • • • •• •• • • • •• • • •• •••• • • • • • • •• • •• . , .. . . • • • •• • •• • • . , , , . Рис. 5 • • • • • • • • • • ••• • •• • • • • •• •• • • • • • • •• •••• Ý Ý Ý Ý Ý Ý Ý Ý Ý Рис. 6 9 Принцип модульности Модуль − функционально значимый элемент системы, обладающий установленным межмодульным интерфейсом. Последний предполагает возможность замены его любым другим модулем, обладающим таким же интерфейсом. Принцип модульности дает максимальный эффект, если он распространяется на ОС, прикладные программы и аппаратуру. Принцип функциональной избирательности Некоторая часть модулей ОС должна частично находиться в памяти для более эффективной организации вычислительного процесса. Они называются ядром ОС, должны быть минимальными по требуемому объему памяти и наиболее часто используемым функциям. Сюда, как правило, относят модули управления системой прерывания, управления задачами, модули управления ресурсами. Остальные модули ОС называются транзитными. Они загружаются в память по необходимости, а при отсутствии свободного пространства памяти замещаются другими, более необходимыми в данный момент. Принцип генерируемости Суть его в том, что способ исходного представления ядра ОС должен позволять настройку его на непосредственную конфигурацию вычислительного комплекса, где ОС устанавливается и на круг решаемых задач. Процесс генерации осуществляется программой генерации ОС с использованием соответствующего программного языка, позволяющего описывать программные возможности системы и конфигурацию машины. В результате генерируется полная рабочая версия ОС. Использование принципа модульности упрощает настройку ОС. В большинстве современных ОС для персональных компьютеров конфигурирование под имеющийся состав оборудования производится на этапе инсталляции, а последующие изменения параметров ОС или состава драйверов производится редактированием файла конфигурации. Единственной ОС, генерируемой в полном смысле является ОС Linux. В ней можно сгенерировать ядро, оптимальное для данного компьютера, и указать набор подгружаемых драйверов и служб. Принцип функциональной избыточности Он учитывает возможность выполнения одной и той же работы различными средствами. Наличие возможности использования нескольких типов мониторов, систем управления файлами и т.д., позволяет быстро и адекватно адаптировать ОС к данной конфигурации вычислительной системы, эффективно загружать технические средства, получать максимальную производительность. 10 Принцип виртуализации Он позволяет представить структуру системы в виде набора планировщиков процессов и распределителей ресурсов (мониторов) и использовать единую схему распределения ресурсов. Наиболее полно принцип проявляется в понятии виртуальной машины (машины вообще), обладающей идеальными для пользователя архитектурными характеристиками: • Единая (т. е. виртуальная) память неограниченного объема, со временем доступа, совпадающим с аналогичным параметром реальной машины; • Произвольное количество процессов (виртуальных), способных работать с памятью параллельно и последовательно, синхронно и асинхронно (способ выбирается пользователем). • Произвольное число внешних устройств с неограниченными объемами хранимой информации, с различными видами доступа и открытой архитектурой. Степень приближения реальной машины и виртуальной тем больше, чем больше ее реальные характеристики отличны от машины, полученной пользователем. Одним из примеров применения принципа виртуальности является имеющаяся во всех ОС Windows, OS/2 VDM, машина, защищенная подсистема, предоставляющая полную MS-DOS среду и консоль для выполнения DOS приложений. Принцип независимости программ от внешних устройств Суть его в том, что связь программы с внешним устройством устанавливается не на этапе трансляции, а в период планирования ее исполнения. Программа общается не с устройствами, а с ОС, сообщая ей о потребности в ресурсах для выполнения данной работы. Конкретное устройство, на котором эта работа будет выполнена, программу не интересует, это задача ОС. Принцип совместимости Суть принципа − в обеспечении совместимости ОС выполнять программы, написанные для других ОС или под другие аппаратные платформы. Различают совместимость на уровне исходных текстов (текстовая совместимость) и на уровне кодов (двоичная совместимость). Первая требует наличия транслятора, совместимость на уровне библиотек и системных вызовов. При этом требуется повторная компиляция исходных текстов в новый исполняемый модуль. Вторая требует совместимость на уровне архитектуры процессов и систем команд. Для реализации такой совместимости используются эмуляторы (прикладные среды). Одним из средств обеспечения совместимости интерфейсов является соответствие их стандарту POSIX. 11 Принцип открытости и наращиваемости Открытая ОС доступна для анализа пользователем и системным программистом. Наращиваемая ОС позволяет вводить в состав новые модули, модернизировать существующие и т. д., не нарушая целостность системы. Пример наращиваемости демонстрируют структурированные ОС типа «клиент-сервер» на основе микроядерной технологии, когда ядро системы (привилегированная управляющая программа) сохраняется неизменяемым, а состав серверов (набор непривилегированных услуг) может модифицироваться. Примером открытой системы являет собой ОС Linux и все UNIX-системы. Принцип мобильности (переносимости) Принцип требует, чтобы ОС легко устанавливалась с одного процессора на другой, с одной аппаратной платформы на другую. Для этого ОС в основном должна быть написана на языке, имеющимся на всех платформах, куда ее планируется переносить (предпочтительно на С). Кроме того, в ней должны быть минимизированы (а еще лучше − исключены) средства взаимодействия с аппаратурой. Не исключенные аппаратно зависимые части кода должны быть изолированы в хорошо локализируемых модулях. Тогда при переносе меняются (или подстраиваются) только эти локальные данные и функции взаимодействия с ними. ОС, соответствующие стандарту POSIX, являются переносимыми, поскольку для этой цели он и создавался. Принцип безопасности вычислений Правила безопасности защищают ресурсы одного пользователя от другого и устанавливают квоты на ресурсы для предотвращения захвата всех ресурсов одним пользователем. Основы стандартов в области безопасности вычислений были заложены в документе под названием «Критерии оценки наземных компьютерных систем», изданном Национальным центром компьютерной безопасности США (NCSC – National Computer Security Center) в 1983 г. В соответствии с ним, безопасной считается система, посредством специальных механизмов контролирующая доступ к информации так, что доступ к ней получают только лица с соответствующими полномочиями или процессы, выполняющиеся от их имени. Определено 4 уровня безопасности : А (высший) − D (низший), при этом в класс D попадают системы, у которых выявлено несоответствие всем трем высшим классам. Класс С делится на два уровня: 1. С1 обеспечивает защиту данных от ошибок пользователя, но не от злоумышленников. 2. С2 защищает данные в обоих ситуациях. На уровне С2 должны присутствовать: • Секретность входа (идентификация пользователей по уникальному имени и паролю). 12 • Контроль доступа (информирование владельца данными о лицах, имеющими доступ к данным и их правах на пользование ими). • Наблюдение и учет за ситуацией с безопасностью (фиксация попыток получить доступ, создать или удалить системные реестры). • Защита памяти (удаление содержимого памяти предыдущего сеанса работы перед началом нового). Системы уровня В реализуют контроль доступа (каждый пользователь имеет рейтинг защиты и доступ только в соответствии с этим рейтингом). Эти системы защищены от ошибочного поведения пользователя. Уровень А требует дополнительно формального, математически доказанного соответствия системы требованиям безопасности. Однако А-уровень занимает своими управляющими механизмами до 90% процессорного времени. Более безопасные системы не только снижают продуктивность, но и ограничивают число доступных приложений. Различные коммерческие структуры, например, банки, поддерживают безопасность своих систем, как правило, на уровне С2. ОС виртуальных машин • • • •••• • • • • • •• • • • Ý Ý Ý Ý Рис. 7 Несколько вариантов реализации промежуточного ПО Если не вдаваться в детали, то компьютерные системы обычно состоят из аппаратного обеспечения, операционной системы, которая работает на этом аппаратном обеспечении, и приложений, работающих в этой ОС. (Правда, во встроенных системах операционная система в традиционном понимании может 13 отсутствовать.) Так или иначе, операционная система «знает» обо всех ресурсах и возможностях используемого оборудования и управляет им напрямую. Если между ОС и вычислительной системой присутствует еще один программный уровень, тогда ОС получает только ту информацию, которую этот промежуточный уровень ей предоставляет. И сведения о ресурсах и возможностях используемого аппаратного обеспечения будут зависеть от этого промежуточного программного уровня. Операционная система может контролировать нижележащее оборудование только в той мере, в какой ему позволяет этот промежуточный уровень. Промежуточный программный уровень может сообщить ОС все, что ему известно об аппаратном обеспечении, и просто передавать управляющие директивы без каких-либо изменений. Но возможно и иное решение: промежуточный уровень может не сообщать ОС все данные об имеющемся оборудовании и до определенной степени менять управляющие директивы, передаваемые ОС. Конечно, существуют разнообразные устройства и интерфейсы, которые добавляют свои нюансы и сложности в вычислительную среду. Но использование программного уровня для формирования набора вычислительных ресурсов и правил работы и составляют основу так называемой виртуальной машины. Виртуальная машина — это вычислительная среда, набор ресурсов и правил работы которой формируется (с помощью программного обеспечения) в некой другой вычислительной среде. Виртуальная машина — гипервизор Виртуальные машины являются основой таких технологий, как ESX Server компании VMware и свободно распространяемый монитор виртуальной машины Xen. Оба продукта представляют собой серверы, в которых используется одновременно несколько операционных систем, ориентированных на архитектуру x86. Подходы, реализованные в этих продуктах, — разновидности того, что называют виртуальными машинами для аппаратного уровня, для «голого железа» или виртуальными машинами типа гипервизор. Промежуточный программный уровень (называемый монитором виртуальной машины или гипервизором) размещается между ОС и аппаратным обеспечением. Благодаря гипервизору у всех работающих на данном компьютере операционных систем создается иллюзия, что каждая из них является единственной. Использование нескольких операционных систем на одной серверной платформе дает целый ряд преимуществ. В этом случае можно полностью задействовать ресурсы очень мощных серверов, обеспечить обратную совместимость для унаследованных программ и распределить приложения между несколькими ОС таким образом, чтобы они не создавали помех друг для друга. VMware использует прозрачную виртуализацию, что значит отсутствие необходимости менять операционные системы, работающие на гипервизоре. Xen использует «паравиртуализацию», то есть требует изменения операционных систем таким образом, чтобы они могли одновременно работать на одном 14 и том же аппаратном обеспечении. По мнению специалистов Xen, паравиртуализация увеличивает скорость и повышает эффективность работы. Хостовые виртуальные машины Microsoft Virtual PC и GSX Server и GSX Workstation компании VMware называют хостовыми виртуальными машинами. В этих продуктах виртуальная машина, как и другие приложения, работает в операционной системе. Приложение виртуальной машины разделяется на промежуточный программный уровень, операционную систему и приложение, работающее в этой ОС. Такая схема менее эффективна и предлагает не столь широкие возможности, как схема, применяемая в серверах с гипервизором, но дает свои преимущества, позволяя работать с унаследованными программами и отделять приложения от остальной системы. Пользователь, который хочет посетить потенциально опасные Web-сайты, например, может усилить защиту на время своей работы в Web за счет применения виртуальной машины. Виртуальные машины уровня приложений Виртуальные машины уровня приложений, такие как виртуальная машина Java, работают как приложения, что роднит их с решениями, поддерживающими хостовую модель. Такие виртуальные машины, однако, сочетают в себе промежуточный программный уровень с операционной системой. Виртуальная машина Java выполняется как приложение в исходной среде, а приложения Java работают уже на виртуальной машине. Как утверждается, одно из преимуществ такой парадигмы программирования состоит в том, что программа на Java будет работать в любой виртуальной машине Java без перекомпиляции. И в этом случае только от поставщика зависит, сможет ли виртуальная машина Java работать с несколькими исходными операционными системами. Параллельная виртуальная машина Параллельная виртуальная машина — это несколько иной подход к созданию виртуальной машины. В этом случае промежуточный программный уровень имеет вид программного демона, или серверной программы, которая наряду с набором библиотечных вызовов должна быть скомпилирована в приложение, предназначенное для работы на параллельной виртуальной машине. Библиотечные вызовы, которые предполагают обращение к серверным программам, позволяют представлять сеть компьютеров как один компьютер с параллельными процессорами. Как говорится, вы можете заставить программу делать все, что угодно. Правда, не всегда такая программа будет работать быстро и эффективно. Но до тех пор, пока существует промежуточный программный уровень, будет существовать и виртуализация. 15 • ••• • • • • , , , . , . . . Рис. 8 Экзоядро — ядро операционной системы компьютеров, предоставляющее лишь функции для взаимодействия между процессами и безопасного выделения и освобождения ресурсов. «Экзо» — приставка, обозначающая нечто внешнее, находящееся снаружи. В традиционных операционных системах ядро предоставляет не только минимальный набор сервисов, обеспечивающих выполнение программ, но и большое количество высокоуровневых абстракций для использования разнородных ресурсов компьютера: оперативной памяти, жестких дисков, сетевых подключений. В отличие от них, ОС на основе экзоядра предоставляет лишь набор сервисов для взаимодействия между приложениями, а также необходимый минимум функций, связанных с защитой: выделение и высвобождение ресурсов, контроль прав доступа, и т. д. Экзоядро не занимается предоставлением абстракций для физических ресурсов – эти функции выносятся в библиотеку пользовательского уровня (так называемую libOS). 16 Основная идея операционной системы на основе экзоядра состоит в том, что ядро должно выполнять лишь функции координатора для небольших процессов, связанных только одним ограничением — экзоядро должно иметь возможность гарантировать безопасное выделение и освобождение ресурсов оборудования. В отличие от ОС на основе микроядра, ОС, базирующиеся на экзоядре, обеспечивают гораздо большую эффективность за счет отсутствия необходимости в переключении между процессами при каждом обращении к оборудованию. Архитектуры на основе экзоядер являются дальнейшим развитием и усовершенствованием микроядерных архитектур и одновременно ужесточают требования к минималистичности и простоте кода ядра. LibOS может обеспечивать произвольный набор абстракций, совместимый с той или другой уже существующей операционной системой, например, Linux или Windows. • • • •• • •• • •• •-••• • •• • • • •• • «•• • •• • -••• • •• » , - . , , , , . , . , , . , . Рис. 9 Модель «клиент-сервер» Модель «клиент-сервер» − это еще один подход к структурированию ОС. В широком смысле модель «клиент-сервер» предполагает наличие программного компонента − потребителя какого-либо сервиса-клиента, и программного компонента − поставщика этого сервиса-сервера. Взаимодействие между клиентом и сервером стандартизуется, так что сервер может обслуживать клиентов, реализованных различными способами и, может быть, разными производителями. При этом главным требованием является то, чтобы они запрашивали 17 услуги сервера понятным ему способом. Инициатором обмена обычно является клиент, который посылает запрос на обслуживание серверу, находящемуся в состоянии ожидания запроса. Один и тот же программный компонент может быть клиентом по отношению к одному виду услуг и сервером для другого вида услуг. Модель «клиент-сервер» является скорее удобным концептуальным средством ясного представления функций того или иного программного элемента в той или иной ситуации, нежели технологией. Эта модель успешно применяется не только при построении ОС, но и на всех уровнях программного обеспечения, и имеет в некоторых случаях более узкий, специфический смысл, сохраняя, естественно, при этом все свои общие черты. Рис. 10 Применительно к структурированию ОС идея состоит в разбиении ее на несколько процессов-серверов, каждый из которых выполняет отдельный набор сервисных функций, например, управление памятью, создание или планирование процессов. Каждый сервер выполняется в пользовательском режиме. Клиент, которым может быть либо другой компонент ОС, либо прикладная программа, запрашивает сервис, посылая сообщение на сервер. Ядро ОС (называемое здесь микроядром), работая в привилегированном режиме, доставляет сообщение нужному серверу, сервер выполняет операцию, после чего ядро возвращает результаты клиенту с помощью другого сообщения (рисунок 10). Данная теоретическая модель является идеализированным описанием системы клиент-сервер, в которой ядро состоит только из средств передачи сообщений. В действительности различные варианты реализации модели клиент18 сервер в структуре ОС могут существенно различаться по объему работ, выполняемых в режиме ядра. Микроядро реализует жизненно важные функции, лежащие в основе операционной системы. Это базис для менее существенных системных служб и приложений. Именно вопрос о том, какие из системных функций считать несущественными, и, соответственно, не включать их в состав ядра, является предметом спора среди соперничающих сторонников идеи микроядра. В общем случае подсистемы, бывшие традиционно неотъемлемыми частями операционной системы − файловые системы, управление окнами и обеспечение безопасности − становятся периферийными модулями, взаимодействующими с ядром и друг с другом. Использование модели «клиент-сервер» повышает надежность. Каждый сервер выполняется в виде отдельного процесса в своей собственной области памяти, и, таким образом, защищен от других процессов. Более того, поскольку серверы выполняются в пространстве пользователя, они не имеют непосредственного доступа к аппаратуре и не могут модифицировать память, в которой хранится управляющая программа. И если отдельный сервер может потерпеть крах, то он может быть перезапущен без остановки или повреждения остальной части ОС. Эта модель хорошо подходит для распределенных вычислений, так как отдельные серверы могут работать на разных процессорах мультипроцессорного компьютера или даже на разных компьютерах. При получении от процесса сообщения микроядро может обработать его самостоятельно или переслать другому процессу. Так как микроядру все равно, пришло ли сообщение от локального или удаленного процесса, подобная схема передачи сообщений является элегантным базисом для RPC. Однако такая гибкость не дается даром. Пересылка сообщений не так быстра, как обычные вызовы функций, и ее оптимизация является критическим фактором успеха операционной системы на основе микроядра. Windows NT, например, в некоторых случаях заменяет перенос сообщений на коммуникационные каналы с общей памятью, имеющие более высокую пропускную способность. Хотя это и стоит дороже в смысле потребления фиксированной памяти микроядра, данная альтернатива может помочь сделать модель пересылки сообщений более практичной. 19 1. • •• • • • •• • (••• •• • • ••• •), . 2. • •• • • • • •• •••• (••••• • ), , . Рис. 11 Ядро в привилегированном режиме Для надежного управления ходом выполнения приложений операционная система должна иметь по отношению к приложениям определенные привилегии. Иначе некорректно работающее приложение может вмешаться в работу ОС и, например, разрушить часть ее кодов. Все усилия разработчиков операционной системы окажутся напрасными, если их решения воплощены в незащищенные от приложений модули системы, какими бы элегантными и эффективными эти решения ни были. Операционная система должна обладать исключительными полномочиями также для того, чтобы играть роль арбитра в споре приложений за ресурсы компьютера в мультипрограммном режиме. Ни одно приложение не должно иметь возможности без ведома ОС получать дополнительную область памяти, занимать процессор дольше разрешенного операционной системой периода времени, непосредственно управлять совместно используемыми внешними устройствами. Обеспечить привилегии операционной системе невозможно без специальных средств аппаратной поддержки. Аппаратура компьютера должна поддерживать как минимум два режима работы – пользовательский режим (user mode) и привилегированный режим, который также называют режимом ядра (kernel mode), или режимом супервизора (supervisor mode). Подразумевается, что операционная система или некоторые ее части работают в привилегированном режиме, а приложения – в пользовательском режиме. Так как ядро выполняет все основные функции ОС, то чаще всего именно ядро становится той частью ОС, которая работает в привилегированном режи20 ме. Иногда это свойство – работа в привилегированном режиме – служит основным определением понятия «ядро». Приложения ставятся в подчиненное положение за счет запрета выполнения в пользовательском режиме некоторых критичных команд, связанных с переключением процессора с задачи на задачу, управлением устройствами вводавывода, доступом к механизмам распределения и защиты памяти. Выполнение некоторых инструкций в пользовательском режиме запрещается безусловно (очевидно, что к таким инструкциям относится инструкция перехода в привилегированный режим), тогда как другие запрещается выполнять только при определенных условиях. Например, инструкции ввода-вывода могут быть запрещены приложениям при доступе к контроллеру жесткого диска, который хранит данные, общие для ОС и всех приложений, но разрешены при доступе к последовательному порту, который выделен в монопольное владение для определенного приложения. Важно, что условия разрешения выполнения критичных инструкций находятся под полным контролем ОС, и этот контроль обеспечивается за счет набора инструкций, безусловно запрещенных для пользовательского режима. Аналогичным образом обеспечиваются привилегии ОС при доступе к памяти. Например, выполнение инструкции доступа к памяти для приложения разрешается, если инструкция обращается к области памяти, отведенной данному приложению операционной системой, и запрещается при обращении к областям памяти, занимаемым ОС или другими приложениями. Полный контроль ОС над доступом к памяти достигается за счет того, что инструкция или инструкции конфигурирования механизмов защиты памяти (например, изменения ключей защиты памяти в мэйнфреймах IBM или указателя таблицы дескрипторов памяти в процессорах Pentium) разрешается выполнять только в привилегированном режиме. Очень важно, что механизмы защиты памяти используются операционной системой не только для защиты своих областей памяти от приложений, но и для защиты областей памяти, выделенных ОС какому-либо приложению, от остальных приложений. Говорят, что каждое приложение работает в своем адресном пространстве. Это свойство позволяет локализовать некорректно работающее приложение в собственной области памяти, так что его ошибки не оказывают влияния на остальные приложения и операционную систему. Между количеством уровней привилегий, реализуемых аппаратно, и количеством уровней привилегий, поддерживаемых ОС, нет прямого соответствия. Так, на базе четырех уровней, обеспечиваемых процессорами компании Intel, операционная система OS/2 строит трехуровневую систему привилегий, а операционные системы Windows NT, UNIX и некоторые другие ограничиваются двухуровневой системой. С другой стороны, если аппаратура поддерживает хотя бы два уровня привилегий, то ОС может на этой основе создать программным способом сколь угодно развитую систему защиты. 21 Эта система может, например, поддерживать несколько уровней привилегий, образующих иерархию. Наличие нескольких уровней привилегий позволяет более тонко распределять полномочия как между модулями операционной системы, так и между самими приложениями. Появление внутри операционной системы более привилегированных и менее привилегированных частей позволяет повысить устойчивость ОС к внутренним ошибкам программных кодов, так как такие ошибки будут распространяться только внутри модулей с определенным уровнем привилегий. Дифференциация привилегий в среде прикладных модулей позволяет строить сложные прикладные комплексы, в которых часть более привилегированных модулей может, например, получать доступ к данным менее привилегированных модулей и управлять их выполнением. На основе двух режимов привилегий процессора ОС может построить сложную систему индивидуальной защиты ресурсов, примером которой является типичная система защиты файлов и каталогов. Такая система позволяет задать для любого пользователя определенные права доступа к каждому из файлов и каталогов. Повышение устойчивости операционной системы, обеспечиваемое переходом ядра в привилегированный режим, достигается за счет некоторого замедления выполнения системных вызовов. Системный вызов привилегированного ядра инициирует переключение процессора из пользовательского режима в привилегированный, а при возврате к приложению — переключение из привилегированного режима в пользовательский. Во всех типах процессоров из-за дополнительной двукратной задержки переключения переход на процедуру со сменой режима выполняется медленнее, чем вызов процедуры без смены режима. Архитектура ОС, основанная на привилегированном ядре и приложениях пользовательского режима, стала, по существу, классической. Ее используют многие популярные операционные системы, в том числе многочисленные версии UNIX, VAX VMS, IBM OS/390, OS/2 и с определенными модификациями – Windows NT. В некоторых случаях разработчики ОС отступают от этого классического варианта архитектуры, организуя работу ядра и приложений в одном и том же режиме. Так, известная специализированная операционная система NetWare компании Novell использует привилегированный режим процессоров Intel x86/ Pentium как для работы ядра, так и для работы своих специфических приложений – загружаемых модулей NLM. При таком построении ОС обращения приложений к ядру выполняются быстрее, так как нет переключения режимов, однако при этом отсутствует надежная аппаратная защита памяти, занимаемой модулями ОС, от некорректно работающего приложения. Разработчики NetWare пошли на такое потенциальное снижение надежности своей операционной системы, поскольку ограниченный набор ее специализированных приложений позволяет компенсировать этот архитектурный недостаток за счет тщательной отладки каждого приложения. 22 В одном режиме работают также ядро и приложения тех операционных систем, которые разработаны для процессоров, вообще не поддерживающих привилегированного режима работы. Наиболее популярным процессором такого типа был процессор Intel 8088/86, послуживший основой для персональных компьютеров компании IBM. Операционная система MS-DOS, разработанная компанией Microsoft для этих компьютеров, состояла из двух модулей msdos.sys и io.sys, составлявших ядро системы (хотя название «ядро» для этих модулей не употреблялось, по своей сути они им являлись), к которым с системными вызовами обращались командный интерпретатор command.com, системные утилиты и приложения. Архитектура MS-DOS соответствует архитектуре ОС. Некорректно написанные приложения вполне могли разрушить основные модули MS-DOS, что иногда и происходило, но область использования MS-DOS (и многих подобных ей ранних операционных систем для персональных компьютеров, таких как MSX, СР/М) и не предъявляла высоких требований к надежности ОС. ПРИМЕЧАНИЕ Появление в более поздних версиях процессоров Intel (начиная с 80286) возможности работы в привилегированном режиме не было использовано разработчиками MS-DOS. Эта ОС всегда работает на процессорах данного типа в так называемом реальном режиме, в котором эмулируется процессор 8086/88. Не следует считать, что реальный режим является синонимом пользовательского режима, а привилегированный режим — его альтернативой. Реальный режим был реализован только для совместимости поздних моделей процессоров с ранней моделью 8086/88 и альтернативой ему является защищенный режим работы процессора, в котором становятся доступными все особенности процессоров поздних моделей, в том числе и работа на одном из четырех уровней привилегий. 23 Рис. 12 Системные вызовы Интерфейс между операционной системой и программами пользователя определяется набором системных вызовов, предоставляемых операционной системой. Чтобы на самом деле понять, что же делает операционная система, мы должны подробно рассмотреть этот интерфейс. Системные вызовы, доступные в интерфейсе, меняются от одной операционной системы к другой (хотя лежащая в их основе концепция практически одинакова). Теперь мы столкнулись с проблемой выбора между (1) неопределенными обобщениями («операционные системы имеют системные вызовы для чтения файлов») и (2) какой-либо конкретной системой («в UNIX существует системный вызов для чтения с тремя параметрами: один для задания файла, второй – для того, чтобы указать, куда нужно поместить прочитанные данные, третий задает количество байтов, которое нужно прочитать»). Мы выбрали второй подход. При этом способе нужно проделать больше работы, но он обеспечивает лучшее понимание torp, что в реальности происходит в операционной системе. Несмотря на то что это обсуждение затрагивает конкретно стандарт POSIX (международный стандарт 9945-1), а, следовательно, также и операционные системы UNIX, System V, BSD, Linux, MINIX и т. д., у большинства других современных операционных систем есть системные вызовы, выполняющие те же самые функции, хотя детали могут быть различны. Так как фактический механизм обращения к системным функциям является в высокой степени машинно-зависимым и часто должен реализовываться на ас24 семблере, существуют библиотеки процедур, делающие возможным обращение к системным процедурам из программ на Си других языках с тем же успехом. Очень полезно всегда помнить следующее. Любой компьютер с одним процессором в каждый конкретный момент времени может выполнить только одну команду. Если процесс выполняет программу пользователя в пользовательском режиме и нуждается в системной службе, например чтении данных из файла, он должен выполнить прерывание или команду системного вызова для передачи управления операционной системе. Затем операционная система по параметрам вызова определяет, что требуется вызывающему процессу. После этого она обрабатывает системный вызов и возвращает управление команде, следующей за системным вызовом. В известном смысле выполнение системного вызова похоже на осуществление вызова процедуры, только первый проникает в ядро, а второй этого не делает. Для того чтобы прояснить механизм системных вызовов, кратко рассмотрим системный вызов read. Как упоминалось выше, у него есть три параметра: первый служит для задания файла, второй указывает на буфер, третий задает количество байтов, которое нужно прочитать. Как практически все системные вызовы, он запускается из программы на Си с помощью вызова библиотечной процедуры с тем же именем, что и системный вызов: read. Вызов из программы на Си может выглядеть так: count = read(fd. buffer, nbytes): Системный вызов (и библиотечная процедура) возвращает количество действительно прочитанных байтов в переменной сcount. Обычно эта величина совпадает с параметром nbytes, но может быть меньше, если, например, в процессе чтения процедуре встретился конец файла. Если системный вызов не может быть выполнен или из-за неправильных параметров или из-за дисковой ошибки, значение счетчика count устанавливается равным 1, а номер ошибки помещается в глобальную переменную errno. Программы всегда должны проверять результат системного вызова, чтобы отслеживать появление ошибки. Системные вызовы выполняются за серию шагов. Вернемся к упоминавшемуся выше примеру вызова read для того, чтобы разъяснить этот момент. Сначала при подготовке к вызову библиотечной процедуры read, которая фактически осуществляет системный вызов read, вызывающая программа помещает параметры в стек, как показано в шагах 1-3 на рис. 12. Компиляторы С и С++ помещают параметры в стек в обратном порядке, так исторически сложилось (чтобы первым был параметр для printf, то есть строка формата оказалась на вершине стека). Первый и третий параметры передаются по значению, а второй параметр передается по ссылке, то есть передается адрес буфера (на то, что это ссылка, указывает символ &), а не его содержимое. Затем следует собственно вызов библиотечной процедуры (шаг 4). Эта команда процессора представляет 25 собой обычную команду вызова процедуры и применяется для вызова любых процедур. Библиотечная процедура, возможно, написанная на ассемблере, обычно помещает номер системного вызова туда, где его ожидает операционная система, например в регистр (шаг 5). Затем она выполняет команду TRAP (эмулированное прерывание) для переключения из пользовательского режима в режим ядра и начинает выполнение с фиксированного адреса внутри ядра (шаг 6). Запускаемая программа ядра проверяет номер системного вызова и затем отправляет его нужному обработчику, как правило, используя таблицу указателей на обработчики системных вызовов, индексированную по номерам вызовов (шаг 7). В этом месте начинает функционировать обработчик системных вызовов (шаг 8). Как только он завершает свою работу, управление может возвращаться в пространство пользователя к библиотечной процедуре, к команде, следующей за командой TRAP (шаг 9). Эта процедура в свою очередь передает управление программе пользователя обычным способом, которым производится возврат из вызванной процедуры (шаг 10). Чтобы закончить работу, программа пользователя должна очистить стек, как это делается и после каждого вызова процедуры (шаг 11). Учитывая, что стек растет вниз, последняя команда увеличивает указатель стека ровно настолько, насколько нужно для удаления параметров, помещенных в стек перед запросом read. Теперь программа может продолжать свою работу. На шаге 9 мы использовали выражение «может возвращаться в пространство пользователя к библиотечной процедуре...» не просто так. Системный вызов может блокировать вызвавшую его процедуру, препятствуя продолжению ее работы. Например, если она пытается прочесть что-то с клавиатуры, а там еще ничего не набрано, процедура должна быть блокирована. В этом случае операционная система ищет процесс, который может быть запущен следующим. Позже, когда нужное устройство станет доступно, система вспомнит о блокированном процессе и шаги 9 − 11 будут выполнены. В следующих разделах мы рассмотрим некоторые из наиболее часто применяющихся системных вызовов стандарта POSIX или, точнее, библиотечных процедур, которые выполняют эти вызовы. В POSIX существует более 100 процедурных вызовов. Далее мы кратко опишем каждый вызов его действие. Службы, предоставляемые этими вызовами, в значительной степени определяют действий операционной системы, так как управление ресурсами на персональном компьютере минимально (по крайней мере, по сравнению с большими машинами, на которых работают несколько пользователей). К этим службам относятся такие функции, как создание и завершение процессов, создание, удаление, чтение и запись файлов, управление каталогами, выполнение ввода и вывода. Особое внимание следует обратить на то, что преобразование вызовов процедур POSIX в системные вызовы не является взаимно однозначным. Стандарт POSIX определяет ряд процедур, которые должны поддерживать совместимые системы, но он не указывает, являются ли они системными вызовами, 26 библиотечными вызовами или чем-нибудь еще. Если процедуру можно выполнить без системного вызова (то есть без переключения в режим работы ядра), то обычно она работает в пространстве пользователя, потому что так быстрее. Однако большинство процедур POSIX выполняет системные вызовы, обычно с одной процедурой, преобразующейся напрямую в системный вызов. В некоторых случаях, особенно когда требуемые процедуры являются всего лишь разновидностями друг друга, один системный вызов обрабатывает сразу несколько библиотечных вызовов. Принципы взаимодействия с ядром В любой операционной системе поддерживается некоторый механизм, который позволяет пользовательским программам обращаться за услугами ядра ОС. В операционных системах наиболее известной советской вычислительной машины БЭСМ-6 соответствующие средства общения с ядром назывались экстракодами, в операционных системах IBM они назывались системными макрокомандами и т. д. В ОС UNIX такие средства называются системными вызовами. Название не изменяет смысл, который состоит в том, что для обращения к функциям ядра ОС используются «специальные команды» процессора, при выполнении которых возникает особого рода внутреннее прерывание процессора, переводящее его в режим ядра (в большинстве современных ОС этот вид прерываний называется trap − ловушка). При обработке таких прерываний (дешифрации) ядро ОС распознает, что на самом деле прерывание является запросом к ядру со стороны пользовательской программы на выполнение определенных действий, выбирает параметры обращения и обрабатывает его, после чего выполняет «возврат из прерывания», возобновляя нормальное выполнение пользовательской программы. Понятно, что конкретные механизмы возбуждения внутренних прерываний по инициативе пользовательской программы различаются в разных аппаратных архитектурах. Поскольку ОС UNIX стремится обеспечить среду, в которой пользовательские программы могли бы быть полностью мобильны, потребовался дополнительный уровень, скрывающий особенности конкретного механизма возбуждения внутренних прерываний. Этот механизм обеспечивается так называемой библиотекой системных вызовов. Для пользователя библиотека системных вызовов представляет собой обычную библиотеку заранее реализованных функций системы программирования языка Си. При программировании на языке Си использование любой функции из библиотеки системных вызовов ничем не отличается от использования любой собственной или библиотечной Си-функции. Однако внутри любой функции конкретной библиотеки системных вызовов содержится код, являющийся, вообще говоря, специфичным для данной аппаратной платформы. Наиболее важные системные вызовы ОС UNIX рассматриваются в оставшихся разделах этой части курса и в следующей части. 27 Принципы обработки прерываний Конечно, применяемый в операционных системах механизм обработки внутренних и внешних прерываний в основном зависит от того, какая аппаратная поддержка обработки прерываний обеспечивается конкретной аппаратной платформой. К счастью, к настоящему моменту (и уже довольно давно) основные производители компьютеров де-факто пришли к соглашению о базовых механизмах прерываний. Если говорить не очень точно и конкретно, суть принятого на сегодня механизма состоит в том, что каждому возможному прерыванию процессора (будь то внутреннее или внешнее прерывание) соответствует некоторый фиксированный адрес физической оперативной памяти. В тот момент, когда процессору разрешается прерваться по причине наличия внутренней или внешней заявки на прерывание, происходит аппаратная передача управления на ячейку физической оперативной памяти с соответствующим адресом − обычно адрес этой ячейки называется «вектором прерывания» (как правило, заявки на внутреннее прерывание, т. е. заявки, поступающие непосредственно от процессора, удовлетворяются немедленно). Дело операционной системы − разместить в соответствующих ячейках оперативной памяти программный код, обеспечивающий начальную обработку прерывания и инициирующий полную обработку. В основном, ОС UNIX придерживается общего подхода. В векторе прерывания, соответствующем внешнему прерыванию, т. е. прерыванию от некоторого внешнего устройства, содержатся команды, устанавливающие уровень выполнения процессора (уровень выполнения определяет, на какие внешние прерывания процессор должен реагировать незамедлительно) и осуществляющие переход на программу полной обработки прерывания в соответствующем драйвере устройства. Для внутреннего прерывания (например, прерывания по инициативе программы пользователя при отсутствии в основной памяти нужной страницы виртуальной памяти, при возникновении исключительной ситуации в программе пользователя и т. д.) или прерывания от таймера в векторе прерывания содержится переход на соответствующую программу ядра ОС UNIX. 28 Рис. 13 Многослойная структура ОС Вычислительную систему, работающую под управлением ОС на основе ядра, можно рассматривать как систему, состоящую из трех иерархически расположенных слоев: нижний слой образует аппаратура, промежуточный – ядро, а утилиты, обрабатывающие программы и приложения, составляют верхний слой системы (рис. 13). Слоистую структуру вычислительной системы принято изображать в виде системы концентрических окружностей, иллюстрируя тот факт, что каждый слой может взаимодействовать только со смежными слоями. Действительно, при такой организации ОС приложения не могут непосредственно взаимодействовать с аппаратурой, а только через слой ядра. Многослойный подход является универсальным и эффективным способом декомпозиции сложных систем любого типа, в том числе и программных. В соответствии с этим подходом система состоит из иерархии слоев. Каждый слой обслуживает вышележащий слой, выполняя для него некоторый набор функций, которые образуют межслойный интерфейс (рис. 13). На основе функций нижележащего слоя следующий (вверх по иерархии) слой строит свои функции – более сложные и более мощные, которые, в свою очередь, оказываются примитивами для создания еще более мощных функций вышележащего слоя. Строгие правила касаются только взаимодействия между слоями системы, а между модулями внутри слоя связи могут быть произвольными. Отдельный 29 модуль может выполнить свою работу либо самостоятельно, либо обратиться к другому модулю своего слоя, либо обратиться за помощью к нижележащему слою через межслойный интерфейс. Такая организация системы имеет много достоинств. Она существенно упрощает разработку системы, так как позволяет сначала определить «сверху вниз» функции слоев и межслойные интерфейсы, а затем при детальной реализации постепенно наращивать мощность функций слоев, двигаясь «снизу вверх». Кроме того, при модернизации системы можно изменять модули внутри слоя без необходимости производить какие-либо изменения в остальных слоях, если при этих внутренних изменениях межслойные интерфейсы остаются в силе. Поскольку ядро представляет собой сложный многофункциональный комплекс, то многослойный подход обычно распространяется и на структуру ядра. - • • • •• •••• • Рис. 14 Ядро может состоять из следующих слоев: • Средства аппаратной поддержки ОС. До сих пор об операционной системе говорилось как о комплексе программ, но часть функций ОС может выполняться и аппаратными средствами. Поэтому иногда можно встретить определение операционной системы как совокупности программных и аппаратных средств, что и отражено на рис. 14. К операционной системе относят, естественно, не все аппаратные устройства компьютера, а только средства аппаратной поддержки ОС, то есть те, которые прямо участвуют в организации вычислительных процессов: средства поддержки привилегированного режима, систему прерываний, средст30 ва переключения контекстов процессов, средства защиты областей памяти и т. п. • Машинно-зависимые компоненты ОС. Этот слой образуют программные модули, в которых отражается специфика аппаратной платформы компьютера. В идеале этот слой полностью экранирует вышележащие слои ядра от особенностей аппаратуры. Это позволяет разрабатывать вышележащие слои на основе машинно-независимых модулей, существующих в единственном экземпляре для всех типов аппаратных платформ, поддерживаемых данной ОС. Примером экранирующего слоя может служить слой HAL операционной системы Windows NT. • Базовые механизмы ядра. Этот слой выполняет наиболее примитивные операции ядра, такие как программное переключение контекстов процессов, диспетчеризацию прерываний, перемещение страниц из памяти на диск и обратно и т. п. Модули данного слоя не принимают решений о распределении ресурсов – они только отрабатывают принятые «наверху» решения, что и дает повод называть их исполнительными механизмами для модулей верхних слоев. Например, решение о том, что в данный момент нужно прервать выполнение текущего процесса А и начать выполнение процесса В, принимается менеджером процессов на вышележащем слое, а слою базовых механизмов передается только директива о том, что нужно выполнить переключение с контекста текущего процесса на контекст процесса В. • Менеджеры ресурсов. Этот слой состоит из мощных функциональных модулей, реализующих стратегические задачи по управлению основными ресурсами вычислительной системы. Обычно на данном слое работают менеджеры (называемые также диспетчерами) процессов, вводавывода, файловой системы и оперативной памяти. Разбиение на менеджеры может быть и несколько иным, например, менеджера файловой системы иногда объединяют с менеджером ввода-вывода, а функции управления доступом пользователей к системе в целом и ее отдельным объектам поручают отдельному менеджеру безопасности. Каждый из менеджеров ведет учет свободных и используемых ресурсов определенного типа и планирует их распределение в соответствии с запросами приложений. Например, менеджер виртуальной памяти управляет перемещением страниц из оперативной памяти на диск и обратно. Менеджер должен отслеживать интенсивность обращений к страницам, время пребывания их в памяти, состояния процессов, использующих данные, и многие другие параметры, на основании которых он время от времени принимает решения о том, какие страницы необходимо выгрузить и какие – загрузить. Для исполнения принятых решений менеджер обращается к нижележащему слою базовых механизмов с запросами о загрузке (выгрузке) конкретных страниц. Внутри слоя менеджеров существуют тесные взаимные связи, отражающие тот факт, что для выполнения процессу нужен доступ одновременно к нескольким ресурсам: процессору, области памяти, воз31 можно, к определенному файлу или устройству ввода-вывода. Например, при создании процесса менеджер процессов обращается к менеджеру памяти, который должен выделить процессу определенную область памяти для его кодов и данных. • Интерфейс системных вызовов. Этот слой является самым верхним слоем ядра и взаимодействует непосредственно с приложениями и системными утилитами, образуя прикладной программный интерфейс операционной системы. Функции API, обслуживающие системные вызовы, предоставляют доступ к ресурсам системы в удобной и компактной форме без указания деталей их физического расположения. Например, в операционной системе UNIX с помощью системного вызова fd = open("/doc/a.txt", 0_RDONLY) приложение открывает файл a.txt, хранящийся в каталоге /doc, а с помощью системного вызова read(fd. buffer, count) читает из этого файла в область своего адресного пространства, имеющую имя buffer, некоторое количество байт. Для осуществления таких комплексных действий системные вызовы обычно обращаются за помощью к функциям слоя менеджеров ресурсов, причем для выполнения одного системного вызова может понадобиться несколько таких обращений. Приведенное разбиение ядра ОС на слои является достаточно условным. В реальной системе количество слоев и распределение функций между ними может быть и иным. В системах, предназначенных для аппаратных платформ одного типа, например, ОС NetWare, слой машинно-зависимых модулей обычно не выделяется, сливаясь со слоем базовых механизмов и, частично, со слоем менеджеров ресурсов. Не всегда оформляются в отдельный слой базовые механизмы – в этом случае менеджеры ресурсов не только планируют использование ресурсов, но и самостоятельно реализуют свои планы. Возможна и противоположная картина, когда ядро состоит из большего количества слоев. Например, менеджеры ресурсов, составляя определенный слой ядра, в свою очередь, могут обладать многослойной структурой. Прежде всего это относится к менеджеру ввода-вывода, нижний слой которого составляют драйверы устройств, например, драйвер жесткого диска или драйвер сетевого адаптера, а верхние слои – драйверы файловых систем или протоколов сетевых служб, имеющие дело с логической организацией информации. Способ взаимодействия слоев в реальной ОС также может отклоняться от описанной выше схемы. Для ускорения работы ядра в некоторых случаях происходит непосредственное обращение с верхнего слоя к функциям нижних слоев, минуя промежуточные. Типичным примером такого «неправильного» взаимодействия является начальная стадия обработки системного вызова. На многих аппаратных платформах для реализации системного вызова используется инструкция программного прерывания. Этим приложение фактически вызывает модуль первичной обработки прерываний, который находится в слое базовых механизмов, а уже этот модуль вызывает нужную функцию из слоя системных 32 вызовов. Сами функции системных вызовов также иногда нарушают субординацию иерархических слоев, обращаясь прямо к базовым механизмам ядра. Выбор количества слоев ядра является ответственным и сложным делом: увеличение числа слоев ведет к некоторому замедлению работы ядра за счет дополнительных накладных расходов на межслойное взаимодействие, а уменьшение числа слоев ухудшает расширяемость и логичность системы. Обычно операционные системы, прошедшие долгий путь эволюционного развития, например, многие версии UNIX, имеют неупорядоченное ядро с небольшим числом четко выделенных слоев, а у сравнительно «молодых» операционных систем, таких как Windows NT, ядро разделено на большее число слоев и их взаимодействие формализовано в гораздо большей степени. • • •••• • ••• •• •• •• ••• ••• • • • • • •• ••• • •• •• •• ••• ••• • • •• • •• • • • • • • • • • ••• • •• •• • • • • • • • • • ••• • •• •• • • • Рис. 15 Микроядерная архитектура является альтернативой классическому способу построения операционной системы. Под классической архитектурой в данном случае понимается рассмотренная выше структурная организация ОС, в соответствии с которой все основные функции операционной системы, составляющие многослойное ядро, выполняются в привилегированном режиме. При этом некоторые вспомогательные функции ОС оформляются в виде приложений и выполняются в пользовательском режиме наряду с обычными пользовательскими программами (становясь системными утилитами или обрабатывающими программами). Каждое приложение пользовательского режима работает в собственном адресном пространстве и защищено тем самым от какого-либо вмешательства других приложений. Код ядра, выполняемый в привилегированном режиме, имеет доступ к областям памяти всех приложений, но сам полно33 стью от них защищен. Приложения обращаются к ядру с запросами на выполнение системных функций. Суть микроядерной архитектуры состоит в следующем. В привилегированном режиме остается работать только очень небольшая часть ОС, называемая микроядром (рис. 15). Микроядро защищено от остальных частей ОС и приложений. В состав микроядра обычно входят машинно-зависимые модули, а также модули, выполняющие базовые (но не все!) функции ядра по управлению процессами, обработке прерываний, управлению виртуальной памятью, пересылке сообщений и управлению устройствами ввода-вывода, связанные с загрузкой или чтением регистров устройств. Набор функций микроядра обычно соответствует функциям слоя базовых механизмов обычного ядра. Такие функции операционной системы трудно, если не невозможно, выполнить в пространстве пользователя. Все остальные более высокоуровневые функции ядра оформляются в виде приложений, работающих в пользовательском режиме. Однозначного решения о том, какие из системных функций нужно оставить в привилегированном режиме, а какие перенести в пользовательский, не существует. В общем случае многие менеджеры ресурсов, являющиеся неотъемлемыми частями обычного ядра – файловая система, подсистемы управления виртуальной памятью и процессами, менеджер безопасности и т. п., – становятся «периферийными» модулями, работающими в пользовательском режиме. Работающие в пользовательском режиме менеджеры ресурсов имеют принципиальные отличия от традиционных утилит и обрабатывающих программ операционной системы, хотя при микроядерной архитектуре все эти программные компоненты также оформлены в виде приложений. Утилиты и обрабатывающие программы вызываются в основном пользователями. Ситуации, когда одному приложению требуется выполнение функции (процедуры) другого приложения, возникают крайне редко. Поэтому в операционных системах с классической архитектурой отсутствует механизм, с помощью которого одно приложение могло бы вызвать функции другого. Совсем другая ситуация возникает, когда в форме приложения оформляется часть операционной системы. По определению, основным назначением такого приложения является обслуживание запросов других приложений, например, создание процесса, выделение памяти, проверка прав доступа к ресурсу и т. д. Именно поэтому менеджеры ресурсов, вынесенные в пользовательский режим, называются серверами ОС, то есть модулями, основным назначением которых является обслуживание запросов локальных приложений и других модулей ОС. Очевидно, что для реализации микроядерной архитектуры необходимым условием является наличие в операционной системе удобного и эффективного способа вызова процедур одного процесса из другого. Поддержка такого механизма и является одной из главных задач микроядра. Схематично механизм обращения к функциям ОС, оформленным в виде серверов, выглядит следующим образом. Клиент, которым может быть либо прикладная программа, либо другой компонент ОС, запрашивает выполнение 34 некоторой функции у соответствующего сервера, посылая ему сообщение. Непосредственная передача сообщений между приложениями невозможна, так как их адресные пространства изолированы друг от друга. Микроядро, выполняющееся в привилегированном режиме, имеет доступ к адресным пространствам каждого из этих приложений и поэтому может работать в качестве посредника. Микроядро сначала передает сообщение, содержащее имя и параметры вызываемой процедуры нужному серверу, затем сервер выполняет запрошенную операцию, после чего ядро возвращает результаты клиенту с помощью другого сообщения. Таким образом, работа микроядерной операционной системы соответствует известной модели клиент-сервер, в которой роль транспортных средств выполняет микроядро. Рис. 16 На структуру ранних операционных систем, разработанных в средине 50х годов, обращалось мало внимания. Ни у кого не было опыта в разработке действительно больших программных систем, а проблемы взаимозависимости и взаимодействия сильно недооценивались. В подобных монолитных операционных системах (monolithic operating systems) почти все процедуры могли вызывать одна другую. Такое отсутствие структуры было несовместимо с расширением операционных систем. Первая версия операционной системы OS/360 была создана коллективом программистов из 5000 человек за пять лет и содержала более миллиона строк кода. В частности, были разработаны слоистые операционные системы (layered operating systems) (рис. 16,а) с иерархической организацией функций, взаимодействие в которых возможно только между функциями, находящимися на соседних уровнях. При «многослойном» подходе все уровни или большинство из них выполняются в режиме ядра, 35 «Слоистый» подход тоже не лишен своих проблем. Каждый слой обладает определенными функциональными возможностями, и значительные изменения одного из уровней могут иметь различное влияние (которое трудно предвидеть) на код в смежных слоях (как верхнем, так и нижнем). Поэтому довольно трудно реализовать согласованные версии операционной системы, имеющие на несколько функций больше или меньше, чем в базовой версии. Многочисленные взаимодействия между соседними уровнями усложняют обеспечение безопасности. Суть философии, лежащей в основе использования микроядра, заключается в том, что в ядре должны быть только самые важные функции операционной системы. Работа служб и приложений, не являющихся критическими, основана на работе микроядра, но выполняются они в пользовательском режиме. И разделение на то, что находится внутри микроядра, и то, что выносится за его пределы, зависит от архитектуры системы. В архитектуре с микроядром традиционное вертикальное расположение уровней заменяется горизонтальным (рис. 16,6). Внешние по отношению к микроядру компоненты операционной системы реализуются как обслуживающие процессы. Между собой они взаимодействуют как равноправные партнеры; обычно взаимодействие реализуется с помощью обмена сообщениями, которые передаются через микроядро. Таким образом, ядро выступает в роли посредника: оно подтверждает правильность сообщений, передает их от одного компонента другому и предоставляет доступ к аппаратному обеспечению. Кроме того, микроядро выполняет защитные функции: оно не пропускает сообщение, если такой обмен не разрешен. Достоинства архитектуры с микроядром В их число входят следующее: • единообразные интерфейсы; • расширяемость; • гибкость; • переносимость; • надежность; • поддержка распределенных систем; • поддержка объектно-ориентированных операционных систем. Использование микроядра предполагает единообразный интерфейс (uniform interface) запросов, генерируемых процессами. Процессам не нужно различать службы, выполняющиеся на уровне ядра и на пользовательском уровне, потому что ко всем этим службам осуществляется только с помощью передачи сообщений. С появлением новых аппаратных устройств или методов программирования любую операционную систему неизбежно нужно будет пополнять новыми свойствами. Архитектура с применением микроядра способствует расширяемости (extensibility) операционных систем, позволяя добавлять в них новые серви- 36 сы, а также обеспечивать множественные сервисы в одной и той же функциональной области. Таким образом, из всего разнообразия сервисов пользователь может выбрать тот, который подходит ему больше других. При добавлении нового свойства в операционную систему с архитектурой микроядра достаточно добавить или модифицировать лишь некоторые из серверов. Влияние новых или измененных серверов распространяется на ограниченное подмножество системы; кроме того, после модификации не нужно будет строить новое ядро. С расширяемостью архитектуры микроядра тесно связано такое ее свойство, как гибкость (flexibility). В операционную систему можно не только добавлять новые свойства, но и удалять из нее те, которыми она обладает. Иногда это нужно, чтобы получить более компактную и эффективную версию. Операционная система с микроядром не обязательно является маленькой. На самом деле ее структура приспособлена к добавлению разнообразных возможностей. Однако не для всех компонентов системы нужно, чтобы они обладали, например, высоким уровнем безопасности или способностью к распределенным вычислениям. Если свойства, предъявляющие значительные требования к объему памяти, будут необязательными, базовый продукт сможет привлечь более широкий круг пользователей. Микроядро способствует поддержке распределенных систем (distributed systems), к которым относятся кластеры, управляемые распределенными операционными системами. Сообщение, которое пересылается от обслуживаемого процесса обслуживающему, должно содержать в себе идентификатор запрашиваемого сервиса. Если распределенная система (т. е. кластер) сконфигурирована так, что все процессы и сервисы в ней обладают уникальными идентификаторами, то, в сущности, на уровне микроядра образуется единый образ системы. Процесс может отправлять сообщение, не зная, на какой именно машине находится сервис, к которому он обращается. Архитектура с микроядром хорошо работает в среде объектно-ориентированных операционных систем (object-oriented operating system). Объектно-ориентированный подход способствует более строгой разработке микроядра и модульных расширений операционной системы. Поэтому многие разработчики прилагают усилия для перехода к объектно-ориентированному конструированию [WAYN94b]. Одним из многообещающих подходов, в котором сочетаются архитектура с микроядром и принципы объектно-ориентированных операционных систем, является подход с использованием компонентов [MESS96]. Компоненты – это объекты с четко заданными интерфейсами, которые могут соединяться между собой, образуя программы по принципу строительных блоков. Во всех взаимодействиях между компонентами используются их интерфейсы. Разработчики других систем, таких, как Windows 2000, не полагаются целиком и полностью на объектноориентированные методы, однако при разработке микроядра они широко применяют объектно-ориентированные принципы. 37 Наиболее общим подходом к структуризации операционной системы является разделение всех ее модулей на две группы: • ядро – модули, выполняющие основные функции ОС; • модули, выполняющие вспомогательные функции ОС. Модули ядра выполняют такие базовые функции ОС, как управление процессами, памятью, устройствами ввода-вывода и т. п. Ядро составляет сердцевину операционной системы, без него ОС является полностью неработоспособной и не сможет выполнить ни одну из своих функций. В состав ядра входят функции, решающие внутрисистемные задачи организации вычислительного процесса, такие как переключение контекстов, загрузка/выгрузка страниц, обработка прерываний. Эти функции недоступны для приложений. Другой класс функций ядра служит для поддержки приложений, создавая для них так называемую прикладную программную среду. Приложения могут обращаться к ядру с запросами – системными вызовами – для выполнения тех или иных действий, например, для открытия и чтения файла, вывода графической информации на дисплей, получения системного времени и т. д. Функции ядра, которые могут вызываться приложениями, образуют интерфейс прикладного программирования – API. Функции, выполняемые модулями ядра, являются наиболее часто используемыми функциями операционной системы, поэтому скорость их выполнения определяет производительность всей системы в целом. Для обеспечения высокой скорости работы ОС все модули ядра или большая их часть постоянно находятся в оперативной памяти, то есть являются резидентными. Ядро является движущей силой всех вычислительных процессов в компьютерной системе, и крах ядра равносилен краху всей системы. Поэтому разработчики операционной системы уделяют особое внимание надежности кодов ядра, в результате процесс их отладки может растягиваться на многие месяцы. Обычно ядро оформляется в виде программного модуля некоторого специального формата, отличающегося от формата пользовательских приложений. Термин «ядро» в разных ОС трактуется по-разному. Одним из определяющих свойств ядра является работа в привилегированном режиме. Этот вопрос будет рассмотрен в следующем разделе. Остальные модули ОС выполняют весьма полезные, но менее обязательные функции. Например, к таким вспомогательным модулям могут быть отнесены программы архивирования данных на магнитной ленте, дефрагментации диска, текстового редактора. Вспомогательные модули ОС оформляются либо в виде приложений, либо в виде библиотек процедур. Поскольку некоторые компоненты ОС оформлены как обычные приложения, то есть в виде исполняемых модулей стандартного для данной ОС формата, то часто бывает очень сложно провести четкую грань между операционной системой и приложениями. Решение о том, является ли какая-либо программа частью ОС или нет, принимает производитель ОС. Среди многих факторов, способных повлиять на 38 это решение, немаловажными являются перспективы того, будет ли программа иметь массовый спрос у потенциальных пользователей данной ОС. Некоторая программа может существовать определенное время как пользовательское приложение, а потом стать частью ОС, или наоборот. Ярким примером такого изменения статуса программы является Web-браузер компании Microsoft, который сначала поставлялся как отдельное приложение, затем стал частью операционных систем Windows NT 4.0 и Windows 95/98, а сегодня существует большая вероятность того, что по решению суда этот браузер снова превратится в самостоятельное приложение. Вспомогательные модули ОС обычно подразделяются на следующие группы: • утилиты – программы, решающие отдельные задачи управления и сопровождения компьютерной системы, такие, например, как программы сжатия дисков, архивирования данных на магнитную ленту; • системные обрабатывающие программы — текстовые или графические редакторы, компиляторы, компоновщики, отладчики; • программы предоставления пользователю дополнительных услуг — специальный вариант пользовательского интерфейса, калькулятор и даже игры; • библиотеки процедур различного назначения, упрощающие разработку приложений, например библиотека математических функций, функций ввода-вывода и т. д. Как и обычные приложения, для выполнения своих задач утилиты, обрабатывающие программы и библиотеки ОС, обращаются к функциям ядра посредством системных вызовов Разделение операционной системы на ядро и модули-приложения обеспечивает легкую расширяемость ОС. Чтобы добавить новую высокоуровневую функцию, достаточно разработать новое приложение, и при этом не требуется модифицировать ответственные функции, образующие ядро системы. Однако внесение изменений в функции ядра может оказаться гораздо сложнее, и сложность эта зависит от структурной организации самого ядра. В некоторых случаях каждое исправление ядра может потребовать его полной перекомпиляции. Модули ОС, оформленные в виде утилит, системных обрабатывающих программ и библиотек, обычно загружаются в оперативную память только на время выполнения своих функций, то есть являются транзитными. Постоянно в оперативной памяти располагаются только самые необходимые коды ОС, составляющие ее ядро. Такая организация ОС экономит оперативную память компьютера. Важным свойством архитектуры ОС, основанной на ядре, является возможность защиты кодов и данных операционной системы за счет выполнения функций ядра в привилегированном режиме. 39 Рис. 17 Производительность При классической организации ОС выполнение системного вызова сопровождается двумя переключениями режимов, а при микроядерной организации — четырьмя. Таким образом, операционная система на основе микроядра при прочих равных условиях всегда будет менее производительной, чем ОС с классическим ядром. Именно по этой причине микроядерный подход не получил такого широкого распространения, которое ему предрекали. Серьезность этого недостатка хорошо иллюстрирует история развития Windows NT. В версиях 3.1 и 3.5 диспетчер окон, графическая библиотека и высокоуровневые драйверы графических устройств входили в состав сервера пользовательского режима, и вызов функций этих модулей осуществлялся в соответствии с микроядерной схемой. Однако очень скоро разработчики Windows NT поняли, что такой механизм обращений к часто используемым функциям графического интерфейса существенно замедляет работу приложений и делает данную операционную систему уязвимой в условиях острой конкуренции. В результате в версию Windows NT 4.0 были внесены существенные изменения – все перечисленные выше модули были перенесены в ядро, что отдалило эту ОС от идеальной микроядерной архитектуры, но зато резко повысило ее производительность. Этот пример иллюстрирует главную проблему, с которой сталкиваются разработчики операционной системы, решившие применить микроядерный подход: что включать в микроядро, а что выносить в пользовательское пространство. В идеальном случае микроядро может состоять только из средств 40 передачи сообщений, средств взаимодействия с аппаратурой, в том числе средств доступа к механизмам привилегированной защиты. Однако многие разработчики не всегда жестко придерживаются принципа минимизации функций ядра, часто жертвуя этим ради повышения производительности. В результате реализации ОС образуют некоторый спектр, на одном краю которого находятся системы с минимально возможным микроядром, а на другом — системы, подобные Windows NT, в которых микроядро выполняет достаточно большой объем функций. Рис. 18 На рис. 18, представлена общая структура операционной системы W2K. Модульная структура этой системы делает ее довольно гибкой. Она в состоянии работать на самых разных аппаратных платформах и поддерживать приложения, написанные для разных операционных систем. К моменту написания этой книги операционная система W2K была реализована только на аппаратной платформе Pentium/x86. Как и прочие операционные системы, W2K различает прикладные программы и программы операционной системы. К последним относятся исполняющая система, микроядро, драйверы устройств и уровень аппаратных абстракций (hardware abstraction layer – HAL), которые выполняются в режиме ядра. Программы, выполняющиеся в этом режиме, имеют доступ к системным дан- 41 ным и к аппаратному обеспечению. Остальные программы, работающие в пользовательском режиме, имеют ограниченный доступ к системным данным. Организация операционной системы В операционной системе W2K трудно однозначно выделить микроядро. Вместо этого W2K имеет структуру, которую фирма Microsoft называет модифицированной архитектурой микроядра. Как и обычной архитектуре микроядра, операционной системе W2K присуще четкое разделение на модули. Каждая функция системы управляется только одним компонентом операционной системы остальные ее части и все приложения обращаются к этой функции через стандартный интерфейс. Доступ к основным системным данным можно получить только через определенные функции. В принципе любой модуль можно удалить, обновить или заменить, не переписывая всю систему или стандартный интерфейс прикладного программирования (application program interface — API). Однако в отличие от систем с четко выделенный микроядром, у W2K многие функции системы которые не входят в микроядро, выполняются в режиме ядра, что сделано с целью повышения производительности. Разработчики системы W2K о6наружили, что использование традиционного подхода с выделением микроядра приводит к тому, что многие функции, не входящие в микроядро, требуют наличия нескольких переключателей процессов или потоков, переключателей режимов, а также используют дополнительные буферы памяти. Одной из целей создателей операционной системы W2K была ее переносимость, т. е. возможность ее использования на самых разнообразных аппаратных платформах. Для достижения этой цели большая часть исполняющей системы W2K рассматривает лежащее в основе аппаратное обеспечение с одной и той же точки зрения, используя представленную ниже структуру уровней. • Уровень аппаратных абстракций. На этом уровне формируется отображение между общими командами и ответными сигналами аппаратного обеспечения, и таковыми для конкретной платформы. Этот уровень отделяет операционную систему от особенностей используемой аппаратной платформы, благодаря чему системная шина, контроллер прямого доступа к памяти, контроллер прерываний, системные таймеры и память выглядят с точки зрения ядра одинаково. Кроме того, на этом уровне поддерживается симметричная многопроцессорность, принцип работы которой объясняется далее. • Микроядро. В микроядро входят наиболее часто используемые компоненты операционной системы. Ядро отвечает за распределение ресурсов между процессами, их переключение и синхронизацию. В отличие от остальной части исполняющей системы и от процессов, исполняемых на уровне пользователя, код микроядра не разделяется на потоки. Таким образом, это единственная часть опе- 42 рационной системы, которая не может быть вытеснена или выгружена на диск. • Драйверы устройств. К ним относится как файловая система, так и драйверы аппаратных устройств, которые преобразуют поступившие от пользователя вызовы функций ввода-вывода в запросы для конкретных устройств. Исполнительная система W2K. Включает модули, обеспечивающие поддержку ее функций и предоставляющие работающим в пользовательском режиме программам соответствующий API, Ниже приведено краткое описание каждого из модулей исполнительной системы. • Диспетчер ввода-вывода. Поддерживает доступность для приложений устройств ввода-вывода. Кроме того, этот диспетчер отвечает за координацию работы драйверов устройств, выполняющих дальнейшую обработку. Диспетчер ввода-вывода реализует все API ввода-вывода W2K и (с помощью диспетчера объектов) следит за безопасностью и именованием устройств и файловых систем • Диспетчер объектов. Создает и удаляет объекты и абстрактные типы данных исполнительной системы W2K, а также управляет ими. Эти объекты и абстрактные типы данных используются для представления таких ресурсов, как процессы, потоки и объекты синхронизации. Диспетчер объектов обеспечивает выполнение стандартных правил поддержки объектов, именования и безопасности. Кроме того, этот диспетчер создает дескрипторы объектов, в которых содержится информация о правах доступа и указатель на объект-Объекты операционной системы W2K обсуждаются немного позже. • Монитор безопасности обращений. Обеспечивает выполнение правил прав доступа и аудита. Объектно-ориентированная модель операционной системы W2K позволяет сформировать согласованный и единообразный взгляд на безопасность фундаментальных составляющих исполняющей системы. Так для авторизации доступа и аудита всех защищенных объектов, включая файлы, процессы, адресные пространства и устройства ввода-вывода, операционная система W2K использует одни и те же служебные программы Безопасность W2K обсуждается в главе 15, «Безопасность». • Диспетчер процессов и потоков. Создает и удаляет объекты, а также следит за процессами и потоками. 43 • Средства локального вызова процедур. В рамках одной системы устанавливают взаимосвязь между приложениями и исполняющими подсистемами по модели клиент/сервер. Этот модуль похож на средства удаленного вызова процедур, который используется при распределенной обработке данных. • Диспетчер виртуальной памяти. Отображает виртуальные адреса адресного пространства процессов на физические страницы памяти компьютера. • Диспетчер кэша. Повышает производительность файлового вводавывода путем хранения в основной памяти тех данных с диска, к которым недавно производилось обращение. Кроме того, обеспечивает отложенную запись на диск, некоторое время храня в памяти обновления дисковых файлов. • Графические модули. Создают оконный экранный интерфейс и управляют графическими устройствами. Пользовательские процессы Операционная система W2K поддерживает четыре основных типа пользовательских процессов. • Специальные процессы системной поддержки. К таким процессам относятся служебные программы, которые не вошли в операционную систему W2K, например процесс входа в систему и диспетчер сессий. • Серверные процессы. Другие сервисы W2K, такие, как журнал регистрации событий. • Подсистемы среды. Предоставляют приложениям пользователя сервисы W2K, обеспечивая таким образом среду операционной системы. Поддерживаются такие подсистемы, как Win32, POSIX и OS/2. В каждую подсистему среды входят динамически компонуемые библиотеки, преобразующие вызовы приложений пользователя в вызовы операционной системы W2K. • Приложения пользователя. Могут быть одного из пяти типов: Win32, POSIX, OS/27, Windows 3.1 или MS-DOS. Операционная система W2K поддерживает приложения, написанные для W2K, Windows 98 и нескольких других операционных систем. Эта поддержка 44 обеспечивается с помощью единой и компактной исполнительной системы через защитные подсистемы среды, к которым относятся части операционной систем W2K, взаимодействующие с конечным пользователем. Каждая из подсистем является отдельным процессом, а исполнительная система защищает адресное пространство этих подсистем от вмешательства других подсистем и приложений. Защищенная подсистема предоставляет пользователю графический интерфейс или интерфейс командной строки, который определяет внешний и наполнение операционной системы для конечного пользователя. Кроме того каждая защищенная подсистема обеспечивает свой API для каждой из oпepaционных сред. Это означает, что приложения, разработанные для определенной операционной среды, могут быть запущены W2K в неизменном виде, так как им будет предоставлен тот интерфейс операционной системы, для которого они были созданы. Так, 16-битовые приложения для операционной системы OS/2 можно запускать в операционной системе W2K без каких-либо изменений. Более того, поскольку W2K разработана независимой от платформы (что обеспечивается наличием уровня аппаратных абстракций), защищенные подсистемы и приложения, которые они поддерживают, должны сравнительно легко переноситься с одной аппаратной платформы на другую. Во многих случаях для этого нужна, лишь обычная перекомпиляция. Наиболее важной из подсистем является Win32. Win32 – это API, который реализован как для W2K, так и для Windows 98. Некоторые возможности подсистемы Win32 недоступны в Windows 98, но все возможности, реализованные в Windows 98, идентичны возможностям, имеющимся в W2K Подсистемы окружения Операционной системой Windows 2000 поддерживаются три различных документированных интерфейса прикладного программирования APL Win32, POSIX и OS/2. У каждого из этих интерфейсов есть список библиотечных вызовов, которые могут использовать программисты. Работа библиотек DLL (Dynamic Link Library – динамически подключаемая библиотека) и подсистем окружения заключается в том, чтобы реализовать функциональные возможности опубликованного интерфейса, тем самым скрывая истинный интерфейс системных вызовов от прикладных программ. В частности, интерфейс Win32 является официальным интерфейсом для операционных систем Windows 2000, Windows NT, Windows 95/98/Me и, в некоторой степени, для Windows СЕ. При использовании библиотеки DLL и подсистемы окружения Win32 программа может быть написана в. соответствии со спецификацией Win32, в результате чего она сможет без каких либо изменений работать на всех этих версиях Windows, несмотря на то, что сами системные вызовы в различных системах различны. Рассмотрим способ реализации этих интерфейсов на примере Win32. Программа, пользующаяся интерфейсом Win32, как правило, состоит из большого количества обращений к функциям Win32 API, например CreateWlndow, DrawMenuBar и OpenSemaphore. Существуют тысячи подобных вызовов, и 45 большинство программ использует значительное их количество. Один из возможных способов реализации заключается в статическом связывании каждой программы, использующей интерфейс Win32, со всеми библиотечными процедурами, которыми она пользуется. При таком подходе каждая двоичная программа будет содержать копию всех используемых ею процедур в своем исполняемом двоичном файле. Недостаток такого подхода заключается в том, что при этом расходуется много памяти, если пользователь одновременно откроет несколько программ, использующих одни и те же библиотечные процедуры. Например, программы Word, Excel и PowerPoint используют абсолютно одинаковые процедуры для открытия диалоговых окон, рисования окон, отображения меню, работы с буфером обмена и т. д. Поэтому, если одновременно открыть все эти программы, при такой реализации программ в памяти будут находиться три (идентичные) копии каждой библиотечной процедуры. Чтобы избежать подобной проблемы, все версии Windows поддерживают динамические библиотеки, называемые DLL (Dynamic-Link Library – динамически подсоединяемая библиотека). Каждая динамическая библиотека содержит набор тесно связанных библиотечных процедур и все их структуры данных в одном файле, как правило (но не всегда), с расширением .dll. Когда приложение компонуется, компоновщик видит, что некоторые библиотечные процедуры принадлежат к динамическим библиотекам, и записывает эту информацию в заголовок исполняемого файла. Обращения к процедурам динамических библиотек производятся не напрямую, а при помощи вектора передачи в адресном пространстве вызывающего процесса. Изначально этот вектор заполнен нулями, так как адреса вызываемых процедур еще неизвестны. При запуске прикладного процесса все требуемые динамические библиотеки обнаруживаются (на диске или в памяти) и отображаются на виртуальное адресное пространство процесса. Затем вектор передачи заполняется верными адресами, что позволяет вызывать библиотечные процедуры через этот вектор с незначительной потерей производительности. Выигрыш такой схемы заключается в том, что при запуске нескольких приложений, использующих одну и ту же динамическую библиотеку, в физической памяти требуется только одна копия текста DLL (но каждый процесс получает свою собственную копию приватных статических данных в DLL). В операционной системе Windows 2000 динамические библиотеки используются очень активно для всех аспектов системы. 46 Различные маршруты выполнения вызовов Win32 API Рис. 19 Теперь мы достаточно подготовились к тому, чтобы понять, как реализован интерфейс Win32, а также другие интерфейсы. Каждый пользовательский процесс, как правило, связан с несколькими динамическими библиотеками, совместно реализующими интерфейс Win32. Чтобы обратиться к вызову API, вызывается одна из процедур в DLL (шаг 1 на рис. 19). Дальнейшие действия зависят от вызова Win32 API. Различные вызовы реализованы по-разному. В некоторых случаях динамические библиотеки обращаются к другой динамической библиотеке (ntdll.dll), которая, в свою очередь, обращается к ядру операционной системы. Этот путь показан на рисунке как шаги 2а и 3а. Динамическая библиотека может также выполнить всю работу самостоятельно, совсем не обращаясь к системным вызовам. Для других вызовов Win32 API выбирается другой маршрут, а именно: сначала процессу подсистемы Win32 (csrss.exe) посылается сообщение, выполняющее некоторую работу и обращающееся к системному вызову (шаги 2б, 3б и 4б). При этом в некоторых случаях подсистема также выполняет всю работу в пространстве пользователя и немедленно возвращает управление. Передача сообщения между прикладным процессом и процессом подсистемы Win32 была старательно оптимизирована по времени, для чего был использован специальный механизм вызова локальной процедуры. В первой версии Windows NT практически все вызовы Win32 API выбирали маршрут 2б, 3б, 4б, так как большая часть операционной системы (например, графика) была помещена в пространство пользователя. Однако, начиная с версии NT 4.0, для увеличения производительности большая часть кода была 47 перенесена в ядро (в драйвер Win32/GDI на рис. 19). В Windows 2000 только небольшое количество вызовов Win32 API, например, вызовы для создания процесса или потока, идут по длинному пути. Остальные вызовы выполняются напрямую, минуя подсистему окружения Win32. Следует также сказать, что на рис. 19 показаны три наиболее важные DLL, но они не являются единственными динамическими библиотеками в системе. В каталоге \winnt\system32 есть более 800 отдельных файлов DLL общим объемом в 130 Мбайт. Количество содержащихся в них вызовов API превышает 13 000. (В конце концов, 29 млн строк исходного текста должны были где-то храниться после компиляции). Для каждого файла приведено количество экспортируемых функций (то есть видимых за пределами файла). Функциональность подсистемы OS/2 ограничена практически в той же степени, что и функциональность подсистемы POSIX. Подсистема OS/2 также не поддерживает графические приложения. На практике она тоже полностью бесполезна. Таким образом, оригинальная идея наличия интерфейсов нескольких операционных систем, реализованных различными процессами в пространстве пользователя, окончилась ничем. Осталась лишь полная реализация интерфейса Win32 в режиме ядра. Программный интерфейс Win32 API Рис. 20 48 Как и в других операционных системах, в Windows 2000 есть свой набор системных вызовов, которые она может выполнять. Однако корпорация Microsoft никогда не публиковала список системных вызовов Windows, кроме того, она постоянно меняет их от одного выпуска к другому. Вместо этого корпорация Microsoft определила набор функциональных вызовов, называемый Win32 API (Win32 Application Programming Interface – интерфейс прикладного программирования). Эти вызовы опубликованы и полностью документированы. Они представляют собой библиотечные процедуры, которые либо обращаются к системным вызовам, чтобы выполнить требуемую работу, либо, в некоторых случаях, выполняют работу прямо в пространстве пользователя. Существующие вызовы Win32 API не изменяются с новыми выпусками системы Windows, хотя часто добавляются новые вызовы Win32 API. Двоичные программы для процессоров Intel x86, строго придерживающиеся интерфейса Win32 API, будут без каких-либо изменений работать на всех версиях Windows, начиная с Windows 95. Как показано на рис. 20, для операционной системы Windows 3.1 требуется дополнительная библиотека, преобразующая подмножество 32-разрядных вызовов Win32 API в 16-разрядные системные вызовы, но для остальных систем никакой адаптации не требуется. Следует отметить, что в операционной системе Windows 2000 к интерфейсу Win32 API добавлено значительное количество новых функций, поэтому в ней есть дополнительные вызовы API, не включенные в старые версии Win32, которые не будут работать на старых версиях Windows. Философия Win32 API полностью отлична от философии UNIX. В операционной системе UNIX все системные вызовы опубликованы и формируют минимальный интерфейс: удаление даже одного из них приведет к снижению функциональности операционной системы. Философия Win32 заключается в предоставлении всеобъемлющего интерфейса, часто с возможностью выполнить одно и то же тремя или четырьмя способами, включающего множество функций (например, процедур). Эти функции, очевидно, не должны быть (и не являются) системными вызовами, как, например, вызов API для копирования целого файла. Трансляция библиотек Выходом в таких случаях является использование так называемых прикладных программных сред. Одной из составляющих, формирующих прикладную программную среду, является набор функций интерфейса прикладного программирования API, которые операционная система предоставляет своим приложениям. Для сокращения времени на выполнение чужих программ прикладные среды имитируют обращения к библиотечным функциям. Эффективность этого подхода связана с тем, что большинство сегодняшних программ работают под управлением GUI (графических интерфейсов пользователя) типа Windows, Mac или UNIX Motif, при этом приложения тратят большую часть времени, производя некоторые хорошо предсказуемые действия. Они непрерывно выполняют вызовы библиотек GUI для манипулирования 49 окнами и для других связанных с GUI действий. Сегодня в типичных программах 60-80% времени тратится на выполнение функций GUI и других библиотечных вызовов ОС. Именно это свойство приложений позволяет прикладным средам компенсировать большие затраты времени, потраченные на покомандное эмулирование программы. Тщательно спроектированная программная прикладная среда имеет в своем составе библиотеки, имитирующие внутренние библиотеки GUI, но написанные на «родном» коде. Таким образом достигается существенное ускорение выполнения программ с API другой операционной системы. Иногда такой подход называют трансляцией для того, чтобы отличать его от более медленного процесса эмулирования кода по одной команде за раз. Например, для Windows-программы, работающей на Macintosh, при интерпретации команд процессора Intel 80x86 производительность может быть очень низкой. Но, когда производится вызов функции GUI открытия окна, модуль ОС, реализующий прикладную среду Windows, может перехватить этот вызов и перенаправить его на перекомпилированную для процессора Motorola 680x0 подпрограмму открытия окна. В результате, на таких участках кода скорость работы программы может достичь (а, возможно, и превзойти) скорость работы на своем «родном» процессоре. Чтобы программа, написанная для одной ОС, могла быть выполнена в рамках другой ОС, недостаточно лишь обеспечить совместимость API. Концепции, положенные в основу разных ОС, могут входить в противоречие друг с другом. Например, в одной операционной системе приложению может быть разрешено непосредственно управлять устройствами ввода-вывода, в другой эти действия являются прерогативой ОС. Каждая операционная система имеет свои собственные механизмы защиты ресурсов, свои алгоритмы обработки ошибок и исключительных ситуаций, особую структуру процесса и схему управления памятью, свою семантику доступа к файлам и графический пользовательский интерфейс. Для обеспечения совместимости необходимо организовать бесконфликтное сосуществование в рамках одной ОС нескольких способов управления ресурсами компьютера. 50 Рис. 21 Еще один способ построения множественных прикладных сред основан на микроядерном подходе. При этом очень важно отделить базовые, общие для всех прикладных сред, механизмы операционной системы от специфических для каждой из прикладных сред высокоуровневых функций, решающих стратегические задачи. В соответствии с микроядерной архитектурой все функции ОС реализуются микроядром и серверами пользовательского режима. Важно, что каждая прикладная среда оформляется в виде отдельного сервера пользовательского режима и не включает базовых механизмов (рис. 21). Приложения, используя API, обращаются с системными вызовами к соответствующей прикладной среде через микроядро. Прикладная среда обрабатывает запрос, выполняет его (возможно, обращаясь для этого за помощью к базовым функциям микроядра) и отсылает приложению результат. В ходе выполнения запроса прикладной среде приходится, в свою очередь, обращаться к базовым механизмам ОС, реализуемым микроядром и другими серверами ОС. Такому подходу к конструированию множественных прикладных сред присущи все достоинства и недостатки микроядерной архитектуры, в частности: • очень просто можно добавлять и исключать прикладные среды, что является следствием хорошей расширяемости микроядерных ОС; • надежность и стабильность выражаются в том, что при отказе одной из прикладных сред все остальные сохраняют работоспособность; 51 • низкая производительность микроядерных ОС сказывается на скорости работы прикладных сред, а значит, и на скорости выполнения приложений. Создание в рамках одной операционной системы нескольких прикладных сред для выполнения приложений различных ОС представляет собой путь, который позволяет иметь единственную версию программы и переносить ее между операционными системами. Множественные прикладные среды обеспечивают совместимость на двоичном уровне данной ОС с приложениями, написанными для других ОС. В результате пользователи получают большую свободу выбора операционных систем и более легкий доступ к качественному программному обеспечению. Процессы и потоки Под процессом понимается программа в стадии выполнения. Процесс можно рассматривать также как единицу работы для процессора. Для современных типов процессоров существует и более мелкая единица работы поток или нить. Другими словами процесс может породить один и более потоков. Итак, в чем же состоят принципиальные отличия в понятиях «процесс» и «поток»? Очевидно, что любая работа вычислительной системы заключается в выполнении некоторой программы. Поэтому и с процессом, и с потоком связывается определенный программный код, который для этих целей оформляется в виде исполняемого модуля. Чтобы этот программный код мог быть выполнен, его необходимо загрузить в оперативную память, возможно, выделить некоторое место на диске для хранения данных, предоставить доступ к устройствам ввода-вывода, например к последовательному порту для получения данных по подключенному к этому порту модему; и т. д. В ходе выполнения программе может также понадобиться доступ к информационным ресурсам, например файлам, портам TCP/UPD, семафорам. И, конечно же, невозможно выполнение программы без предоставления ей процессорного времени, то есть времени, в течение которого процессор выполняет коды данной программы. В операционных системах, где существуют и процессы, и потоки, процесс рассматривается операционной системой как заявка на потребление всех видов ресурсов, кроме одного – процессорного времени. Этот последний важнейший ресурс распределяется операционной системой между другими единицами работы – потоками, которые и получили свое название благодаря тому, что они представляют собой последовательности (потоки выполнения) команд. В простейшем случае процесс состоит из одного потока, и именно таким образом трактовалось понятие «процесс» до середины 80-х годов (например, в ранних версиях UNIX) и в таком же виде оно сохранилось в некоторых современных ОС. В таких системах понятие «поток» полностью поглощается понятием «процесс», то есть остается только одна единица работы и потребления ресурсов – процесс. 52 Потоки возникли в операционных системах как средство распараллеливания вычислений. Конечно, задача распараллеливания вычислений в рамках одного приложения может быть решена и традиционными способами. Во-первых, прикладной программист может взять на себя сложную задачу организации параллелизма, выделив в приложении некоторую подпрограмму - диспетчер, которая периодически передает управление той или иной ветви вычислений. При этом программа получается логически весьма запутанной, с многочисленными передачами управления, что существенно затрудняет ее отладку и модификацию. Во-вторых, решением является создание для одного приложения нескольких процессов для каждой из параллельных работ. Однако использование для создания процессов стандартных средств ОС не позволяет учесть тот факт, что эти процессы решают единую задачу, а значит, имеют много общего между собой – они могут работать с одними и теми же данными, использовать один и тот же кодовый сегмент, наделяться одними и теми же правами доступа к ресурсам вычислительной системы. Так, если в примере с сервером баз данных создавать отдельные процессы для каждого запроса, поступающего из сети, то все процессы будут выполнять один и тот же программный код и выполнять поиск в записях, общих для всех процессов файлов данных. А операционная система при таком подходе будет рассматривать эти процессы наравне со всеми остальными процессами и с помощью универсальных механизмов обеспечивать их изоляцию друг от друга. В данном случае все эти достаточно громоздкие механизмы используются явно не по назначению, выполняя не только бесполезную, но и вредную работу, затрудняющую обмен данными между различными частями приложения. Кроме того, на создание каждого процесса ОС тратит определенные системные ресурсы, которые в данном случае неоправданно дублируются – каждому процессу выделяются собственное виртуальное адресное пространство, физическая память, закрепляются устройства ввода-вывода и т. п. Из всего вышеизложенного, следует, что в операционной системе наряду с процессами нужен другой механизм распараллеливания вычислений, который учитывал бы тесные связи между отдельными ветвями вычислений одного и того же приложения. Для этих целей современные ОС предлагают механизм многопоточной обработки (multithreading). При этом вводится новая единица работы – поток выполнения, а понятие «процесс» в значительной степени меняет смысл. Понятию «поток» соответствует последовательный переход процессора от одной команды программы к другой. ОС распределяет процессорное время между потоками. Процессу ОС назначает адресное пространство и набор ресурсов, которые совместно используются всеми его потоками. 53 • • • • •••• • • • •• •• • • • • ••• · , , • • • • •• • •• •• •• • • • • • • •••• • • • • •• • ••• •• • •• • • • • • • •••• 1. 2. 1. ( ) ( ) 2. 3. 3. 4. ( ) ( ) 4. Рис. 22 Создание процессов Когда операционная система собирается добавить новый процесс к тем, которые уже состоят на учете, она создает структуры данных, использующиеся при управлении этим процессом и размещает его адресное пространство в основной памяти. С помощью этих действий и создается новый процесс. В среде пакетной обработки процесс создается в ответ на поступление задания; в интерактивной среде процесс создается при попытке нового пользователя войти в систему. В обоих случаях ответственность за создание нового процесса лежит на операционной системе. Кроме того, операционная система может создавать процесс по требованию приложения. Например, если пользователь отправляет запрос на распечатку файла, операционная система может создать процесс, управляющий печатью. Затем процесс, производивший запрос, может продолжить свою работу, независимо от того, сколько времени понадобится для печати. Новое пакетное задание В операционную систему для обработки поступает управляющий поток пакетных заданий (обычно с ленты или с диска). Готовясь принять на обработку новое задание, операционная система считывает очередную последовательность команд управления заданиями. Вход в систему в интерактивном режиме В систему с терминала входит новый пользователь. 54 Создание операционной системой процесса, необходимого для работы каких-либо служб Операционная система может создать процесс для выполнения некоторой функции, которая требуется для программы пользователя. При этом пользователь не должен ждать, пока закончится ее выполнение (как в примере, в котором создавался процесс управления печатью) Порождение одного процесса другим С целью структуризации программы или использования возможностей параллельных вычислений программа может создавать другие процессы Традиционно операционная система создает все процессы незаметно для пользователя или приложения; такой способ принят во многих современных операционных системах. Однако иногда требуется, чтобы один процесс мог послужить причиной создания другого процесса. Например, процесс приложения может сгенерировать другой процесс, который будет получать данные от первого процесса и приводить их к виду, удобному для дальнейшего анализа. Новый процесс будет работать параллельно с приложением и время от времени активизироваться для получения новых данных. Такая организация может быть очень полезна для структурирования приложений. В качестве другого примера можно привести ситуацию, в которой процесс-сервер (например, сервер печати или файловый сервер) может генерировать новый процесс для каждого обрабатываемого им запроса. Создание операционной системой процесса по явному запросу другого процесса называется порождением процесса (process spawning). Когда один процесс порождает другой, то порождающий процесс называется родительским, или предком (parent), а порождаемый процесс – дочерним, или потомком (child). Обычно «родственные» процессы обмениваются между собой информацией и взаимодействуют друг с другом. Организация такого взаимодействия является достаточно трудной задачей для программиста. Завершение процессов В любой компьютерной системе должны быть средства, позволяющие определить, закончилось выполнение процесса или нет. Пакетное задание должно включать в себя команду типа halt (останов) или какой-то явный вызов службы операционной системы, приводящий к завершению процесса. В первом случае генерируется прерывание для извещения операционной системы о завершении процесса. Например, в системе с разделением времени процесс пользователя должен быть завершен, когда пользователь выходит из системы или выключает терминал. На персональном компьютере или рабочей станции пользователь может выйти из приложения (например, закрыть программу обработки текста или электронную таблицу). Все эти действия в конечном счете приведут 55 к тому, что будет вызвана служба операционной системы, завершающая процесс. Обычное завершение Процесс вызывает службу операционной системы, чтобы сообщить, что он завершил свою работу Превышение лимита отведенного программе времени Общее время выполнения процесса превышает заданное предельное значение. Это время может измеряться несколькими способами. Одним из них является учет полного времени, затраченного на выполнение («по настенным часам»); при выполнении интерактивного процесса время можно отсчитывать с момента последнего ввода данных пользователем. Недостаточный объем памяти Для работы процесса требуется больше памяти, чем имеется в системе. Выход за пределы отведенной области памяти Процесс пытается получить доступ к ячейке памяти, к которой у него нет прав доступа. Ошибка защиты Процесс пытается использовать недоступный для него ресурс или файл, или пытается сделать это недопустимым образом, например, производит попытку записи в файл, открытый только для чтения. Арифметическая ошибка Процесс пытается выполнить запрещенную арифметическую операцию, например, деление на ноль, или пытается использовать число, превышающее возможности аппаратного обеспечения. Излишнее ожидание Процесс ждет наступления определенного события дольше, чем задано в параметрах системы. Ошибка ввода-вывода Во время ввода или вывода происходит ошибка. Например, не удается найти нужный файл или выполнить чтение или запись за максимально возможное количество попыток (когда, например, на магнитном носителе попался дефектный участок) или производится попытка выполнить недопустимую операцию (например, чтение с печатающего устройства). 56 Неверная команда Процесс пытается выполнить несуществующую команду (часто это бывает, если процесс переходит в область данных и пытается интерпретировать их как команду). Команда с недоступными привилегиями Процесс пытается использовать команду, зарезервированную для операционной системы. Неправильное использование данных Часть данных принадлежит не к тому типу или не инициализирована. Вмешательство оператора или операционной системы По какой-либо причине операционная система может завершить процесс (например, в случае взаимоблокировки). Завершение родительского процесса При завершении родительского процесса операционная система может автоматически прекращать все его дочерние процессы. Запрос со стороны родительского процесса Обычно родительский процесс имеет право прекращать любой из своих дочерних процессов. Наконец, в некоторых операционных системах процесс может быть завершен процессом, который его породил, а также при завершении самого родительского процесса. Снисходительная операционная система в некоторых случаях может позволять пользователю восстановить работу процесса, в котором возникли условия отказа, не прекращая его. Например, если пользователь запросил доступ к файлу, к которому у него нет доступа, операционная система может просто сообщить пользователю, что ему отказано в доступе, предоставив процессу возможность продолжать свою работу. 57 • • • • •••• • • • •• •• • • • • ••• .), • • •• • - ( , - . . • •• ••• • • • • •• • • • • •• • • • • • • •••• • • • • •• •• • Ý Ý Ý Ý ( Ý - , ) ( ’ ù, ) Ý -’ ù, . . Рис. 23 Планирование процессов включает в себя решение следующих задач: • определение момента времени для смены выполняемого процесса; • выбор процесса на выполнение из очереди готовых процессов; • переключение контекстов «старого» и «нового» процессов. Первые две задачи решаются программными средствами, а последняя в значительной степени аппаратно. Существует множество различных алгоритмов планирования процессов, по разному решающих вышеперечисленные задачи, преследующих различные цели и обеспечивающих различное качество мультипрограммирования. Среди этого множества алгоритмов рассмотрим подробнее две группы наиболее часто встречающихся алгоритмов: алгоритмы, основанные на квантовании, и алгоритмы, основанные на приоритетах. В соответствии с алгоритмами, основанными на квантовании, смена активного процесса происходит, если, • процесс завершился и покинул систему; • произошла ошибка; • процесс перешел в состояние ОЖИДАНИЕ; • исчерпан квант процессорного времени, отведенный данному процессу. Процесс, который исчерпал свой квант, переводится в состояние ГОТОВНОСТЬ и ожидает, когда ему будет предоставлен новый квант процессорного времени, а на выполнение в соответствии с определенным правилом 58 выбирается новый процесс из очереди готовых. Таким образом, ни один процесс не занимает процессор надолго, поэтому квантование широко используется в системах разделения времени. Граф состояний процесса, изображенный на следующем рисунке, соответствует алгоритму планирования, основанному на квантовании. Кванты, выделяемые процессам, могут быть одинаковыми для всех процессов или различными. Кванты, выделяемые одному процессу, могут быть фиксированной величины или могут изменяться в разные периоды жизни процесса. Процессы, которые не полностью использовали выделенный им квант (например, из-за ухода на выполнение операций ввода-вывода), могут получить или не получить компенсацию в виде привилегий при последующем обслуживании. По разному может быть организована очередь готовых процессов: циклически, по правилу «первый пришел – первый обслужился» (FIFO) или по правилу «последний пришел – первый обслужился» (LIFO). Другая группа алгоритмов использует понятие «приоритет» процесса. Приоритет − это число, характеризующее степень привилегированности процесса при использовании ресурсов вычислительной машины, в частности, процессорного времени: чем выше приоритет, тем выше привилегии. Приоритет может выражаться целыми или дробными, положительным или отрицательным значением. Чем выше привилегии процесса, тем меньше времени он будет проводить в очередях. Приоритет может назначаться директивно администратором системы в зависимости от важности работы или внесенной платы, либо вычисляться самой ОС по определенным правилам, он может оставаться фиксированным на протяжении всей жизни процесса либо изменяться во времени в соответствии с некоторым законом. В последнем случае приоритеты называются динамическими. Существует две разновидности приоритетных алгоритмов: алгоритмы, использующие относительные приоритеты, и алгоритмы, использующие абсолютные приоритеты. В обоих случаях выбор процесса на выполнение из очереди готовых осуществляется одинаково: выбирается процесс, имеющий наивысший приоритет. По-разному решается проблема определения момента смены активного процесса. В системах с относительными приоритетами активный процесс выполняется до тех пор, пока он сам не покинет процессор, перейдя в состояние ОЖИДАНИЕ (или же произойдет ошибка, или процесс завершится). В системах с абсолютными приоритетами выполнение активного процесса прерывается еще при одном условии: если в очереди готовых процессов появился процесс, приоритет которого выше приоритета активного процесса. В этом случае прерванный процесс переходит в состояние готовности. Во многих операционных системах алгоритмы планирования построены с использованием как квантования, так и приоритетов. Например, в основе планирования лежит квантование, но величина кванта и/или порядок выбора процесса из очереди готовых определяется приоритетами процессов. 59 Вытесняющие и невытесняющие алгоритмы планирования Существуют два основных типа процедур планирования процессов − вытесняющие (preemptive) иневытесняющие (non-preemptive). Non-preemptive multitasking − не вытесняющая многозадачность − это способ планирования процессов, при котором активный процесс выполняется до тех пор, пока он сам, по собственной инициативе, не отдаст управление планировщику операционной системы для того, чтобы тот выбрал из очереди другой, готовый к выполнению процесс. Preemptive multitasking − вытесняющая многозадачность − это такой способ, при котором решение о переключении процессора с выполнения одного процесса на выполнение другого процесса принимается планировщиком операционной системы, а не самой активной задачей. Понятия preemptive и non-preemptive иногда отождествляются с понятиями приоритетных и бесприоритетных дисциплин, что совершенно неверно, а также с понятиями абсолютных и относительных приоритетов, что неверно отчасти. Вытесняющая и невытесняющая многозадачность − это более широкие понятия, чем типы приоритетности. Приоритеты задач могут как использоваться, так и не использоваться и при вытесняющих, и при невытесняющих способах планирования. Так в случае использования приоритетов дисциплина относительных приоритетов может быть отнесена к классу систем с невытесняющей многозадачностью, а дисциплина абсолютных приоритетов − к классу систем с вытесняющей многозадачностью. А бесприоритетная дисциплина планирования, основанная на выделении равных квантов времени для всех задач, относится к вытесняющим алгоритмам. Основным различием между preemptive и non-preemptive вариантами многозадачности является степень централизации механизма планирования задач. При вытесняющей многозадачности механизм планирования задач целиком сосредоточен в операционной системе, и программист пишет свое приложение, не заботясь о том, что оно будет выполняться параллельно с другими задачами. При этом операционная система выполняет следующие функции: определяет момент снятия с выполнения активной задачи, запоминает ее контекст, выбирает из очереди готовых задач следующую и запускает ее на выполнение, загружая ее контекст. При невытесняющей многозадачности механизм планирования распределен между системой и прикладными программами. Прикладная программа, получив управление от операционной системы, сама определяет момент завершения своей очередной итерации и передает управление ОС с помощью какоголибо системного вызова, а ОС формирует очереди задач и выбирает в соответствии с некоторым алгоритмом (например, с учетом приоритетов) следующую задачу на выполнение. Такой механизм создает проблемы как для пользователей, так и для разработчиков. Для пользователей это означает, что управление системой теряется на произвольный период времени, который определяется приложением (а не поль60 зователем). Если приложение тратит слишком много времени на выполнение какой-либо работы, например, на форматирование диска, пользователь не может переключиться с этой задачи на другую задачу, например, на текстовый редактор, в то время как форматирование продолжалось бы в фоновом режиме. Эта ситуация нежелательна, так как пользователи обычно не хотят долго ждать, когда машина завершит свою задачу. Поэтому разработчики приложений для non-preemptive операционной среды, возлагая на себя функции планировщика, должны создавать приложения так, чтобы они выполняли свои задачи небольшими частями. Например, программа форматирования может отформатировать одну дорожку дискеты и вернуть управление системе. После выполнения других задач система возвратит управление программе форматирования, чтобы та отформатировала следующую дорожку. Подобный метод разделения времени между задачами работает, но он существенно затрудняет разработку программ и предъявляет повышенные требования к квалификации программиста. Программист должен обеспечить «дружественное» отношение своей программы к другим выполняемым одновременно с ней программам, достаточно часто отдавая им управление. Крайним проявлением «недружественности» приложения является его зависание, которое приводит к общему краху системы. В системах с вытесняющей многозадачностью такие ситуации, как правило, исключены, так как центральный планирующий механизм снимет зависшую задачу с выполнения. Однако распределение функций планировщика между системой и приложениями не всегда является недостатком, а при определенных условиях может быть и преимуществом, потому что дает возможность разработчику приложений самому проектировать алгоритм планирования, наиболее подходящий для данного фиксированного набора задач. Так как разработчик сам определяет в программе момент времени отдачи управления, то при этом исключаются нерациональные прерывания программ в «неудобные» для них моменты времени. Кроме того, легко разрешаются проблемы совместного использования данных: задача во время каждой итерации использует их монопольно и уверена, что на протяжении этого периода никто другой не изменит эти данные. Существенным преимуществом non-preemptive систем является более высокая скорость переключения с задачи на задачу. Примером эффективного использования невытесняющей многозадачности является файл-сервер NetWare, в котором, в значительной степени благодаря этому, достигнута высокая скорость выполнения файловых операций. Менее удачным оказалось использование невытесняющей многозадачности в операционной среде Windows 3.х. Однако почти во всех современных операционных системах, ориентированных на высокопроизводительное выполнение приложений (UNIX, Windows NT, OS/2, VAX/VMS), реализована вытесняющая многозадачность. В последнее время дошла очередь и до ОС класса настольных систем, например, OS/2 Warp и Windows 95. Возможно в связи с этим вытесняющую многозадачность часто называют истинной многозадачностью. 61 Рис. 24 Рис. 25 62 Программный код только тогда начнет выполняться, когда для него операционной системой будет создан процесс. Создать процесс − это значит: 1. создать информационные структуры, описывающие данный процесс, то есть его дескриптор и контекст; 2. включить дескриптор нового процесса в очередь готовых процессов; 3. загрузить кодовый сегмент процесса в оперативную память или в область свопинга. Рис. 26 В многозадачной (многопроцессорной) системе процесс может находиться в одном из трех основных состояний: ВЫПОЛНЕНИЕ − активное состояние процесса, во время которого процесс обладает всеми необходимыми ресурсами и непосредственно выполняется процессором; ОЖИДАНИЕ − пассивное состояние процесса, процесс заблокирован, он не может выполняться по своим внутренним причинам, он ждет осуществления некоторого события, например, завершения операции ввода-вывода, получения сообщения от другого процесса, освобождения какого-либо необходимого ему ресурса; ГОТОВНОСТЬ − также пассивное состояние процесса, но в этом случае процесс заблокирован в связи с внешними по отношению к нему обстоятельст- 63 вами: процесс имеет все требуемые для него ресурсы, он готов выполняться, однако процессор занят выполнением другого процесса. В ходе жизненного цикла каждый процесс переходит из одного состояния в другое в соответствии с алгоритмом планирования процессов, реализуемым в данной операционной системе. Типичный граф состояний процесса показан на рисунке 26. В состоянии ВЫПОЛНЕНИЕ в однопроцессорной системе может находиться только один процесс, а в каждом из состояний ОЖИДАНИЕ и ГОТОВНОСТЬ − несколько процессов, эти процессы образуют очереди соответственно ожидающих и готовых процессов. Жизненный цикл процесса начинается с состояния ГОТОВНОСТЬ, когда процесс готов к выполнению и ждет своей очереди. При активизации процесс переходит в состояние ВЫПОЛНЕНИЕ и находится в нем до тех пор, пока либо он сам освободит процессор, перейдя в состояние ОЖИДАНИЯ какого-нибудь события, либо будет насильно «вытеснен» из процессора, например, вследствие исчерпания отведенного данному процессу кванта процессорного времени. В последнем случае процесс возвращается в состояние ГОТОВНОСТЬ. В это же состояние процесс переходит из состояния ОЖИДАНИЕ, после того, как ожидаемое событие произойдет. Рис. 27 Модель с пятью состояниями Если бы все процессы всегда были готовы к выполнению, то очередь на рис. 27 могла бы работать вполне эффективно. Такая очередь работает по 64 принципу обработки в порядке поступления, а процессор обслуживает имеющиеся в наличии процессы круговым (round-robin) методом (каждому процессу в очереди отводится определенный промежуток времени, по истечении которого процесс возвращается обратно в очередь, если он не был блокирован). Однако даже в таком простом примере, который был описан выше, подобная реализация не является адекватной: некоторые из не выполняющихся процессов готовы к выполнению, в то время как другие являются заблокированными и ждут окончания операции ввода-вывода. Таким образом, при наличии только одной очереди диспетчер не может просто выбрать для выполнения первый процесс из очереди. Перед этим он должен будет просмотреть весь список, отыскивая незаблокированный процесс, который находится в очереди дольше других. Естественнее было бы разделить все невыполняющиеся процессы на два типа: готовые к выполнению и заблокированные. Такая схема показана на рис. 27. Здесь добавлены еще два состояния, которые окажутся полезными в дальнейшем. Опишем каждое из пяти состояний процессов, представленных на диаграмме. • Выполняющийся. Процесс, который выполняется в текущий момент времени. В настоящей главе предполагается, что на компьютере установлен только один процессор, поэтому в этом состоянии может находиться только один процесс. • Готовый к выполнению. Процесс, который может быть запущен, как только для этого представится возможность. • Блокированный. Процесс, который не может выполняться до тех пор, пока не произойдет некоторое событие, например завершение операции ввода-вывода. • Новый. Только что созданный процесс, который еще не помещен операционной системой в пул выполнимых процессов. Обычно это новый процесс, который еще не загружен в основную память. • Завершающийся. Процесс, удаленный операционной системой из пула выполнимых процессов. Состояния «новый» и «завершающийся» представляют собой полезные конструкции для управления процессами. Первое из них соответствует процессу, который был только что определен. Например, если новый пользователь пытается войти в систему с разделением времени или в систему поступает новое пакетное задание, операционная система может определить новый процесс в два этапа. Во-первых, она выполняет всю необходимую рутинную работу: процессу присваивается идентификатор, формируются все необходимые для управления процессом таблицы. В этот период времени процесс находится в состоянии нового. Это означает, что операционная система выполнила необхо65 димые для создания процесса действия, но еще не приготовилась к его запуску. Например, операционная система может иметь ограничения по количеству одновременно выполняющихся процессов. Такие ограничения устанавливаются, например, чтобы не снижать производительность системы (или не переполнять основную память). Таблицы управления новыми процессами в необходимой операционной системе информацией содержатся в основной памяти, однако самих процессов там нет, т. е. код программы, которую нужно выполнить, не загружен в память, данным, относящимся к этой программе, не выделено пространство. Отвечающая такому процессу программа остается во вторичной памяти (обычно это диск) Выход процесса из системы также происходит в два этапа. Во-первых, процесс переходит в состояние завершающегося при достижении точки естественно завершения, а также когда он останавливается из-за возникновения неустранимой ошибки или когда его останавливает другой процесс, обладающий необходимыми для этого полномочиями. После этого момента процесс больше не может выполняться. Операционная система временно сохраняет таблицы и другую информацию, связанную с этим заданием, так что вспомогательные программы могут получить все необходимые сведения о завершившемся процессе (например, эти данные могут понадобиться программе, ведущей учет использования процессорного времени и других ресурсов). После того, как эти программы извлекут всю необходимую информацию, операционной системе больше не нужно хранить данные, связанные с процессом, и он полностью удаляется из системы. На рис. 27 показаны типы событий, соответствующие каждому из возможных переходов из одного состояния в другое. Возможны следующие переходы. • Нулевое состояние → Новый. Для выполнения программы создается новый процесс. Это событие может быть вызвано одной из причин, перечисленных в табл. 3.1. • Новый → Готовый. Операционная система переводит процесс из состояния нового в состояние готового к выполнению, когда она будет готова к обработке дополнительных процессов. В большинстве систем устанавливается ограничение на количество существующих процессов или на объем выделяемой для процессов виртуальной памяти. Таким образом предотвращается снижение производительности, которое может произойти, если будет загружено слишком много активных процессов. • Готовый → Выполняющийся. Когда наступает момент выбора нового процесса для запуска, операционная система выбирает один из готовых для выполнения процессов. Принцип этого выбора обсуждается в четвертой части книги. 66 • Выполняющийся → Завершающийся. Если процесс сигнализирует об окончании своей работы или происходит его аварийное завершение, операционная система прекращает его выполнение. • Выполняющийся → Готовый. Этот переход чаще всего происходит из-за того, что процесс выполняется в течение максимального промежутка времени, отведенного для непрерывной работы одного процесса. Подобная стратегия планирования используется практически во всех многозадачных операционных системах. Такой переход возможен и по некоторым другим причинам, зависящим от конкретной операционной системы. Например, если операционная система назначает разным процессам различные приоритеты, то может случиться так, что процесс будет выгружен из-за появления процесса с более высоким приоритетом. Предположим, что выполняется процесс А, имеющий определенный приоритет, а процесс В, приоритет которого выше, блокирован. Когда операционная система обнаружит, что произошло событие, ожидаемое процессом В, она переведет этот процесс в состояние готовности, в результате чего процесс А может быть, прерван и управление перейдет к процессу В. Говорят, что операционная система вытесняет (preempt) процесс А [3]. Наконец, процесс сам по себе может отказаться от использования процессора. • ·Выполняющийся → Блокированный. Процесс переводится в заблокированное состояние, если для продолжения работы требуется наступление некоторого события. Посылаемый операционной системе запрос обычно имеет вид вызова какой-нибудь системной службы, т. е. вызова процедуры, являющейся частью кода операционной системы. Процесс может запросить ресурс (например, файл или совместно используемую ячейку виртуальной памяти), который окажется временно недоступным, и потребуется подождать его освобождения. Кроме того, возможна ситуация, в которой для продолжения процесса требуется выполнить некоторое действие, например, операцию ввода-вывода. Если процессы обмениваются информацией друг с другом, один из них может быть блокирован в состоянии ожидания ввода или сообщения от другого процесса. • Блокированный → Готовый. Заблокированный процесс переходит в состояние готовности к выполнению в тот момент, когда происходит ожидаемое им событие. • Готовый → Завершающийся. Чтобы не усложнять картину, этот переход на диаграмме состояний не показан. В некоторых системах родительский процесс может в любой момент прервать выполнение 67 дочернего процесса. Кроме того, дочерние процессы могут прекратиться при завершении родительского процесса. • Блокированный → Завершающийся. См. комментарии к предыдущему пункту. Рис. 28 Многопоточностью (multithreading) называется способность операционной системы поддерживать в рамках одного процесса выполнение нескольких потоков. Традиционный подход, при котором каждый процесс представляет собой единый поток выполнения, называется однопоточным подходом. Две левые части рис. 28 иллюстрируют однопоточные подходы. MS DOS является примером операционной системы, способной поддерживать не более одного однопоточного пользовательского процесса. Другие операционные системы, такие, как разнообразные разновидности UNIX, поддерживают процессы множества пользователей, но в каждом из этих процессов может содержаться только один поток. В правой половине рис. 28 представлены многопоточные подходы. Примером системы, в которой один процесс может расщепляться на несколько потоков, является среда выполнения Java. В этом разделе нас будет интересовать использование нескольких процессов, каждый из которых поддерживает выполнение нескольких потоков. Подобный подход принят в таких операционных системах, как OS/2, Windows 2000 (W2K), Linux, Solaris, Mach и ряде других. В этом разделе приведено общее описание многопоточного режима, а в после- 68 дующих разделах будут подробно рассмотрены подходы, использующиеся в операционных системах W2K, Solaris и Linux. В многопоточной среде процесс определяется как структурная единица распределения ресурсов, а также структурная единица защиты. С процессами связаны следующие элементы: • Виртуальное адресное пространство, в котором содержится образ процесса. • Защищенный доступ к процессорам, другим процессам (при обмене информаций между ними), файлам и ресурсам ввода-вывода (устройствам и каналам). • В рамках процесса могут находиться один или несколько потоков, каждый из которых обладает следующими характеристиками. • Состояние выполнения потока (выполняющийся, готовый к выполнению и т. д.). • Сохраненный контекст не выполняющегося потока; один из способов рассмотрения потока — считать его независимым счетчиком команд, работающим в рамках процесса. • Стек выполнения. • Статическая память, выделяемая потоку для локальных переменных. • Доступ к памяти и ресурсам процесса, которому этот поток принадлежит; этот доступ разделяется всеми потоками данного процесса. На рис. 28 продемонстрировано различие между потоками и процессами с точки зрения управления последними. В однопоточной модели процесса в его представление входит управляющий блок этого процесса и пользовательское адресное пространство, а также стеки ядра и пользователя, с помощью которых осуществляются вызовы процедур и возвраты из них при выполнении процесса. Когда выполнение процесса прерывается, содержимое регистров процессора сохраняется в памяти. В многопоточной среде с каждым процессом тоже связаны управляющий блок и адресное пространство, но теперь для каждого потока создаются свои отдельные стеки, а также свой управляющий блок, в котором содержатся значения регистров, приоритет и другая информация о состоянии потока. Таким образом, все потоки процесса разделяют между собой состояние и ресурсы этого процесса. Они находятся в одном и том же адресном пространстве и имеют доступ к одним и тем же данным. Если один поток изменяет в памяти какие-то данные, то другие потоки во время своего доступа к этим данным имеют возможность отследить эти изменения. Если один поток открывает файл с правом чтения, другие потоки данного процесса тоже могут читать из этого файла. Перечислим основные преимущества использования потоков с точки зрения производительности. 69 1. Создание нового потока в уже существующем процессе занимает намного меньше времени, чем создание совершенно нового процесса. Исследования, проведенные разработчиками операционной системы Mach, показали, что скорость создания процессов по сравнению с такой же скоростью в UNIX-совместимых приложениях, в которых не используются потоки, возрастает в 10 раз 2. Поток можно завершить намного быстрее, чем процесс. 3. Переключение потоков в рамках одного и того же процесса происходит намного быстрее. 4. При использовании потоков повышается эффективность обмена информацией между двумя выполняющимися программами. В большинстве операционных систем обмен между независимыми процессами происходит с участием ядра, в функции которого входит обеспечение защиты и механизма, необходимого для осуществления обмена. Однако благодаря тому, что различные потоки одного и того же процесса используют одну и ту же область памяти и одни и те же файлы, они могут обмениваться информацией без участия ядра. Итак, если приложение или функцию нужно реализовать в виде набора взаимосвязанных модулей, намного эффективнее реализовать ее в виде набора потоков, чем в виде набора отдельных процессов. Примером приложения, в котором можно удачно применить потоки, является файловый сервер. При получении каждого нового файлового запроса программа управления файлами может порождать новый поток. Из-за того, что серверу приходится обрабатывать очень большое количество запросов, за короткий промежуток времени будут создаваться и удаляться множество потоков. Если такая серверная программа работает на многопроцессорной машине, то на разных процессорах в рамках одного процесса могут одновременно выполняться несколько потоков. Кроме того, из-за того, что процессы или потоки файлового сервера должны совместно использовать данные из файлов, а, следовательно, координировать свои действия, рациональнее использовать потоки и общую область памяти, а не процессы и обмен сообщениями. Потоковая конструкция процесса полезна и на однопроцессорных машинах. Она помогает упростить структуру программы, выполняющей несколько логически различных функций. 70 Рис. 29 Рис. 30 Обычно выделяют две общие категории потоков: потоки на уровне пользователя (user-level threads − ULT) и потоки на уровне ядра (kernel-level threads 71 — KLT). Потоки второго типа в литературе иногда называются потоками, поддерживаемыми ядром, или облегченными процессами. Потоки на уровне пользователя В программе, полностью состоящей из ULT-потоков, все действия по управлению потоками выполняются самим приложением; ядро по сути и не подозревает о существовании потоков. На рис. 30, а проиллюстрирован подход, при котором используются только потоки на уровне пользователя. Чтобы приложение было многопоточным, его следует создавать с применением специальной библиотеки, представляющей собой пакет программ для работы с потоками на уровне ядра. Такая библиотека для работы с потоками содержит код, с помощью которого можно создавать и удалять потоки, производить обмен сообщениями и данными между потоками, планировать их выполнение, а также сохранять и восстанавливать их контекст. По умолчанию приложение в начале своей работы состоит из одного потока и его выполнение начинается как выполнение этого потока. Такое приложение вместе с составляющим его потоком размещается в едином процессе, который управляется ядром. Выполняющееся приложение в любой момент времени может породить новый поток, который будет выполняться в пределах того же процесса. Новый поток создается с помощью вызова специальной подпрограммы из библиотеки, предназначенной для работы с потоками. Управление к этой подпрограмме переходит в результате вызова процедуры. Библиотека потоков создает структуру данных для нового потока, а потом передает управление одному из готовых к выполнению потоков данного процесса, руководствуясь некоторым алгоритмом планирования. Когда управление переходит к библиотечной подпрограмме, контекст текущего потока сохраняется, а когда управление возвращается к потоку, его контекст восстанавливается. Этот контекст в основном состоит из содержимого пользовательских регистров, счетчика команд и указателей стека. Все, описанные в предыдущих абзацах события, происходят в пользовательском пространстве в рамках одного процесса. Ядро не подозревает об этой деятельности. Оно продолжает осуществлять планирование процесса как единого целого и приписывать ему единое состояние выполнения (состояние готовности, состояние выполняющегося процесса, состояние блокировки и т. д.). 72 Рис. 31 Использование потоков на пользовательском уровне обладает некоторыми преимуществами перед использованием потоков на уровне ядра. К этим преимуществам относятся следующие: 1. Переключение потоков не включает в себя переход в режим ядра, так как структуры данных по управлению потоками находятся в адресном пространстве одного и того же процесса. Поэтому для управления потоками процессу не нужно переключаться в режим ядра. Благодаря этому обстоятельству удается избежать накладных расходов, связанных с двумя переключениями режимов (пользовательского режима в режим ядра и обратно). 2. Планирование производится в зависимости от специфики приложения. Для одних приложений может лучше подойти простой алгоритм планирования по круговому алгоритму, а для других — алгоритм планирования, основанный на использовании приоритета. Алгоритм планирования может подбираться для конкретного приложения, причем это не повлияет на алгоритм планирования, заложенный в операционной системе. 3. Использование потоков на пользовательском уровне применимо для любой операционной системы. Для их поддержки в ядро системы не потребуется вносить никаких изменений. Библиотека потоков представляет собой набор утилит, работающих на уровне приложения и совместно используемых всеми приложениями. 73 Использование потоков на пользовательском уровне обладает двумя явными недостатками по сравнению с использованием потоков на уровне ядра. 1. В типичной операционной системе многие системные вызовы являются блокирующими. Когда в потоке, работающем на пользовательском уровне, выполняется системный вызов, блокируется не только данный поток, но и все потоки того процесса, к которому он относится. 2. В стратегии с наличием потоков только на пользовательском уровне приложение не может воспользоваться преимуществами многопроцессорной системы, так как ядро закрепляет за каждым процессом только один процессор. Поэтому несколько потоков одного и того же процесса не могут выполняться одновременно. В сущности у нас получается многозадачность на уровне приложения в рамках одного процесса. Несмотря на то, что даже такая многозадачность может привести к значительному увеличению скорости работы приложения, имеются приложения, которые работали бы гораздо лучше, если бы различные части их кода могли выполняться одновременно. Эти две проблемы разрешимы. Например, их можно преодолеть, если писать приложение не в виде нескольких потоков, а в виде нескольких процессов. Однако при таком подходе основные преимущества потоков сводятся на нет: каждое переключение становится не переключением потоков, а переключением процессов, что приведет к значительно большим накладным затратам. Другим методом преодоления проблемы блокирования является использование преобразования блокирующего системного вызова в неблокирующий. Например, вместо непосредственного вызова системной процедуры вводавывода поток вызывает подпрограмму-оболочку, которая производит вводвывод на уровне приложения. В этой программе содержится код, который проверяет, занято ли устройство ввода-вывода. Если оно занято, поток передает управление другому потоку (что происходит с помощью библиотеки потоков). Когда наш поток вновь получает управление, он повторно осуществляет проверку занятости устройства ввода-вывода. 74 Рис. 32 В программе, работа которой полностью основана на потоках, работающих на уровне ядра, все действия по управлению потоками выполняются ядром. В области приложений отсутствует код, предназначенный для управления потоками. Вместо него используется интерфейс прикладного программирования (application programming interface — API) средств ядра, управляющих потоками. Примерами такого подхода являются операционные системы OS/2, Linux и W2K. На рис. 32 проиллюстрирована стратегия использования потоков на уровне ядра. Любое приложение при этом можно запрограммировать как многопоточное; все потоки приложения поддерживаются в рамках единого процесса. Ядро поддерживает информацию контекста процесса как единого целого, а также контекстов каждого отдельного потока процесса. Планирование выполняется ядром исходя из состояния потоков. С помощью такого подхода удается избавиться от двух упомянутых ранее основных недостатков потоков пользовательского уровня. Во-первых, ядро может одновременно осуществлять планирование работы нескольких потоков одного и того же процесса на нескольких процессорах. Во-вторых, при блокировке одного из потоков процесса ядро может выбрать для выполнения другой поток этого же процесса. Еще одним преимуществом такого подхода является то, что сами процедуры ядра могут быть многопоточными. Основным недостатком подхода с использованием потоков на уровне ядра по сравнению с использованием потоков на пользовательском уровне является то, что для передачи управления от одного потока другому в рамках одно75 го и того же процесса приходится переключаться в режим ядра. Результаты исследований, проведенных на однопроцессорной машине VAX под управлением UNIX-подобной операционной системы, представленные в табл. 4.1, иллюстрируют различие между этими двумя подходами. Сравнивалось время выполнения таких двух задач, как (1) нулевое ветвление (Null − время, затраченное на создание, планирование и выполнение процесса/потока, состоящего только из нулевой процедуры измеряются только накладные расходы, связанные с ветвлением процесса/потока), и (2) ожидание сигнала (Signal-Wait) — время, затраченное на передачу сигнала от одного процесса/потока другому процессу/потоку, находящемуся в состоянии ожидания (накладные расходы на синхронизацию двух процессов/потоков). Чтобы было легче сравнивать полученные значения, заметим, что вызов процедуры на машине VAX, используемой в этом исследовании, длится 7 µs, а системное прерывание − 17 µs. Мы видим, что различие во времени выполнения потоков на уровне ядра и потоков на пользовательском уровне более чем на порядок превосходит по величине различие во времени выполнения потоков на уровне ядра и процессов. Таким образом, создается впечатление, что как применение многопоточности на уровне ядра дает выигрыш по сравнению с процессами, так и многопоточность на пользовательском уровне дает выигрыш по сравнению с многопоточностью на пользовательском уровне. Однако на деле возможность этого дополнительного выигрыша зависит от характера приложений. Если для большинства переключений потоков приложения необходим доступ к ядру, то схема с потоками на пользовательском уровне может работать не намного лучше, чем схема с потоками на уровне ядра. 76 ОГЛАВЛЕНИЕ ВВЕДЕНИЕ………………………………………………………..…………………3 АРХИТЕКТУРА Операционной системы…………………………….………...….4 Основные модели структуры ОС…………………………………………….4 Монолитные системы………………………………………………….4 Многоуровневые системы……………………………………………..9 Виртуальная машина………………………………………………….13 Экзоядро……………………………………………………………….16 Модель клиент-сервер….…………………………………………..... 17 Режим работы системы……………………………………………………...20 Многослойная структура ОС………………………………………………..29 Многослойная структура ядра ОС………………………………….……... 30 Концепция микроядерной архитектуры……………………………………33 Производительность…………………………………………………………40 Подсистема окружения……………………………………………………...45 Трансляция библиотек ………………………..…………………………….49 ПРОЦЕССЫ И ПОТОКИ.…………………………………………………………52 Создание процессов…………………………………………………………54 Завершение процессов………………………………………………………55 Планирование процессов и потоков……………………………………….58 Контекст процесса…………………………………………………………..62 Дескриптор процесса………………………………………………………..62 Модель с пятью состояниями……………………………………………….64 Многопоточность……………………………………………………………68 Потоки на уровне пользователя…………………………………………….72 Потоки на уровне ядра………………………………………………………75 77 Учебное издание ОПЕРАЦИОННЫЕ СИСТЕМЫ: АРХИТЕКТУРА И УПРАВЛЕНИЕ ПРОЦЕССАМ Методические указания Составитель: МАКАРОВ Павел Сергеевич Редактор Штаева М. Формат 60×84/16. Усл. печ. л. 3.49. Ульяновский государственный технический университет 432027, г. Ульяновск, ул. Сев. Венец, д. 32 Типография УлГТУ, 432027, г. Ульяновск, ул. Сев. Венец, д. 32 78