Операционные системы, среды и оболочки. Основные теоретические сведения: Учебное пособие [С. Ф. Храпский] (doc) читать онлайн

Книга в формате doc! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]

 Федеральное агентство по образованию
Омский государственный институт сервиса
Кафедра высшей математики и информатики







С. Ф. Храпский


ОПЕРАЦИОННЫЕ СИСТЕМЫ, СРЕДЫ И ОБОЛОЧКИ

ОСНОВНЫЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ


Учебное пособие




Д О П У Щ Е Н О
Министерством образования и науки Российской Федерации
в качестве учебного пособия
для студентов высших учебных заведений, обучающихся
по специальности «Прикладная информатика (в сфере сервиса)»







Омск 2005
УДК 681.3
Х – 90
Храпский С. Ф.
Операционные системы, среды и оболочки. Основные теоретические сведения: Учебное пособие. – Омск: ОГИС, 2005. – 268 c.

ISBN 5-93252-039-6

Учебное пособие разработано с целью обеспечения обучающихся и преподавателей систематизированным учебным материалом по теоретическим основам операционных систем.
Рассмотрены основные понятия операционных систем, сред и оболочек, вопросы управления процессами и ресурсами, особенности построения 2перациионных систем для многопроцессорных вычислительных машин и многомашинных вычислительных систем, общие концепции и принципы разработки операционных систем, история развития операционных систем и эволюция их функциональных характеристик. Представлены примеры практической реализации наиболее распространенных современных операционных систем.
Учебное пособие подготовлено в соответствии с требованиями ГОС ВПО специальности 351400 «Прикладная информатика (в сфере сервиса)».
Предназначено для студентов очной и заочной форм обучения специальности 351400 «Прикладная информатика (в сфере сервиса)», изучающих дисциплину «Операционные системы, среды и оболочки». Может быть использовано при изучении ряда смежных дисциплин этой специальности, а также студентами других специальностей, изучающих соответствующие разделы в дисциплинах «Информатика», «Информационные технологии», «Информационные системы».
УДК 681.3
Библиогр.: 18 назв., словарь, алфавитно-предметный указ. (в конце)

Рецензенты:
д-р техн. наук, профессор В. Ю. Юрков
(Сибирская государственная автомобильно-дорожная академия)

канд. техн. наук, доцент В. И. Стариков
(Омский государственный институт сервиса)

Ответственный за выпуск
зав. кафедрой ВМиИ, канд. пед. наук, доцент О. Н. Лучко

ISBN 5-93252-039-6  Омский государственный
институт сервиса, 2005
Оглавление
Предисловие………………………………………...…………………………………….....
Введение……………………………………………………………………………………...
1. Управление процессами в автономных
однопроцессорных вычислительных машинах…………………………...……………
1.1. Понятия вычислительного процесса и ресурса……………………………………..
1.2. Планирование процессов……………………………………………………………..
1.3. Межпроцессное взаимодействие…………………………………………………….
1.4. Понятия потока («нити») и многопоточности………………………………………
Контрольные вопросы и задания…………………………………………………………
2. Управление ресурсами в автономных однопроцессорных компьютерах…….….
2.1. Управление памятью………………………………………………………………….
2.2. Управление вводом-выводом………………………………………………………...
2.3. Управление файлами и файловая система…………………………………………..
Контрольные вопросы и задания…………………...…………………………………….
3. Управление процессами и ресурсами в автономных многопроцессорных
вычислительных машинах……………………………………………………………….
3.1. Реализация операционных систем многопроцессорных вычислительных машин.
3.2.Планирование и синхронизация
в многопроцессорных вычислительных машинах…….................………………......
Контрольные вопросы и задания………………………………………...……………….
4.Управление процессами и ресурсами в многомашинных
вычислительных системах..................................................................................................
4.1. Способы организации управления процессами и ресурсами
в многомашинных вычислительных системах…………………………………………….
4.2. Понятия сетевой и распределенной операционных систем………………………...
4.3. Варианты реализации распределенных операционных систем…………………….
Контрольные вопросы и задания…………………………...…………………………….
5. Общие концепции разработки операционных систем…………………………..
5.1. Основные принципы построения операционных систем …………………………..
5.2. Архитектурные особенности проектирования операционных систем…………….
5.3. Принципы построения системных и прикладных программных интерфейсов…...
Контрольные вопросы и задания……………………...………………………………….
6. История развития операционных систем
и эволюция их функциональных характеристик………………………..……….….....
6.1.Операционные системы разных этапов разработки вычислительных машин..........
6.2. История развития и характеристики операционных систем UNIX.........................
6.3. История развития и характеристики операционных систем семейства Windows..
Контрольные вопросы и задания……………………………...………………………….
7. Пример практической реализации операционной системы: UNIX……………….
7.1. Обзор системы UNIX………………………………………………………………….
7.1.1. Общие представления……………………………………………………………..
7.1.2. Интерфейсы системы UNIX………………………………………………………
7.1.3. Оболочка и утилиты системы UNIX……………………………………………..
7.1.4. Структура ядра системы UNIX…………………………………………………...
7.2. Процессы в UNIX……………………………………………………………………...
7.2.1. Основные понятия…………………………………………………………………
7.2.2. Реализация процессов в UNIX……………………………………………………
7.2.3. Планирование в системе UNIX…………………………………………………...
7.3. Управление памятью в UNIX…………………………………………………………
7.3.1. Основные понятия…………………………………………………………………
7.3.2. Реализация управления памятью в UNIX………………………………………..
7.4. Ввод-вывод в системе UNIX………………………………………………………….
7.4.1. Основные понятия…………………………………………………………………
7.4.2. Реализация ввода-вывода в системе UNIX………………………………………
7.4.3. Потоки данных в UNIX……………………………………….....……...………...
7.5. Файловые системы UNIX……………………………………………………………..
7.5.1. Основные понятия…………………………………………………………………
7.5.2. Реализация классической файловой системы UNIX…………………....………
7.5.3. Реализация файловой системы Berkeley Fast…………………………………...
7.5.4. Реализация файловой системы Linux....................................................................
7.5.5. Реализация файловой системы NFS......................................................................
7.6. Безопасность в UNIX.....................................................................................................
7.6.1. Основные понятия...................................................................................................
7.6.2. Реализация безопасности в UNIX..........................................................................
Контрольные вопросы и задания........................................................................................
8. Пример практической реализации операционной системы: Windows 2000..........
8.1. Обзор структуры операционной систем Windows 2000.............................................
8.1.1. Структура системы..................................................................................................
8.1.2. Реализация объектов................................................................................................
8.1.3. Подсистемы окружения...........................................................................................
8.2. Процессы и потоки в Windows 2000............................................................................
8.2.1. Основные понятия....................................................................................................
8.2.2. Межпроцессное взаимодействие............................................................................
8.2.3. Реализация процессов и потоков............................................................................
8.2.4. Загрузка Windows 2000............................................................................................
8.3. Управление памятью в Windows 2000.........................................................................
8.3.1. Основные понятия....................................................................................................
8.3.2. Реализация управления памятью............................................................................
8.4. Ввод-вывод в системе Windows 2000...........................................................................
8.4.1. Основные понятия....................................................................................................
8.4.2. Реализация ввода-вывода в Windows 2000............................................................
8.5. Файловые системы Windows 2000................................................................................
8.5.1. Файловые системы типа FAT.................................................................................
8.5.2. Файловая система типа NTFS.................................................................................
8.6. Безопасность в Windows 2000.......................................................................................
8.6.1. Основные понятия....................................................................................................
8.6.2. Реализация защиты в Windows 2000......................................................................
Контрольные вопросы и задания........................................................................................
Заключение..............................................................................................................................
Библиографический список.................................................................................................
Словарь терминов и определений......................................................................................
Алфавитно-предметный указатель....................................................................................
5
6

8
8
11
23
28
34
35
35
46
51
66

67
67

72
82

83

83
91
93
99
100
100
107
115
121

122
122
129
132
140
141
141
141
141
142
144
146
146
150
153
157
157
160
165
165
168
170
172
172
175
177
178
180
187
187
189
191
193
193
193
203
208
211
211
214
217
221
223
223
227
234
234
235
239
239
242
249
249
254
251
255
260
262
265
Предисловие

Учебное пособие предназначено для студентов очной и заочной форм обучения специальности 351400 «Прикладная информатика (в сфере сервиса)», изучающих дисциплину «Операционные системы, среды и оболочки», и разработано с целью обеспечения обучающихся и преподавателей систематизированным учебным материалом по теоретическим основам операционных систем.
Рассмотрены основные понятия операционных систем, сред и оболочек, вопросы управления процессами и ресурсами, особенности построения операционных систем для многопроцессорных вычислительных машин и многомашинных вычислительных систем, общие концепции и принципы разработки операционных систем. Приведены краткие исторические сведения о возникновении и развитии операционных систем, а также даны оценки их свойств и характеристик. В качестве примеров практической реализации реально функционирующих современных операционных систем описаны наиболее распространенные и «знаковые» системы семейств UNIX и Windows.
Представленные в учебном пособии сведения могут найти применение при изучении ряда смежных дисциплин специальности 351400 «Прикладная информатика (в сфере сервиса)», а также соответствующих разделов в дисциплинах «Информатика», «Информационные технологии», «Информационные системы» других специальностей.
Учебное пособие подготовлено кандидатом технических наук, проректором ОГИС по информационным технологиям Сергеем Филипповичем Храпским.
В разделах 7 и 8 использованы материалы книги Э. Таненбаума «Современные операционные системы» (см. Библиографический список).
Данное учебное пособие по порядку изучения является следующим за учебным пособием С.Ф. Храпского «Вычислительные системы, сети и телекоммуникации» (ОГИС, 2005 г.). Поэтому при изложении материала предполагается наличие у читателя предварительных знаний по основным понятиям и терминологии вычислительной техники.


Автор


Введение

Операционные системы являются основой программного обеспечения вычислительных машин (и их систем – вычислительных систем). Операционная система (далее – ОС) – это комплекс управляю­щих и обрабатывающих программ, который, с одной стороны, выступает как интерфейс между пользователем (с его задачами) и аппаратными компонентами вычислительных машин (далее – ВМ) и вычислительных систем (далее – ВС), а с другой стороны предназначен для эффективного управления вычислительными про­цессами, а также наиболее рационального распределения и использования вычислительных ресур­сов ВМ и ВС.
Обеспечение пользователю определенного уровня удобств осуществляется посредством того, что ОС представляет для него так называемую «расширенную» (или виртуальную) машину, которая избавляет пользователя от необходимости работать напрямую с аппаратными компонентами и берет на себя выполнение большинства рутинных операций. Таким образом, абстрактная «расширенная» машина, с которой, благодаря ОС, имеет дело пользователь, гораздо проще и удобнее в обращении, чем реальная аппаратура, лежащая в основе этой абстрактной машины.
Идея о том, что ОС прежде всего система, обеспечивающая удобный интерфейс пользователям, соответствует рассмотрению ее сверху вниз. Другой взгляд, снизу вверх, дает представление об ОС как о некотором механизме, распределяющим и управляющим всеми компонентами и ресурсами ВМ и ВС с целью обеспечения максимальной эффективности их функционирования.
Таким образом, ОС выполняет функции управления вычислительными про­цессами в ВМ и ВС, распределяет ресурсы ВМ и ВС между различными вычислительными процессами и образует программ­ную среду, в которой выполняются прикладные программы пользователей. Такая среда называется операционной средой.
Благодаря наличию операционной системы пользователи-программисты при написании собственных программ могут вообще не знать многих деталей управления конкретными ресурсами ВМ и ВС, а должны только обращаться к ОС как к некоторой программной подсистеме с соответствующими вызовами и получать от нее необходимые функции и сервисы. Набор таких функций и правил обращения к ним как раз и образуют то базовое понятие, которое называют операционной средой. Таким образом можно сказать, что термин «операционная среда» означает, прежде всего, соответствующие интерфейсы, необходимые программам и пользователям для обращения к ОС с целью получить определенные сервисы.
Параллельное существование терминов «операционная система» и «операционная среда» вызвано тем, что операционная система в общем случае может поддерживать несколько операционных сред. Операционная среда в свою очередь может включать несколько разных пользовательских и программных интерфейсов. Например, ряд ОС предоставляют для пользователя интерфейсы командной строки, интерфейсы-оболочки (типа Norton Commander и т.п.), а также графические интерфейсы. Если же говорить о программных интерфейсах, то это те интерфейсы, к которым могут обращаться программисты для получения соответствующих функций и сервисов. Можно сказать, что операционная среда – это то системное программное окружение, в котором могут выполняться программы, созданные по правилам работы этой среды.
Операционная система в значительной степени определяет функциональные возможности, удобства пользования и эффективность работы ВМ и ВС. На сегодняшний день существует большое количество разных типов ОС, отличающихся областями применения, аппаратными платформами и методами реализации. Естественно, это обуславливает и значительные функциональные различия этих ОС. Поэтому при изучении операционных систем очень важно из всего многообразия выделить те функции, которые присущи всем операционным системам как классу программных продуктов. Именно общие понятия ОС, концепции их построения и функциональные возможности являются предметами рассмотрения данного учебного пособия. В заключительных разделах учебного пособия уделено внимание историческим сведениям о возникновении и развитии ОС, а также даны оценки свойств и характеристик современных ОС. Представлены примеры практического построения, состава и особенностей функционирования наиболее популярных и эффективных операционных систем.

1. Управление процессами в автономных
однопроцессорных вычислительных машинах
1.1. Понятия вычислительного процесса и ресурса

Понятие «вычислительный процесс» (или просто – процесс) является одним из основных при рассмотрении операционных систем. Под процессом обычно понимается последовательность операций при выполнении программы или ее части в совокупности с используемыми данными. В общем случае процесс и программа представляют собой разные понятия. Програм­ма – это план действий, а процесс – это само действие, поэтому понятие процесса включает программный код, данные, содержимое стека, содержимое адресного и других регистров процессора. Таким образом, для одной программы могут быть созданы не­сколько процессов в том случае, если с помощью одной программы в центральном процессоре (central processor unit – CPU) вычислительной машины выполняется несколько несовпадающих последовательностей команд.
По временным характеристикам различают интерактивные процессы, пакетные процессы и процессы реального времени. Время существования интерактивного процесса определя­ется реакцией ВМ на запрос обслуживания и составляет секунды. Процессы реального времени имеют гарантированное время окон­чания работы, а время реакции измеряется долями секунды. Пакетные процессы запуска­ются один вслед за другим, а время реакции составляет часы и более.
По генеалогическому признаку различают порождающие и порож­денные процессы.
По результативности различают эквивалентные, тождествен-ные и равные процессы. Все они имеют одинаковый конечный результат, нo эквивалентные процессы могут реализовываться как на одном, так и на многих процессорах по одному или разным алгоритмам, то есть они имеют разные трассы, которые определяют порядок и длительность пребывания процесса в разных состояниях. Тождествен­ные процессы реализуются по одной и той же программе, но имеют разные трассы. Равные (или одинаковые процессы) реализуются по одной про­грамме и имеют одинаковые трассы.

По времени развития процессы делятся на последовательные, па­раллельные и комбинированные (для последних есть точки, в которых существуют оба процесса, и точки, в которых существует только один процесс).
По месту развития процессы делятся на внутренние (реализуются на центральном процессоре) и внешние (реализуются на внешних процессорах).
По принадлежности к операционной системе процессы бывают системные (исполняют программу из состава операционной систе­мы) и пользовательские.
По связности различают процессы:
а) взаимосвязанные – имеющие какую-то связь (простран­ственно-временную, управляющую, информационную);
б) изолированные – слабо связанные;
в) информационно-независимые – использующие совмест­ные ресурсы, но имеющие собственные информационные базы;
г) взаимодействующие – имеющие информационные связи и раз­деляющие общие структуры данных;
д) взаимосвязанные по ресурсам;
е) конкурирующие.
Порядок взаимосвязи процессов определяется правилами синхро­низации. Процессы могут находиться в отношении:
а) предшествования – когда один процесс всегда находится в активном состо­янии раньше, чем другой;
б) приоритетности – когда процесс может быть переведен в ак­тивное состояние только в том случае, если в состоянии готовности нет процессов с более высоким приоритетом, или если свобо­ден процессор, или если на процессоре реализуется процесс с меньшим приоритетом;
в) взаимного исключения – когда в процессе используется общий критический ресурс, и процессы не могут развиваться одновремен­но (если один из них использует критический ресурс, то другой на­ходится в состоянии ожидания).
Примерами процессов являются прикладные программы пользователей, утилиты и другие системные обрабатывающие про­граммы. Процессами могут быть редактирование какого-либо текста, трансляция исходной программы, ее компоновка, исполнение. При этом трансляция какой-ни­будь исходной программы является одним процессом, а трансляция следующей исходной программы – другим процессом, хотя транслятор (как объ­единение программных модулей) здесь выступает как одна и та же программа, но обрабатываемые им данные являются разными.
Определение концепции процесса преследует цель выработать механизмы распре­деления и управления так называемыми «ресурсами», которые также относятся к числу важнейших понятий ОС.
Ресурс – это любой потребляемый (расходуемый) объект. По запа­сам ресурсы подразделяются на исчерпаемые и неисчерпаемые. По­требители ресурсов – процессы. Ресурс – это средство вычисли-тельной машины или вычислительной системы, которое может быть выделено процессу на определенный интервал времени.
Ресурсы классифицируются по следующим свойствам:
а) по признаку реальности – на физические и виртуальные (последние только в отдельных свойствах схожи с физическими ресурсами);
б) по возможности расширения свойств – на эластичные и жесткие (не допускающие расширяемости);
в) по степени активности – на пассивные и активные (способные выполнять действия по отношению к другим ресурсам);
г) по времени существования – на постоянные (доступные на протяжении всего времени существования процесса, а также до и после его работы) и временные;
д) по степени важности – на основные и второстепенные (допускающие альтернативное развитие процесса);
е) по функциональной избыточности при распределении – на дорогие (но предоставляемые быстро) и дешевые (но предоставляемые с ожида­нием);
ж) по структуре – на со­ставные и простые (которые не содержат составных элементов и могут находиться только в одном из двух состояний: «доступен» или «занят»);
з) по характеру использования – на потребляе­мые и воспроизводимые (допускающие многократное использо­вание и освобожде-ние), а также – на ис­пользуемые последовательно и ис­пользуемые параллельно (использующиеся несколькими процессами);
и) по форме реализации – на жесткие (не допускающие копи­рования) и мягкие (допускающие тиражирование и в свою очередь подразделяющиеся на программные и информационные ресурсы).

Примерами ресурсов являются некоторые аппартные устройства ВМ (процессоры, запоминающие устройства, устройства ввода-вывода) или некоторые наборы информации (в частности, совокупности записей определенной структуры – так называемые «файлы»).

1.2. Планирование процессов

Важнейшей частью операционной системы, непосредственно влияющей на функционирование вычислительной машины, является подсистема управления процессами. Для операционной системы процесс представляет собой единицу работы, заявку на потребление системных ресурсов. Подсистема управления процессами планирует выполнение процессов, то есть распределяет процессорное время между несколькими одновременно существующими в системе процессами, а также занимается созданием и уничтожением процессов, обеспечивает процессы необходимыми системными ресурсами, поддерживает взаимодействие между процессами.
За время своего существования процесс многократно изменяет свое состояние. Обычно различают следующие состояния процесса:
• новый (процесс только что создан);
• готовый (процесс ожидает освобождения CPU);
• выполняемый (команды программы выполняются в CPU);
• ожидающий (процесс ожидает завершения некоторого собы­тия, чаще всего операции ввода-вывода);
• завершенный (процесс завершил свою работу).
Переход из одного состояния в другое не может выполняться произвольным образом. Каждый процесс представлен в операционной системе набором данных, называемых «таблица управления процессом» или сокращенно – ТУП (process control block – РСВ). В ТУП процесс описывается набором значе­ний и параметров, характе-ризующих его текущее состояние и ис­пользуемых операционной системой для управления прохождением процесса через ВМ.
Распределение процессов между имеющимися ресурсами носит название «планирование процессов».
На протяжении существования процесса его выполнение может быть многократно прервано и продолжено. Для того, чтобы возобновить выполнение процесса, необходимо восстановить состояние его операционной среды. Состояние операционной среды отображается состоянием регистров и программного счетчика, режимом работы процессора, указателями на открытые файлы, информацией о незавершенных операциях ввода-вывода, кодами ошибок выполняемых процессом системных вызовов и т.д. Эта информация называется контекстом процесса. Кроме этого, операционной системе для реализации планирования процессов требуется дополнительная информация: идентификатор процесса, состояние процесса, данные о степени привилегированности процесса, место нахождения кодового сегмента и другая информация. Информацию такого рода, используемую в некоторых ОС для планирования процессов, называют дескриптором процесса. Дескриптор процесса по сравнению с контекстом содержит более оперативную информацию, которая должна быть легко доступна подсистеме планирования процессов. Контекст процесса содержит менее актуальную информацию и используется операционной системой только после того, как принято решение о возобновлении прерванного процесса.
Одним из методов планирования процессов, ориентированных на эффективную загрузку ресурсов, является метод очередей ресурсов. Новые процессы находятся во входной очереди, часто называемой очередью работ – заданий. Входная очередь располагается во внешней памяти, во входной очереди процессы ожидают освобождения ресурса – адресного про­странства основной памяти. Готовые к выполнению процессы располагаются в основной па­мяти и связаны очередью готовых процессов. Процессы в этой очере­ди ожидают освобождения ресурса «процессорное время». Выделение процессу процессорного времени производится многократно с целью достижения внешнего эффекта как бы «одновременного» выполнения нескольких программ. Такой механизм называется диспетчеризацией.
Процесс в состоянии ожидания, например, завершения операции ввода-вы­вода находится в одной из очередей к оборудованию ввода-вывода. Таким образом при прохождении через ВМ процесс мигрирует между различными очередями под управлением программы, которая назы­вается «планировщик» (scheduler).
Очереди процессов представляют собой дескрипторы отдельных процессов, объединенные в списки. Каждый дескриптор, кроме всего прочего, содержит по крайней мере один указатель на другой дескриптор, соседствующий с ним в очереди. Такая организация очередей позволяет легко их переупорядочивать, включать и исключать процессы, переводить процессы из одного состояния в другое.

Планирование процессов включает в себя решение следующих задач:
1) определение момента времени для смены выполняемого процесса;
2) выбор процесса на выполнение из очереди готовых процессов;
3) переключение контекстов «старого» и «нового» процессов.
Первые две задачи решаются программными средствами, а последняя – в значительной степени аппаратно.
Существует множество различных алгоритмов планирования процессов, по-разному решающих вышеперечисленные задачи, преследующих различные цели и обеспечивающих различную степень многозадачности. Среди этого множества алгоритмов рассмотрим подробнее две группы наиболее часто встречающихся алгоритмов: алгоритмы, основанные на квантовании, и алгоритмы, основанные на приоритетах.
В соответствии с алгоритмами, основанными на квантовании, смена активного процесса происходит в следующих случаях:
– процесс завершился и покинул систему;
– произошла ошибка;
– процесс перешел в состояние «ожидание»;
– исчерпан квант процессорного времени, отведенный данному процессу.
Процесс, который исчерпал свой квант, переводится в состояние «готовый» и ожидает, когда ему будет предоставлен новый квант процессорного времени, а на выполнение в соответствии с определенным правилом выбирается новый процесс из очереди готовых. Таким образом, ни один из процессов не занимает процессор надолго, поэтому квантование широко используется в системах разделения времени.
Кванты, выделяемые процессам, могут быть одинаковыми для всех процессов или различными. Кванты, выделяемые одному процессу, могут быть фиксированной величины или изменяться в разные периоды жизни процесса. Процессы, которые не полностью использовали выделенный им квант (например, из-за ухода на выполнение операций ввода-вывода), могут получить или не получить компенсацию в виде привилегий при последующем обслуживании. Очередь готовых процессов может быть организована циклически по правилу FCFS (First Come First Served – «первым пришел – первым обслужен»), часто называемого правилом FIFO (First In First Out – «первым вошел – первым вышел»), или по правилу стека LCFS (Last Come First Served – «последним пришел – первым обслужен»), имеющего также наименование правила LIFO (Last In First Out – «последним вошел – первым вышел»).
Другая группа алгоритмов использует понятие «приоритет» процесса. Приоритет – это число, характеризующее степень привилегированности процесса при использовании ресурсов ВМ, в частности, процессорного времени: чем выше приоритет, тем выше привилегии. Приоритет может выражаться целым или дробным, положительным или отрицательным значением. Чем выше привилегии процесса, тем меньше времени он будет проводить в очередях. Приоритет может назначаться директивно администратором системы в зависимости от важности работы или внесенной платы, либо вычисляться самой ОС по определенным правилам. Приоритет может оставаться фиксированным на протяжении всей жизни процесса либо изменяться во времени в соответствии с некоторым законом. В последнем случае приоритеты называются динамическими.
Существует две разновидности приоритетных алгоритмов: алгоритмы, использующие относительные приоритеты, и алгоритмы, использующие абсолютные приоритеты.
В обоих случаях выбор процесса на выполнение из очереди готовых осуществляется одинаково: выбирается процесс, имеющий наивысший приоритет. По-разному решается проблема определения момента смены активного процесса. В системах с относительными приоритетами активный процесс выполняется до тех пор, пока он сам не покинет процессор, перейдя в состояние «ожидание» (или же произойдет ошибка, или процесс завершится). В системах с абсолютными приоритетами выполнение активного процесса прерывается еще при одном условии: если в очереди готовых процессов появился процесс, приоритет которого выше приоритета активного процесса. В этом случае прерванный процесс переходит в состояние готовности.
Во многих операционных системах алгоритмы планирования построены с использованием как квантования, так и приоритетов. Например, в основе планирования лежит квантование, но величина кванта и/или порядок выбора процесса из очереди готовых определяется приоритетами процессов.
Существует два основных типа процедур планирования процессов – вытесняющие (preemptive) и невытесняющие (non-preemptive).

Невытесняющая многозадачность (non-preemptive multitasking) – это способ планирования процессов, при котором активный процесс выполняется до тех пор, пока он сам, по собственной инициативе, не отдаст управление планировщику операционной системы для того, чтобы тот выбрал из очереди другой, готовый к выполнению процесс.
Вытесняющая многозадачность (preemptive multitasking) – это такой способ, при котором решение о переключении процессора с выполнения одного процесса на выполнение другого процесса принимается планировщиком операционной системы, а не самой активной задачей.
Вытесняющая и невытесняющая многозадачность – это более широкие понятия, чем типы приоритетности. Приоритеты задач могут как использоваться, так и не использоваться как при вытесняющих, так и при невытесняющих способах планирования процессов. Например, в случае использования приоритетов дисциплина относительных приоритетов может быть отнесена к классу систем с невытесняющей многозадачностью, а дисциплина абсолютных приоритетов – к классу систем с вытесняющей многозадачностью. Бесприоритетная дисциплина планирования, основанная на выделении равных квантов времени для всех задач, относится к вытесняющим алгоритмам.
Основным различием между вытесняющими и невытесняющими вариантами многозадачности является степень централизации механизма планирования задач.
При вытесняющей многозадачности механизм планирования задач целиком сосредоточен в операционной системе, а программист пишет свое приложение, не заботясь о том, что оно будет выполняться «параллельно» с другими задачами. При этом ОС выполняет следующие функции: определяет момент снятия с выполнения активной задачи, запоминает ее контекст, выбирает из очереди готовых задач следующую и запускает ее на выполнение, загружая ее контекст.
При невытесняющей многозадачности механизм планирования распределен между ОС и прикладными программами. Прикладная программа, получив управление от ОС, сама определяет момент завершения своей очередной итерации и передает управление ОС с помощью какого-либо системного вызова, а ОС формирует очереди задач и выбирает в соответствии с некоторым алгоритмом (например, с учетом приоритетов) следующую задачу на выполнение. Такой механизм создает проблемы как для пользователей, так и для разработчиков. Для пользователей это означает, что управление системой теряется на произвольный период времени, который определяется приложением (а не пользователем). Если приложение тратит слишком много времени на выполнение какой-либо работы, например, на форматирование диска, пользователь не может переключиться с этой задачи на другую задачу, например, на текстовый редактор, в то время как форматирование могло бы продолжаться в фоновом режиме.
Поэтому разработчики приложений для невытесняющей операционной среды, возлагая на себя функции планировщика, должны создавать приложения так, чтобы они выполняли свои задачи небольшими частями. Например, программа форматирования может отформатировать одну дорожку дискеты и вернуть управление системе. После выполнения других задач система возвратит управление программе форматирования, чтобы та отформатировала следующую дорожку. Подобный метод разделения времени между задачами работает, но он существенно затрудняет разработку программ и предъявляет повышенные требования к квалификации программиста. Программист должен обеспечить «дружественное» отношение своей программы к другим выполняемым «одновременно» с ней программам, достаточно часто отдавая им управление. Крайним проявлением «недружественности» приложения является его «зависание», которое приводит к общему краху системы. В системах с вытесняющей многозадачностью такие ситуации, как правило, исключены, так как центральный планирующий механизм снимет «зависшую» задачу с выполнения.
Однако распределение функций планировщика между ОС и приложениями не всегда является недостатком, а при определенных условиях может быть и преимуществом, потому что дает возможность разработчику приложений самому проектировать алгоритм планирования, наиболее подходящий для данного фиксированного набора задач. Так как разработчик сам определяет в программе момент времени передачи управления, то при этом исключаются нерациональные прерывания программ в «неудобные» для них моменты времени. Кроме того, легко разрешаются проблемы совместного использования данных: задача во время каждой итерации использует их монопольно и уверена, что на протяжении этого периода никто другой не изменит эти данные. Значительным преимуществом невытесняющих систем является более высокая скорость переключения с задачи на задачу.
Одними из основных движущих сил, изменяющих состояния процессов, являются определенные системные события, называемые «прерываниями».
Прерывания представляют собой механизм, позволяющий коор-динировать па­раллельное функционирование отдельных устройств ВМ и реагировать на особые состояния, возникающие при работе процессора. Таким образом, прерывание – это принудительная передача управления от выполняе­мой программы к системе (а через нее – к соответствующей программе обработ­ки прерывания), происходящая при возникновении определенного события.
Идея прерываний была предложена в середине 1950-х годов и внесла весомый вклад в развитие вычислитель­ной техники. Основная цель введения прерываний – реализация асинхронного режима работы и распараллеливание работы отдельных устройств вычислитель­ного комплекса. Механизм прерываний реализуется аппаратно-программными средствами. Струк­туры систем прерывания (в зависимости от аппаратной архитектуры) могут быть самыми разными, но все они имеют одну общую особенность – прерывание не­пременно влечет за собой изменение порядка выполнения команд процессором.
Механизм обработки прерываний независимо от архитектуры ВМ включает следующие основные этапы-шаги:
1. Установление факта прерывания (прием сигнала на преры-вание) и иденти­фикация прерывания (в операционных системах иногда осуществляется по­вторно на шаге 4).
2. Запоминание состояния прерванного процесса. Состояние процесса определя­ется прежде всего значением счетчика команд (адресом следующей команды), содержимым регистров процессора и может включать также спецификацию режима (например, режим пользовательский или при­вилегированный) и другую информацию.
3. Передача управления (аппаратно) подпрограмме обработки прерывания. В про­стейшем случае в счетчик команд заносится начальный адрес подпрограммы обработки прерываний, а в соответствующие регистры – информация из сло­ва состояния. В более развитых процессорах осуществляется достаточно сложная процедура определения начального адреса соответствую­щей подпрограммы обработки прерывания и не менее сложная процедура инициализации рабочих регистров процессора.
4. Сохранение информации о прерванной программе, которую не удалось спа­сти на шаге 2 с помощью действий аппаратуры. В некоторых ВМ предусматривается запоминание довольно большого объема информации о состоянии прерванного процесса.
5. Обработка прерывания. Эта работа может быть выполнена той же подпро­граммой, которой было передано управление на шаге 3, но в ОС чаще всего она реализуется путем последующего вызова соответствующей подпрограммы.
6. Восстановление информации, относящейся к прерванному процессу (этап, обратный шагу 4).
7. Возврат в прерванную программу.
Шаги 1–3 реализуются аппаратно, а шаги 4–7 – программно.
При возникновении запроса на прерывание естествен­ный ход вычислений нарушается и управление передается программе обработ­ки возникшего прерывания. При этом средствами аппаратуры сохраняется (как правило, с помощью механизмов стековой памяти) адрес той команды, начиная с которой следует продолжить выполнение прерванной программы. После выполнения про­граммы обработки прерывания управление возвращается прерванной ранее про­грамме посредством занесения в указатель команд сохраненного адреса команды. Однако такая схема используется только в самых простых программных средах. В многозадачных ОС обработка прерываний происхо­дит по более сложным схемам.
Итак, главные функции механизма прерываний:
– распознавание или классификация прерываний;
– передача управления соответственно обработчику прерываний;
– корректное возвращение к прерванной программе.
Переход от прерываемой программы к обработчику и обратно должен выпол­няться как можно быстрей. Одним из быстрых методов является использование таблицы, содержащей перечень всех допустимых для данной ВМ прерываний и адресов соответствующих обработчиков. Для корректного возвращения к пре­рванной программе перед передачей управления обработчику прерываний со­держимое регистров процессора запоминается либо в памяти с прямым досту­пом, либо в системном стеке.
Прерывания, возникающие при работе ВМ, можно разде­лить на два основных класса: внешние (их иногда называют асинхронными) и внутренние (синхронные).
Внешние прерывания вызываются асинхронными событиями, которые происхо­дят вне прерываемого процесса, например:
– прерывания от таймера;
– прерывания от внешних устройств (прерывания по вводу-вы-воду);
– прерывания по нарушению питания;
– прерывания от другого процессора или другой ВМ.
Внутренние прерывания вызываются событиями, которые связаны с работой про­цессора и являются синхронными с его операциями. Примерами являются сле­дующие запросы на прерывания:
– при нарушении адресации (в адресной части выполняемой команды указан запрещенный или несуществующий адрес, обращение к отсутствующему сег­менту или странице при организации механизмов виртуальной памяти);
– при наличии в поле кода операции незадействованной двоич-ной комбинации;
– при делении на нуль;
– при переполнении или исчезновении порядка;
– при обнаружении средствами контроля ошибок четности, ошибок в работе различных внутренних аппаратных устройств.
В не­которых ВМ часть команд может использовать только операционная система, а не пользо­ватели. Соответственно в аппаратуре предусмотрены различные режимы работы, при этом пользовательские программы выполняются в режиме, в котором названные привиле­гированные команды не исполняются. При попытке использовать команду, за­прещенную в данном режиме, происходит внутреннее прерывание. К привилегированным командам относятся и ко­манды переключения режима работа центрального процессора.
Наконец, существуют собственно программные прерывания. Эти прерывания возникают по особой команде про­цессора, выполнение которой приводит к переходу на новую последовательность исполняемых инструкций. То есть по этой команде про­цессор осуществляет практически те же действия, что и при обычных внутрен­них прерываниях. Данный механизм был специально введен для того, чтобы переключение на системные программные модули происходило не просто как переход в подпрограмму, а точно таким же образом, как и обычное прерывание. Этим обеспечивается автоматическое переключениепроцессора в привилегиро­ванный режим с возможностью исполнения любых команд.
Сигналы, вызывающие прерывания, формируются вне процессора или в самом процессоре. Они могут возникать одновременно. Выбор одного из них для обра­ботки осуществляется на основе приоритетов, приписанных каждому типу пре­рывания. Очевидно, что прерывания от схем контроля процессора должны обла­дать наивысшим приоритетом (если аппаратура работает неправильно, то не имеет смысла продолжать обработку информации). Учет приоритета может быть встроен в технические средства, а также определяться ОС, то есть кроме аппаратно реализованных приоритетов прерываний большинство вычислительных машин и систем допускают программно-аппаратное управление порядком обработки сигналов прерывания. Второй способ, дополняя первый, позволяет применять различные дисциплины обслуживания прерываний.
Наличие сигнала прерывания не обязательно должно вызывать прерывание исполняющейся программы. Процессор может обладать средствами защиты от прерываний: отключение системы прерываний, маскирование (запрет) отдель­ных сигналов прерывания. Программное управление этими средствами использует специальные команды для управления работой системы прерываний и по­зволяет операционной системе регулировать обработку сигналов прерывания, заставляя процессор обрабатывать их сразу по приходу, откладывать их обработ­ку на некоторое время или полностью игнорировать. Обычно операция прерыва­ния выполняется только после завершения выполнения текущей команды. По­скольку сигналы прерывания возникают в произвольные моменты времени, то на момент прерывания может существовать несколько сигналов прерывания, ко­торые могут быть обработаны только последовательно. Чтобы обработать сигна­лы прерывания в разумном порядке им (как уже отмечалось) присваиваются приоритеты. Сигнал с более высоким приоритетом обрабатывается в первую очередь, обработка остальных сигналов прерывания откладывается.
Программное управление специальными регистрами маски (так называемое маскирование сиг­налов прерывания) позволяет реализо-вать различные дисциплины обслужива­ния, такие как:
1) дисциплина обслужива­ния с относительными приоритетами, то есть обслуживание не прерывается даже при наличии запросов с более высокими приоритетами. После окончания об­служивания данного запроса обслуживается запрос с наивысшим приоритетом. Для организации такой дисциплины необходимо в программе обслуживания данного запроса наложить маски на все остальные сигналы прерывания или просто отключить систему прерываний;
2) дисциплина обслужива­ния с абсолютными приоритетами, то есть всегда обслуживается прерывание с наивысшим приоритетом. Для реализации этого режима необходимо на вре­мя обработки прерывания замаскировать все запросы с более низким приори­тетом. При этом возможно многоуровневое прерывание, то есть прерывание программ обработки прерываний. Число уровней прерывания в этом режиме изменяется и зависит от приоритета запроса;
3) дисциплина обслужива­ния по принципу стека (дисциплина LCFS или, по-другому, LIFO), то есть когда запросы с более низким приоритетом могут прерывать обработку прерывания с более вы­соким приоритетом. Для этого необходимо не накладывать маски ни на один сигнал прерывания и не выключать систему прерываний.
Следует особо отметить, что для правильной реализации последних двух дисци­плин нужно обеспечить полное маскирование системы прерываний при выпол­нении шагов 1–4 и 6–7. Это необходимо для того, чтобы не потерять запрос и правильно его обслужить. Многоуровневое прерывание должно происходить на этапе собственно обработки прерывания, а не на этапе перехода с одного процес­са на другой.
Управление ходом выполнения задач со стороны ОС заключается в организации реакций на прерывания, в организации обмена информацией (данными и про­граммами), предоставлении необходимых ресурсов, в динамике выполнения за­дачи и в организации сервиса. Причины прерываний определяет сама ОС, она же и выполняет действия, необходимые при данном прерывании и в данной ситуации. Поэтому в состав любой ОС реального времени прежде всего входят программы управления сис­темой прерываний, контроля состояний задач и событий, синхронизации задач, средства распределения памяти и управления ею, а уже потом средства органи­зации данных (с помощью файловых систем и т. д.).
Как уже было сказано, при появлении запроса на прерывание система прерываний идентифицирует сигнал и, если прерывания разрешены, управление передается на соответствующую подпрограмму обработки прерывания. В подпро­грамме обработки прерывания имеются две служебные секции. Это первая секция, в которой осуществляется сохранение контекста прерванной задачи, не сохраненного на шаге 2, и последняя, заключительная секция, в которой, наоборот, осуществляется восстановление контекста. Для того, чтобы система прерываний не среагировала повторно на сигнал запроса на прерывание, она обычно автоматически «закрывает» (отключает) прерывания, поэтому необ­ходимо потом в подпрограмме обработки прерываний вновь включать систему прерываний. Установка рассмотренных режимов обработки прерываний (с отно­сительными и абсолютными приоритетами, по дисциплине LCFS) осуществляется в конце первой секции подпрограмм обработки прерываний. Таким образом, на время вы­полнения центральной секции (в случае работы в режимах с абсолютными при­оритетами) прерывания разрешены. На время работы заключительной секции подпрограммы обработки прерывания система прерываний должна быть отключена и после восстановления контекста вновь включена. Поскольку эти действия необходимо выполнять практически в каждой подпрограмме обра­ботки прерываний, во многих операционных системах первые секции подпро­грамм обработки прерываний выделяются в специальный системный программ­ный модуль, называемый супервизором прерываний.
Супервизор прерываний прежде всего сохраняет в дескрипторе текущей задачи рабочие регистры процессора, определяющие контекст прерываемого вычисли­тельного процесса. Далее он определяет ту подпрограмму, которая должна выполнить действия, связанные с обслуживанием настоящего (текущего) запроса на прерывание. Наконец, перед тем как передать управление этой подпрограмме, супервизор прерываний устанавливает необходимый режим обработки прерыва­ния. После выполнения подпрограммы обработки прерывания управление вновь передается супервизору, на этот раз уже на тот модуль, который занимается дис­петчеризацией задач. И уже диспетчер задач, в свою очередь, в соответствии с принятым режимом распределения процессорного времени (между выполняю­щимися процессами) восстанавливает контекст той задачи, которой будет решено выделить процессор. Для прямого непосредственного возврата достаточно адрес возврата сохра­нить в стеке, что и делает аппаратура процессора. При этом стек легко обеспе­чивает возможность возврата в случае вложенных прерываний, поскольку он всегда реализует дисциплину LCFS.
Однако если бы контекст процессов сохранялся просто в стеке, как это обычно реализуется аппаратурой, а не в описанных выше дескрипторах задач, то не имелось бы возможности гибко подходить к выбору той задачи, которой нужно пе­редать процессор после завершения работы подпрограммы обработки прерыва­ния. Естественно, что это только общий принцип. В конкретных процессорах и в конкретных ОС могут существовать некоторые отступления от рассмотренной схемы и (или) дополнения к ней. Например, в современных процессорах часто имеются специальные аппаратные возможности для сохранения контекста прерываемого процесса непосредственно в его дескрипторе, то есть дескриптор про­цесса (по крайней мере его часть) становится структурой данных, которую под­держивает аппаратура.

1.3. Межпроцессное взаимодействие

Существенное значение имеет возможность взаимодействия процессов между собой. Например, один процесс может передавать данные другому процессу, или несколько процессов могут обрабатывать данные из общего файла. Во всех этих случаях возникает проблема синхронизации процессов, которая может решаться приостановкой и активизацией процессов, организацией очередей, блокированием и освобождением ресурсов. Пренебрежение вопросами синхронизации процессов, выполняющихся в режиме многозадачности, может привести к их неправильной работе или даже к «краху» системы.
Сложность проблемы синхронизации состоит в нерегулярности возникающих ситуаций. Очень часто все определяется взаимными скоростями процессов и моментами их прерывания. Поэтому отладка взаимодействующих процессов является сложной задачей. Ситуации, когда два или более процессов обрабатывают разделяемые данные, и конечный результат зависит от соотношения скоростей процессов, называются гонками.
Важным понятием синхронизации процессов является понятие «критическая секция» или «критическая область» программы. Критическая секция (критическая область) – это часть программы, в которой осуществляется доступ к разделяемым данным. Для исключения эффекта гонок по отношению к некоторому ресурсу, необходимо обеспечивать такие ситуации, чтобы в каждый момент времени в критической секции, связанной с этим ресурсом, находился максимум один процесс. Этот прием называют взаимным исключением. Простейший способ обеспечить взаимное исключение – позволить процессу, находящемуся в критической секции, запрещать все прерывания. Однако этот способ далеко не всегда пригоден, так как весьма опасно доверять управление системой пользовательскому процессу: он может надолго занять процессор, а при «крахе» процесса в критической области «крах» потерпит вся система, потому что прерывания никогда не будут разрешены.
Другим способом взаимного исключения является использование блокирующих переменных. При этом каждому разделяемому ресурсу ставится в соответствие двоичная переменная, которая принимает, например, значение 0, если ресурс свободен (то есть ни один из процессов не находится в данный момент в критической секции, связанной с данным процессом), и значение 1, если ресурс занят.
Если все процессы реализуются с использованием вышеописанных соглашений, то взаимное исключение гарантируется. Необходимо подчеркнуть, что операция проверки и установки блокирующей переменной должна быть неделимой, что может быть пояснено на следующем примере.
Пусть в результате проверки переменной процесс определил, что ресурс свободен, но сразу после этого, не успев установить переменную в 1, был прерван. За время его приостановки другой процесс занял ресурс, вошел в свою критическую секцию, но также был прерван, не завершив работы с разделяемым ресурсом. Когда управление было возвращено первому процессу, он, считая ресурс свободным, установил признак занятости и начал выполнять свою критическую секцию. Таким образом был нарушен принцип взаимного исключения, что потенциально может привести к нежелательным последствиям.
Во избежание указанных ситуаций в системе команд машины следует иметь единую команду «проверка-установка», или же реализовывать системными средствами соответствующие программные примитивы, которые бы запрещали прерывания на протяжении всей операции проверки и установки.
Реализация критических секций с использованием блокирующих переменных имеет существенный недостаток: в течение времени, когда один процесс находится в критической секции, другой процесс, которому требуется тот же ресурс, будет выполнять рутинные действия по опросу блокирующей переменной, бесполезно тратя процессорное время. Такую постоянную проверку иногда называют активным ожиданием. При этом блокирующая переменная (блокировка), использующая активное ожидание, называется спин-блокировкой. Для устранения таких ситуаций может быть использован так называемый аппарат событий. С помощью этого средства могут решаться не только проблемы взаимного исключения, но и более общие задачи синхронизации процессов. В разных операционных системах аппарат событий реализуется по-своему, но в любом случае используются системные функции аналогичного назначения.
Например, если ресурс занят, то процесс не выполняет циклический опрос, а вызывает некоторую системную функцию wait(D), где D обозначает событие, заключающееся в освобождении ресурса D. Функция wait(D) переводит активный процесс в состояние «ожидание» и делает отметку в его дескрипторе о том, что процесс ожидает события D. Процесс, который в это время использует ресурс D, после выхода из критической секции выполняет системную функцию post(D), в результате чего операционная система просматривает очередь ожидающих процессов и переводит процесс, ожидающий события D, в состояние «готовность».
Для организации синхронизации процессов могут применяться специальные механизмы высокого уровня, блокирующие процесс, ожидающий входа в критическую область или наступления своей очереди использования совместного ресурса. Один из подобных механизмов – механизм так называемых семафоров, являющихся определенным обобщением понятия блокирующих переменных. Семафоры представляют собой такой тип переменных, значение которых может быть нулем (в случае отсутствия сохраненных сигналов активизации процессов) или некоторым положительным числом, соответствующим количеству отложенных активизирующих сигналов.
Для работы с семафорами применяются две операции: down и up. Операция down сравнивает значение семафора с нулем. Если значение семафора больше нуля, операция down уменьшает его (то есть расходует один из сохраненных сигналов активации) и просто возвращает управление. Если значение семафора равно нулю, процедура down не возвращает управление процессу, а процесс переводится в состояние ожидания. Все операции проверки значения семафора, его изменения и перевода процесса в состояние ожидания выполняются как единое и неделимое элементарное действие. Тем самым гарантируется, что после начала операции ни один процесс не получит доступа к семафору до окончания или блокирования операции. Элементарность операции чрезвычайно важна для разрешения пробле­мы синхронизации и предотвращения состояния состязания.
Операция up увеличивает значение семафора. Если с этим семафором связаны один или несколько ожидающих процессов, которые не могут завершить более раннюю операцию down, один из них выбирается системой (например, случайным образом) и ему разрешается завершить свою операцию down. Таким образом, после операции up, примененной к семафору, связанному с несколькими ожидающими процессами, значение семафора так и останется равным 0, но число ожидающих процессов уменьшится на единицу. Операция увеличения значения семафора и активизации процесса тоже неделима. Ни один процесс не может быть блокиро­ван во время выполнения операции up.
Часто используется упрощенная версия семафора, называемая мьютексом (от англоязычного термина mutex, происходящего от сокращения словосочетания mutual exclusion – «взаимное исключение»).
Мьютекс не может считать сигналы, а может лишь управлять взаимным исключением доступа к совместно используемым ресурсам. Реализация мьютекса проста и эффективна. Мьютекс является переменной, которая может находиться в одном из двух состояний: блокированном или неблокированном. Поэтому для описания мьютекса требует­ся всего один бит, хотя чаще используется целая переменная, у которой 0 означает неблокированное состояние, а все остальные значения соответствуют блокирован­ному состоянию. Значение мьютекса устанавливается двумя процедурами. Если процесс собирается войти в критическую секцию, он вызывает проце­дуру mutex_lock. Если мьютекс не заблокирован (то есть вход в критическую секцию разрешен), запрос выполняется и вызывающий процесс может попасть в критическую секцию. Напротив, если мьютекс заблокирован, вызывающий процесс блокируется до тех пор, пока другой процесс, находящийся к критической секции, не выйдет из нее, вызвав процедуру mutex_unlock. Если мьютекс блокирует несколько процессов, то из них случайным образом выбирается один.
Одним из наиболее простых, удобных и интуитивных интерфей­сов межпроцессного взаимодействия является буфер обмена. Буфер обмена может содержать в себе один информаци­онный объект – фрагмент текста, рисунок и т. д. С помощью сис­темного вызова процесс может получить копию информации, со­держащейся в буфере обмена, или сам поместить объект в буфер, при этом старое содержимое буфера теряется. Таким образом, про­граммы получают простой, но эффективный способ взаимного обмена инфор­мацией во время своей работы.
Существенной проблемой синхронизации процессов являются взаимные блокировки (взаимоблокировки) или тупики, называемые также дедлоками (deadlock) или клинчами (clinch). Ниже описан характерный пример взаимоблокировки.
Пусть двум процессам А и В, выполняющимся в режиме многозадачности, для выполнения их работы нужно два ресурса, например, принтер и диск. И пусть после того, как процесс А занял принтер (установил блокирующую переменную), он был прерван. Управление получил процесс В, который сначала занял диск, но при выполнении следующей команды был заблокирован, так как принтер оказался уже занятым процессом А. Управление снова получил процесс А, который в соответствии со своей программой сделал попытку занять диск и был заблокирован: диск уже распределен процессу В. В таком тупиковом положении процессы А и В могут находиться сколь угодно долго.
Процессы в зависимости от соотношения собственных скоростей могут либо совершенно независимо использовать разделяемые ресурсы, либо образовывать очереди к разделяемым ресурсам, либо взаимно блокировать друг друга. Тупиковые ситуации следует отличать от простых очередей, хотя те и другие возникают при совместном использовании ресурсов и внешне похожи друг на друга: процесс приостанавливается и ждет освобождения ресурса. Однако очередь – это нормальное явление, неотъемлемый признак высокого коэффициента использования ресурсов при случайном поступлении запросов. Она возникает тогда, когда ресурс недоступен в данный момент, но через некоторое время он освобождается, и процесс продолжает свое выполнение. Тупик же, что видно из его названия, является в некотором роде неразрешимой ситуацией. В рассмотренном примере тупик (взаимоблокировка) был образован двумя процессами, но взаимно блокировать друг друга может и большее число процессов. Проблема взаимоблокировок включает в себя задачи предотвращения взаимоблокировок, распознавания взаимоблокировок и восстановления системы после взаимоблокировок.
Взаимоблокировки могут быть предотвращены на стадии написания программ, то есть программы должны быть написаны таким образом, чтобы взаимоблокировка не могла возникнуть ни при каком соотношении взаимных скоростей процессов. Так, если бы в рассмотренном выше примере процесс А и процесс В запрашивали ресурсы в одинаковой последовательности, то тупиковая ситуация была бы в принципе невозможна.
Второй подход к предотвращению взаимоблокировок называется динамическим и заключается в использовании определенных правил при назначении ресурсов процессам (например, ресурсы могут выделяться в определенной последовательности, общей для всех процессов).
В некоторых случаях, когда тупиковая ситуация образована многими процессами, использующими множество ресурсов, распознавание взаимоблокировки является нетривиальной задачей. Существуют формальные программно-реализованные методы распознавания взаимоблокировок, основанные на ведении таблиц распределения ресурсов и таблиц запросов к занятым ресурсам. Анализ этих таблиц позволяет обнаружить взаимные блокировки. Если же тупиковая ситуация возникла, то не обязательно снимать с выполнения все заблокированные процессы. Можно снять только часть из них (при этом освобождаются ресурсы, ожидаемые остальными процессами), можно вернуть некоторые процессы в область свопинга (см. раздел 2), можно совершить «откат» некоторых процессов до так называемой контрольной точки, в которой запоминается вся информация, необходимая для восстановления выполнения программы с данного места. Контрольные точки расставляются в программе в тех местах, после которых возможно возникновение тупика.
Для того, чтобы облегчить написание корректных программ, было предложено высокоуровневое средство синхронизации, называемое монитором. В данном случае под монитором понимается набор процедур, переменных и структур данных. Процессы могут вызывать процедуры монитора, но не имеют доступа к внутренним данным монитора. Мониторы имеют важное свойство, которое делает их полезными для достижения взаимного исключения: только один процесс может быть активным по отношению к монитору. Компилятор обрабатывает вызовы процедур монитора особым образом. Обычно, когда процесс вызывает процедуру монитора, то первые несколько инструкций этой процедуры проверяют, не активен ли какой-либо другой процесс по отношению к этому монитору. Если да, то вызывающий процесс приостанавливается, пока другой процесс не освободит монитор. Таким образом, исключение входа нескольких процессов в монитор реализуется не программистом, а компилятором, что делает ошибки менее вероятными.
1.4. Понятия потока («нити») и многопоточности

Когда говорят о процессах, то тем самым хотят отметить, что операци­онная система поддерживает их обособленность: у каждого процесса имеется свое виртуальное адресное пространство, каждому процессу назначаются свои ресур­сы – файлы, окна, семафоры и так далее. Такая обособленность нужна для того, чтобы защитить один процесс от другого, поскольку они, совместно используя все ре­сурсы ВМ, конкурируют друг с другом. В общем случае процессы могут быть никак не связаны между собой и даже могут принадлежать раз­ным пользователям, разделяющим одну вычислительную машину. Другими сло­вами, в случае процессов ОС считает их совершенно несвязанными и независимыми. При этом именно ОС берет на себя роль арбитра в конкуренции между процессами по поводу ресурсов.
Однако желательно иметь еще и возможность задействовать внутренний парал­лелизм, который может быть в самих процессах. Такой внутренний параллелизм встречается достаточно часто, а его использование позволяет ускорить реализацию процессов. Например, некоторые операции, выполняемые приложением, могут требо­вать для своего исполнения достаточно длительного использования центрального процессора. В этом случае при интерактивной работе с приложением пользова­тель вынужден долго ожидать завершения заказанной операции и не может управ­лять приложением до тех пор, пока операция не выполнится до самого конца. Такие ситуации встречаются достаточно часто, например, при обработке боль­ших изображений в графических редакторах. Если же программные модули, исполняющие такие длительные операции, оформлять в виде самостоятельных «подпроцессов» (так называемых легковесных или облегченных процессов), которые будут выполняться парал­лельно с другими подобными «подпроцессами», то у пользователя по­является возможность параллельно выполнять несколько операций в рамках од­ного приложения (процесса). Такие «подпроцессы» принято называть потоками или «нитями» (thread). «Подпроцессы» (потоки) называют легковесными потому, что операционная система не должна для них организовывать полноценную вирту­альную машину. Потоки не имеют своих собственных ресурсов, они развива­ются в том же виртуальном адресном пространстве, могут пользоваться теми же файлами, виртуальными устройствами и иными ресурсами, что и данный про­цесс. Единственное, что им необходимо иметь, – это процессорный ресурс. В од­нопроцессорной машине потоки разделяют между собой процессорное время так же, как это делают обычные процессы, а в многопроцессорной машине могут выполняться одновременно, если не встречают конкуренции из-за об­ращения к иным ресурсам.
Многопоточность обеспечивает возможность параллельного вы­полнения нескольких видов операций в одной прикладной программе. Параллель­ные вычисления (а, следовательно, более эффективное использование ресур­сов центрального процессора и меньшее суммарное время выполнения задач) теперь уже часто реализуется на уровне потоков, и программа, оформленная в виде нескольких потоков в рамках одного процесса, может быть выполнена быстрее за счет параллельного выполнения ее отдельных частей. При этом желательно уменьшать взаимо­действие потоков между собой, так как ускорение от одновременного выполнения параллельных потоков может быть сведено к минимуму из-за задержек синхро­низации и обмена данными.
В качестве примера использования многопоточности можно привести электрон­ную таблицу или текстовый процессор. Если они были разработаны с учетом возможностей многопоточной обработки, то пользователь может запросить пересчет своего ра­бочего листа или слияние нескольких документов и одновременно продолжать заполнять таблицу или открывать для редактирования следующий документ. Особенно эффективно можно использовать многопоточ-ность для выполнения распределенных приложений: например, многопоточный сервер может парал­лельно выполнять запросы сразу нескольких клиентов.
Итак, сущность «поток» была введена для того, чтобы именно с помощью этих единиц распределять процессорное время между возможными работами. Сущ­ность «процесс» предполагает, что при диспетчеризации нужно учитывать все ресурсы, закрепленные за ним. А при манипулировании потоками можно менять только их контекст, если происходит переключение с одного потока на другой в рам­ках одного процесса. Все остальные вычислительные ресурсы при этом не затра­гиваются. Каждый процесс всегда состоит по крайней мере из одного потока, и только в случае наличия внутреннего параллелизма становится возможным «расще­пление» этого одного потока на несколько параллельных.
Потребность в потоках возникла еще на однопроцессорных ВМ, поскольку потоки позволяют организовать вычисления более эф­фективно. Для использования достоинств многопроцессорных ВМ с общей памятью потоки уже являются необходимыми, так как позволяют не только реально ускорить выполнение тех задач, которые допускают их естественное распаралле­ливание, но и загрузить процессоры, исключая таким образом их возможное «простаи­вание».
Каждый поток выполняется строго последовательно и имеет свой собственный программный счетчик и стек. Потоки, как и процессы, могут порождать потоки-по­томки, поскольку любой процесс состоит по крайней мере из одного потока. По­добно традиционным процессам (то есть процессам, состоящим из одного потока), каждый поток может находиться в одном из активных состояний. Пока один поток заблокирован (или просто находится в очереди готовых к исполнению задач), другой поток того же процесса может выполняться. Потоки разделяют процессор­ное время так же, как это делают обычные процессы, в соответствии с различны­ми вариантами диспетчеризации.
Все потоки имеют одно и то же виртуальное адресное про­странство своего процесса. Это означает, что они разделяют одни и те же гло­бальные переменные. Поскольку каждый поток может иметь доступ к каждому виртуальному адресу, один поток может исполь-зовать стек другого потока.
Между потоками нет полной защиты, так как это не является необходимым. Все потоки одного процесса всегда решают общую задачу одного поль­зователя, и механизм потоков используется здесь для более быстрого решения задачи путем ее распараллеливания. При этом программисту очень важно полу­чить в свое распоряжение удобные средства организации взаимодействия разных частей одной программы.
Вследствие того, что потоки, относящиеся к одному процессу, выполняются в од­ном и том же виртуальном адресном пространстве, между ними легко организо­вать тесное взаимодействие (в отличие от процессов, для которых нужны специ­альные механизмы взаимодействия). Более того, программист, создающий многопоточное приложение, может заранее продумать работу мно­жества потоков процесса таким образом, чтобы они могли взаимодействовать наи­более выгодным способом, а не участвовать в конкуренции за предоставление ресурсов тогда, когда этого можно избежать.

Резюме

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

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

Порядок взаимосвязи процессов определяется правилами синхро­низации. Часть программы, в которой осуществляется доступ к разделяемым данным, называется критической секцией (критической областью) программы. Ситуации, когда два или более процессов обрабатывают разделяемые данные, и конечный результат зависит от соотношения скоростей процессов, называются гонками. Исключение эффекта гонок по отношению к некоторому ресурсу обеспечивается различными способами взаимного исключения, такими как использование механизмов блокирующих переменных, семафоров, мьютексов.
Существенной проблемой синхронизации процессов являются их взаимные блокировки или тупики. Проблема взаимоблокировок требует решения задач предотвращения взаимоблокировок, распознавания взаимоблокировок и восстановления системы после взаимоблокировок. Высокоуровневым средством синхронизации процессов, исключающим взаимоблокировки, является так называемый монитор, который представляет собой набор процедур, переменных и структур данных.
Потоки («нити») представляют собой «легковесную» (или «облегченную») форму процессов. Потоки в отличие от процессов не имеют своих собственных ресурсов, они развива­ются в том же виртуальном адресном пространстве, могут пользоваться теми же файлами, виртуальными устройствами и иными ресурсами, что и данный про­цесс. Единственное, что им необходимо иметь, – это процессорный ресурс. Многопоточность обеспечивает возможность параллельного вы­полнения несколько видов операций в одной прикладной программе. Параллель­ные вычисления способствуют более эффективному использованию ресур­сов центрального процессора и существенно уменьшают суммарное время выполнения программ.









Контрольные вопросы и задания

1. Дайте определение понятиям «вычислительный процесс» и «ресурс».
2. Как классифицируются процессы и ресурсы?
3. Решение каких задач включает в себя планирование процессов?
4. Перечислите виды состояний процессов.
5. Что такое контекст и дескриптор процесса?
6. Каковы возможные причины выхода процесса из состояния выполнения?
7. Охарактеризуйте наиболее распространенные алгоритмы плани-рования процессов.
8. В чем заключается сущность алгоритмов планирования процес-сов, основанных на квантовании?
9. Опишите различия между относительными и абсолютными прио-ритетами.
10. Дайте определение понятиям вытесняющая и невытесняющая многозадачность.
11. Дайте определение понятию «прерывание».
12. Какие этапы реализуются механизмом обработки прерываний?
13. Приведите примеры внешних прерываний.
14. Какими событиями вызываются внутренние прерывания?
15. Поясните понятие программного прерывания.
16. Охарактеризуйте применяемые дисциплины обслуживания прерываний.
17. В чем заключается механизм синхронизации процессов?
18. Что такое «критическая область» программы и с какими целями она используется?
19. С помощью каких механизмов осуществляется взаимодействие процессов?
20. В чем различие понятий очередей процессов и взаимоблоки-ровок процессов?
21. Перечислите способы преодоления тупиковых ситуаций при взаимодействии процессов.
22. Опишите понятие монитора как высокоуровневого средства синхронизации процессов.
23. В чем различие понятий «процесс» и «поток»?
24. С какой целью в ОС используется механизм потоков («нитий»)?
2. Управление ресурсами в автономных
однопроцессорных компьютерах

2.1. Управление памятью

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

В первом случае замену виртуальных адресов на физические делает специальная системная программа – перемещающий загрузчик. Перемещающий загрузчик на основании имеющихся у него исходных данных о начальном адресе физической памяти, в которую предстоит загружать процесс, и предоставленной транслятором информации об адресно-зависимых константах программы, выполняет загрузку процесса, совмещая ее с заменой виртуальных адресов физическими.
Второй способ заключается в том, что процесс загружается в память в неизмененном виде в виртуальных адресах, при этом операционная система фиксирует смещение действительного расположения программного кода относительно виртуального адресного пространства. Во время выполнения процесса при каждом обращении к оперативной памяти выполняется преобразование виртуального адреса в физический. Второй способ является более гибким, он допускает перемещение процесса во время выполнения, в то время как перемещающий загрузчик жестко привязывает процесс к первоначально выделенному участку памяти. Вместе с тем использование перемещающего загрузчика уменьшает накладные расходы, так как преобразование каждого виртуального адреса происходит только один раз во время загрузки, а во втором случае – каждый раз при обращении по данному адресу.
В некоторых случаях (обычно в специализированных системах), когда заранее точно известно, в какой области оперативной памяти будет выполняться процесс, транслятор выдает исполняемый код сразу в физических адресах.
Все методы управления памятью могут быть разделены на два класса: методы, которые используют перемещение процессов между оперативной памятью и диском, и методы, которые не делают этого. Начнем с последнего, более простого класса методов.
Самым простым способом управления оперативной памятью является разделение ее на несколько разделов фиксированной величины. Это может быть выполнено вручную администратором во время старта системы или во время ее генерации. Очередной процесс, поступивший на выполнение, помещается либо в общую очередь, либо в очередь к некоторому разделу. Подсистема управления памятью в этом случае выполняет следующие задачи:
1) выбирает свободный раздел, размер которого достаточен для размещения поступившего на выполнение процесса;
2) осуществляет загрузку процесса и настройку адресов.
При очевидном преимуществе – простоте реализации – данный метод имеет существенный недостаток, которым является его «жесткость». Так как в каждом разделе может размещаться только один процесс, то степень «многозадачности» заранее ограничена числом разделов не зависимо от того, какой размер требуется процессам. Другим существенным недостатком является то, что в предлагае­мой схеме очень часто может наблюдаться так называемая фрагментация памяти, то есть потери части памяти разделов вследствие того, что процессы не полностью занимают выделенные им разделы. Например, если процессу требуется небольшой объем, но ему выделяется целый раздел, то оставшаяся память этого раздела не будет использоваться (то есть в этом разделе останется неиспользуе­мый фрагмент памяти). Снижение же степени фрагментации па­мяти за счет уменьшения размеров разделов может на определенном этапе привести к тому, что в малые разделы не смогут быть за­гружены процессы, для которых требуются более объемные разделы. Кроме того, недостат­ком рассматриваемого способа управления оперативной памятью является и то, что если даже весь объем оперативной памяти машины позволяет разместить некоторый процесс, требующий большого объема, наличие фик­сированного разбиения памяти на разделы не позволит сделать это, так как требуемый объем может превосходить по размеру отдельные (пусть и очень большие), но фиксированные разделы памяти.
В случае распределения памяти разделами переменной величины память ВМ не делится заранее на разделы. Сначала вся оперативная память свободна. Каждому вновь поступившему процессу выделяется необходимая память. Если достаточный объем памяти отсутствует, то процесс не принимается на выполнение и стоит в очереди. После завершения процесса память освобождается, и на это место может быть загружен другой процесс. Таким образом, в произвольный момент времени оперативная память представляет собой случайную последовательность занятых и свободных участков (разделов) произвольного размера.
Задачами операционной системы при реализации данного метода управления памятью являются следующие:
1) ведение таблиц свободных и занятых областей, в которых указываются начальные адреса и размеры участков памяти;
2) при поступлении нового процесса – анализ запроса, просмотр таблицы свободных областей и выбор раздела, размер которого достаточен для размещения поступившего процесса;
3) загрузка процесса в выделенный раздел и корректировка таблиц свободных и занятых областей;
4) после завершения процесса – корректировка таблиц свободных и занятых областей.
Выбор раздела для вновь поступившего процесса может осуществляться по разным правилам, таким, например, как «первый попавшийся раздел достаточного размера», или «раздел, имеющий наименьший достаточный размер», или «раздел, имеющий наибольший достаточный размер». Все эти правила имеют свои преимущества и недостатки.
По сравнению с методом распределения памяти фиксированными разделами метод распределения памяти разделами переменной величины обладает гораздо большей гибкостью, но ему также присуща фрагментация памяти. Это связано с образованием через определенныйпромежуток времени функционирования системы некоторого числа несмежных участков свободной памяти (фрагментов), образовавшихся в результате выгрузки выполнившихся процессов, и загрузки на их место новых процессов, требующих меньшие объемы памяти. При этом может возникнуть ситуация, при которой ни для одного из вновь поступающих процессов не будет иметься достаточного участка памяти, хотя суммарный объем фрагментов может составить значительную величину, намного превышающую требуемый процессу объем памяти.
Одним из методов борьбы с фрагментацией является перемещение всех занятых участков в сторону старших либо в сторону младших адресов, так, чтобы вся свободная память образовывала единую свободную область. В дополнение к функциям, которые выполняет ОС при распределении памяти переменными разделами, в данном случае она должна еще время от времени копировать содержимое разделов из одного места памяти в другое, корректируя таблицы свободных и занятых областей. Эта процедура называется «сжатием». Сжатие может выполняться либо при каждом завершении процесса, либо только тогда, когда для вновь поступившего процесса нет свободного раздела достаточного размера. В первом случае требуется меньше вычислительной работы при корректировке таблиц, а во втором – реже выполняется процедура сжатия. Так как процессы перемещаются по оперативной памяти в ходе своего выполнения, то преобразование адресов из виртуальной формы в физическую должно выполняться динамическим способом. Хотя процедура сжатия и приводит к более эффективному использованию памяти, она может потребовать значительного времени, что часто является тем недостатком, который преобладает над преимуществами данного метода.
Развитие методов организации вычислительного процесса привело к появлению метода, использующего так называемую виртуальная память. В общем случае виртуальным называется ресурс, который пользователю или пользовательской программе представляется обладающим некими свойствами, которыми он в действительности не обладает. Так, например, пользователю может быть предоставлена виртуальная оперативная память, размер которой превосходит всю имеющуюся в системе реальную оперативную память. Пользователь пишет программы так, как будто в его распоряжении имеется однородная оперативная память большого объема, но в действительности для реализации программы предоставляются несколько разнородных запоминающих устройств, включая дисковые устройства.
Таким образом, виртуальная память – это совокупность программно-аппаратных средств, позволяющих пользователям писать программы, которые для своей реализации требуют такие объемы памяти, которые превосходят реально существующие объемы оперативной памяти ВМ. Для этого виртуальная память решает следующие задачи:
1) размещает процессы в запоминающих устройствах разного типа, например, часть – в оперативной памяти, а часть – на диске;
2) перемещает процессы по мере необходимости между запоминающими устройствами разного типа, например, подгружает процесс или его часть с диска в оперативную память;
3) преобразует виртуальные адреса в физические.
Все эти действия выполняются автоматически, без участия программиста, то есть механизм виртуальной памяти является прозрачным по отношению к пользователю.
Наиболее распространенными способами реализации виртуальной памяти является страничное, сегментное и странично-сегментное распределение памяти, а также свопинг.
При страничном распределении памяти виртуальное адресное пространство каждого процесса делится на части одинакового, фиксированного для данной системы размера. Эти части называют виртуальными страницами. В общем случае размер виртуального адресного пространства не является кратным размеру страницы, поэтому последняя страница каждого процесса дополняется фиктивной областью. Вся оперативная память машины также делится на части такого же размера, называемые физическими страницами (или блоками). Размер страницы обычно выбирается равным степени числа 2, например: 512, 1024 и т.д. Это позволяет упростить механизм преобразования адресов.
При загрузке процесса часть его виртуальных страниц помещается в оперативную память, а остальные – на диск. Смежные виртуальные страницы не обязательно располагаются в смежных физических страницах. При загрузке операционная система создает для каждого процесса информационную структуру – таблицу страниц, в которой устанавливается соответствие между номерами виртуальных и физических страниц для страниц, загруженных в оперативную память, или делается отметка о том, что виртуальная страница выгружена на диск. Кроме того, в таблице страниц содержится управляющая информация, такая как признак модификации страницы, признак невыгружаемости (выгрузка некоторых страниц может быть запрещена), признак обращения к странице (используется для подсчета числа обращений за определенный период времени) и другие данные, формируемые и используемые механизмом виртуальной памяти.
При активизации очередного процесса в специальный регистр процессора загружается адрес таблицы страниц данного процесса. При каждом обращении к памяти происходит чтение из таблицы страниц информации о виртуальной странице, к которой произошло обращение. Если данная виртуальная страница находится в оперативной памяти, то выполняется преобразование виртуального адреса в физический. Если же нужная виртуальная страница в данный момент выгружена на диск, то происходит так называемое страничное прерывание. Выполняющийся процесс переводится в состояние ожидания, и активизируется другой процесс из очереди готовых. Параллельно программа обработки страничного прерывания находит на диске требуемую виртуальную страницу и пытается загрузить ее в оперативную память. Если в памяти имеется свободная физическая страница, то загрузка выполняется немедленно, если же свободных страниц нет, то решается вопрос, какую страницу следует выгрузить из оперативной памяти.
В данной ситуации может быть использовано много разных критериев выбора, наиболее популярные из них – «первая попавшаяся страница» или «страница, к которой в последнее время было меньше всего обращений».
В некоторых системах используется понятие рабочего множества страниц. Рабочее множество определяется для каждого процесса и представляет собой перечень наиболее часто используемых страниц, которые должны постоянно находиться в оперативной памяти и поэтому не подлежат выгрузке.
После того, как выбрана страница, которая должна покинуть оперативную память, анализируется ее признак модификации (по таблице страниц). Если выталкиваемая страница с момента загрузки была модифицирована, то ее новая версия должна быть переписана на диск. Если нет, то она может быть просто уничтожена, то есть соответствующая физическая страница объявляется свободной.
При каждом обращении к оперативной памяти с помощью аппаратных средств выполняются следующие действия:
1) на основании начального адреса таблицы страниц (содержимое регистра адреса таблицы страниц), номера виртуальной страницы (старшие разряды виртуального адреса) и длины записи в таблице страниц (системная константа) определяется адрес нужной записи в таблице;
2) из этой записи извлекается номер физической страницы;
3) к номеру физической страницы присоединяется смещение (младшие разряды виртуального адреса).
Использование в последнем пункте того факта, что размер страницы равен степени числа 2, позволяет применить операцию конкатенации (присоединения) вместо более длительной операции сложения, что уменьшает время получения физического адреса, а, следовательно, повышает производительность системы.
На производительность системы со страничной организацией памяти влияют затраты времени, связанные с обработкой страничных прерываний и преобразованием виртуального адреса в физический. При часто возникающих страничных прерываниях система может тратить большую часть времени впустую. Чтобы уменьшить частоту страничных прерываний, следовало бы увеличивать размер страницы. Кроме того, увеличение размера страницы уменьшает размер таблицы страниц, а значит, уменьшает затраты памяти. С другой стороны, если страница велика, следовательно, велика и фиктивная область в последней виртуальной странице каждого процесса. В среднем на каждом процессе теряется половина объема страницы, что в сумме при большой странице может составить существенную величину. Время преобразования виртуального адреса в физический в значительной степени определяется временем доступа к таблице страниц. В связи с этим таблицу страниц стремятся размещать в «быстрых» запоминающих устройствах. Это может быть, например, набор специальных регистров или память, использующая для уменьшения времени доступа ассоциативный поиск и кэширование данных.
Страничное распределение памяти может быть реализовано в упрощенном варианте без выгрузки страниц на диск. В этом случае все виртуальные страницы всех процессов постоянно находятся в оперативной памяти. Такой вариант страничной организации хотя и не предоставляет пользователю виртуальной памяти, но почти исключает фрагментацию за счет того, что, во-первых, программа может загружаться в несмежные области, а, во-вторых, при загрузке виртуальных страниц никогда не образуется остатков.
При сегментном распределение памяти виртуальное адресное пространство процесса делится на сегменты, размер которых определяется программистом с учетом смыслового значения содержащейся в них информации. Отдельный сегмент может представлять собой подпрограмму, массив данных и т.п. Иногда сегментация выполняется по умолчанию компилятором. При загрузке процесса часть сегментов помещается в оперативную память (при этом для каждого из этих сегментов операционная система подыскивает подходящий участок свободной памяти), а часть сегментов размещается в дисковой памяти. Сегменты одного процесса могут занимать в оперативной памяти несмежные участки. Во время загрузки система создает таблицу сегментов процесса (аналогичную таблице страниц), в которой для каждого сегмента указывается начальный физический адрес сегмента в оперативной памяти, размер сегмента, правила доступа, признак модификации, признак обращения к данному сегменту за последний интервал времени и некоторая другая информация. Если виртуальные адресные пространства нескольких процессов включают один и тот же сегмент, то в таблицах сегментов этих процессов делаются ссылки на один и тот же участок оперативной памяти, в который данный сегмент загружается в единственном экземпляре.
Система с сегментной организацией функционирует аналогично системе со страничной организацией: время от времени происходят прерывания, связанные с отсутствием нужных сегментов в памяти, при необходимости освобождения памяти некоторые сегменты выгружаются, при каждом обращении к оперативной памяти выполняется преобразование виртуального адреса в физический. Кроме того, при обращении к памяти проверяется, разрешен ли доступ требуемого типа к данному сегменту.
Недостатком данного метода распределения памяти является фрагментация на уровне сегментов и более медленное преобразование адреса по сравнению со страничной организацией памяти.
Странично-сегментное распределение памяти представляет собой комбинацию страничного и сегментного распределения памяти и, вследствие этого, сочетает в себе достоинства обоих подходов. Виртуальное пространство процесса делится на сегменты, а каждый сегмент в свою очередь делится на виртуальные страницы, которые нумеруются в пределах сегмента. Оперативная память делится на физические страницы. Загрузка процесса выполняется операционной системой постранично, при этом часть страниц размещается в оперативной памяти, а часть на диске. Для каждого сегмента создается своя таблица страниц, структура которой полностью совпадает со структурой таблицы страниц, используемой при страничном распределении. Для каждого процесса создается таблица сегментов, в которой указываются адреса таблиц страниц для всех сегментов данного процесса. Адрес таблицы сегментов загружается в специальный регистр процессора, когда активизируется соответствующий процесс.
Разновидностью виртуальной памяти является так называемый свопинг (swapping). В соответствии с методом свопинга некоторые процессы (обычно находящиеся в состоянии ожидания) временно выгружаются на диск. Планировщик операционной системы не исключает их из своего рассмотрения, и при наступлении условий активизации некоторого процесса, находящегося в области свопинга на диске, этот процесс перемещается в оперативную память. Если свободного места в оперативной памяти не хватает, то выгружается другой процесс.
При свопинге, в отличие от рассмотренных ранее методов реализации виртуальной памяти, процесс перемещается между памятью и диском целиком, то есть в течение некоторого времени процесс может полностью отсутствовать в оперативной памяти. Существуют различные алгоритмы выбора процессов на загрузку и выгрузку, а также различные способы выделения оперативной и дисковой памяти загружаемому процессу.
Рассмотрим иерархию запоминающих устройств (ЗУ) и принцип кэширования информации.
Память ВМ представляет собой иерархию ЗУ, включающую внутренние регистры процессора, различные типы сверхоперативной, оперативной и постоянной памяти, внешнюю память на магнитных дисках и других типах устройств. Разные типы ЗУ отличаются средним временем доступа и стоимостью хранения данных в расчете на один бит.
Кэширование информации – это способ организации совместного функционирования двух типов ЗУ, отличающихся временем доступа и стоимостью хранения данных, который позволяет уменьшить среднее время доступа к данным за счет динамического копирования наиболее часто используемой информации из относительно более «медленного» ЗУ в более «быстрое» ЗУ. Кэш-памятью обычно называют одно из таких взаимодействующих устройств, а именно «быстрое» ЗУ. Оно стоит дороже и, как правило, имеет сравнительно небольшой объем. Таким образом кэширование представляет собой некоторое компромиссное решение проблемы стоимости и быстродействия памяти. Важно, что механизм кэш-памяти является прозрачным для пользователя, то есть пользователь не должен сообщать никаких сведений об интенсивности использования информации и никак не должен участвовать в перемещении информации из ЗУ одного типа в ЗУ другого типа. Все это делается автоматически системными средствами.
Представим частный случай использования кэш-памяти для уменьшения среднего времени доступа к данным, хранящимся в оперативной памяти. Для этого между процессором и оперативной памятью помещается «быстрое» ЗУ – кэш-память. Содержимое кэш-памяти представляет собой совокупность записей обо всех загруженных в нее элементах данных. Каждая запись об элементе данных включает в себя адрес, который этот элемент данных имеет в оперативной памяти, и управляющую информацию: признак модификации и признак обращения к данным за некоторый последний период времени.
В системах, оснащенных кэш-памятью, каждый запрос к оперативной памяти выполняется в соответствии со следующим алгоритмом:
1) просматривается содержимое кэш-памяти с целью определения, не находятся ли нужные данные в кэш-памяти (кэш-память не является адресуемой, поэтому поиск нужных данных осуществляется по содержимому – значению поля «адрес в оперативной памяти», взятому из запроса);
2) если данные обнаруживаются в кэш-памяти, то они считываются из нее, и результат передается в процессор;
3) если нужных данных нет, то они вместе со своим адресом копируются из оперативной памяти в кэш-память, и результат выполнения запроса передается в процессор.
При копировании данных может оказаться, что в кэш-памяти нет свободного места, тогда для вытеснения из кэш-памяти выбираются данные, к которым в последний период было меньше всего обращений. Если вытесняемые данные были модифицированы за время нахождения в кэш-памяти, то они переписываются в оперативную память. Если же эти данные не были модифицированы, то их место в кэш-памяти объявляется свободным.
На практике в кэш-память считывается не один элемент данных, к которому произошло обращение, а целый блок данных. Это увеличивает вероятность так называемого «попадания в кэш», то есть нахождения нужных данных в кэш-памяти. В реальных системах вероятность попадания в кэш составляет примерно 0,9.
Высокое значение вероятности нахождения данных в кэш-памяти связано с наличием у данных объективных свойств: пространственной и временной локальности.
Под пространственной локальностью данных в кэш-памяти понимается такой случай, когда произошло обращение по некоторому адресу и с высокой степенью вероятности в ближайшее время произойдет обращение к соседним адресам.
Под временной локальностью данных в кэш-памяти понимается случай большой вероятности обращения по этому же адресу в ближайшее время.
Все предыдущие рассуждения справедливы и для других пар запоминающих устройств, например, для оперативной памяти и внешней памяти. В этом случае уменьшается среднее время доступа к данным, расположенным, например, на магнитном диске, а роль кэш-памяти выполняет буфер в оперативной памяти.
2.2. Управление вводом-выводом

Одной из главных функций ОС является управление всеми устройствами ввода-вывода ВМ. ОС должна передавать устройствам команды, перехватывать прерывания и обрабатывать ошибки; она также должна обеспечивать интерфейс между устройствами и остальной частью машины. В целях развития интерфейс должен быть одинаковым для всех типов устройств (принцип независимости от устройств).
Устройства ввода-вывода делятся на два типа: блок-ориентированные устройства и байт-ориентированные устройства.
Блок-ориентированные устройства ввода-вывода хранят информацию в блоках фиксированного размера, каждый из которых имеет свой собственный адрес. Самое распространенное блок-ориентированное устройство – диск.
Байт-ориентированные устройства ввода-вывода не адресуемы и не позволяют производить операцию поиска, они генерируют или потребляют последовательность байтов. Примерами являются мониторы, принтеры, сетевые адаптеры. Однако некоторые внешние устройства не относятся ни к одному классу, например, часы, которые, с одной стороны, не адресуемы, а с другой стороны, не порождают потока байтов. Это устройство только выдает сигнал прерывания в некоторые моменты времени.
Любое внешнее устройство обычно состоит из механического и электронного компонента. Электронный компонент называют контроллером устройства или адаптером. Механический компонент представляет собственно устройство. Некоторые контроллеры могут управлять несколькими устройствами. Если интерфейс между контроллером и устройством стандартизован, то независимые производители могут выпускать совместимые как контроллеры, так и устройства.
ОС обычно имеет дело не с устройством, а с его контроллером. Контроллер, как правило, выполняет простые функции, например, преобразует поток бит в блоки (состоящие из байт), осуществляют контроль и исправление ошибок. Каждый контроллер имеет несколько регистров, которые используются для взаимодействия с центральным процессором. В некоторых ВМ эти регистры являются частью физического адресного пространства. В таких ВМ нет специальных операций ввода-вывода. В других машинах адреса регистров ввода-вывода, называемых часто портами, образуют собственное адресное пространство за счет введения специальных операций ввода-вывода.
ОС выполняет ввод-вывод, записывая команды в регистры контроллера. Когда команда принята, процессор оставляет контроллер и занимается другой работой. При завершении команды контроллер организует прерывание для того, чтобы передать управление процессором операционной системе, которая должна проверить результаты операции. Процессор получает результаты и статус устройства, читая информацию из регистров контроллера.
Основная идея организации программного обеспечения ввода-вывода состоит в разбиении его на несколько уровней, причем нижние уровни обеспечивают экранирование особенностей аппаратуры от верхних, а те, в свою очередь, обеспечивают удобный интерфейс для пользователей. Ключевым принципом является независимость от устройств ввода-вывода. Вид программы не должен зависеть от того, читает ли она данные с гибкого диска или с жесткого диска.
Очень близкой к идее независимости от устройств является идея единообразного именования, то есть для именования устройств должны быть приняты единые правила.
Другим важным вопросом для программного обеспечения ввода-вывода является обработка ошибок. В лучшем случае ошибки следует обрабатывать как можно ближе к аппаратуре. Если контроллер обнаруживает ошибку чтения, то он должен попытаться ее скорректировать. Если же это ему не удается, то исправлением ошибок должен заняться драйвер устройства. Многие ошибки могут исчезать при повторных попытках выполнения операций ввода-вывода, например, ошибки, вызванные наличием пылинок на головках чтения или на диске. И только если нижний уровень не может справиться с ошибкой, он сообщает об ошибке верхнему уровню.
Еще один ключевой вопрос – это использование блокирующих (синхронных) и неблокирующих (асинхронных) передач. Большинство операций физического ввода-вывода выполняется асинхронно – процессор начинает передачу и переходит на другую работу, пока не наступает прерывание. Пользовательские программы намного легче писать, если операции ввода-вывода блокирующие. ОС выполняет операции ввода-вывода асинхронно, но представляет их для пользовательских программ в синхронной форме.
Проблема состоит также в том, что одни устройства ввода-вывода являются разделяемыми, а другие – выделенными. Диски – это разделяемые устройства, так как одновременный доступ нескольких пользователей к диску не является проблемой. Принтеры – это выделенные устройства, потому что нельзя смешивать строчки, печатаемые различными пользователями. Наличие выделенных устройств создает для операционной системы некоторые проблемы.
Для решения имеющихся проблем целесообразно разделить программное обеспечение ввода-вывода на четыре слоя:
1) обработка прерываний;
2) драйверы устройств;
3) слой операционной системы, независимый от устройств;
4) пользовательский слой программного обеспечения.
Прерывания должны быть скрыты как можно глубже в «недрах» ОС, чтобы как можно меньшая часть ОС имела с ними дело. Наилучший способ состоит в разрешении процессу, инициировавшему операцию ввода-вывода, блокировать себя до завершения операции и наступления прерывания. При наступлении прерывания процедура обработки прерывания выполняет разблокирование процесса, инициировавшего операцию ввода-вывода. В любом случае эффект от прерывания будет состоять в том, что ранее заблокированный процесс теперь продолжит свое выполнение.
Весь зависимый от устройства код помещается в драйвер устройства. Каждый драйвер управляет устройствами одного типа или, может быть, одного класса. В ОС, как правило, только драйвер устройства обладает информацией о конкретных особенностях какого-либо устройства. Например, только драйвер диска имеет дело с дорожками, секторами, цилиндрами, временем установления головки и другими факторами, обеспечивающими правильную работу диска.
Драйвер устройства принимает запрос от устройств программного слоя и решает, как его выполнить. Типичным запросом является, например, чтение N блоков данных. Если драйвер был свободен во время поступления запроса, то он начинает выполнять запрос немедленно. Если же он был занят обслуживанием другого запроса, то вновь поступивший запрос присоединяется к очереди уже имеющихся запросов, и он будет выполнен, когда наступит его очередь.
Первый шаг в реализации запроса ввода-вывода, например, для диска, состоит в преобразовании его из абстрактной формы в конкретную. Для дискового драйвера это означает, во-первых, преобразование номеров блоков в номера цилиндров, головок, секторов, во-вторых, проверку, работает ли двигатель, находится ли головка над нужным цилиндром и т.п. Короче говоря, он должен решить, какие операции контроллера нужно выполнить и в какой последовательности. После передачи команды контроллеру драйвер должен решить следующий вопрос: блокировать ли себя до окончания заданной операции или нет. Если операция занимает значительное время (например, при печати некоторого блока данных), то драйвер блокируется до тех пор, пока операция не завершится, и обработчик прерывания не разблокирует его. Если команда ввода-вывода выполняется быстро (например, прокрутка экрана), то драйвер ожидает ее завершения без блокирования.
Большая часть программного обеспечения ввода-вывода является независимой от устройств. Точная граница между драйверами и программами, независимыми от устройств, определяется системой, так как некоторые функции, которые могли бы быть реализованы независимым способом, в действительности выполнены в виде драйверов для повышения эффективности или по другим причинам.
Типичными функциями слоя операционной системы, независимого от устройств, являются:
– обеспечение общего интерфейса к драйверам устройств;
– именование устройств;
– защита устройств;
– обеспечение независимого размера блока;
– буферизация;
– распределение памяти на блок-ориентированных устройствах;
– распределение и освобождение выделенных устройств;
– уведомление об ошибках.
Остановимся на некоторых функциях данного перечня. Верхним слоям программного обеспечения неудобно работать с блоками разной величины, поэтому данный слой обеспечивает единый размер блока, например, за счет объединения нескольких различных блоков в единый логический блок. В связи с этим верхние уровни имеют дело с абстрактными устройствами, которые используют единый размер логического блока независимо от размера физического сектора.
При создании файла или заполнении его новыми данными необходимо выделить ему новые блоки. Для этого ОС должна вести список или битовую карту свободных блоков диска. На основании информации о наличии свободного места на диске может быть разработан алгоритм поиска свободного блока, независимый от устройства и реализуемый программным слоем, находящимся выше слоя драйверов.
Хотя большая часть программного обеспечения ввода-вывода находится внутри ОС, некоторая его часть содержится в библиотеках, связываемых с пользовательскими программами. Системные вызовы, включающие вызовы ввода-вывода, обычно делаются библиотечными процедурами. В частности, форматирование ввода или вывода выполняется библиотечными процедурами. Стандартная библиотека ввода-вывода содержит значительное число процедур, которые выполняют ввод-вывод и работают как часть пользовательской программы.
Другой категорией программного обеспечения ввода-вывода является подсистема спулинга (spooling). Спулинг – это способ работы с выделенными устройствами в многозадачной системе. Одно из типичных устройств, требующих спулинга, – принтер. Хотя технически легко позволить каждому пользовательскому процессу открыть специальный файл, связанный с принтером, такой способ опасен из-за того, что пользовательский процесс может монополизировать принтер на произвольное время. Вместо этого создается специальный процесс-монитор, который получает исключительные права на использование этого устройства. Также создается специальный каталог, называемый каталогом спулинга. Для того, чтобы напечатать файл, пользовательский процесс помещает выводимую информацию в этот файл и помещает его в каталог спулинга. Процесс-монитор по очереди распечатывает все файлы, содержащиеся в каталоге спулинга.
Запись или чтение большого количества информации из ад-ресного пространства ввода-вывода (например, с диска) приводят к большому количеству операций ввода-вывода, которые должен выполнять процессор. Для освобождения процессора от операций последовательного вывода данных из оперативной памяти или последовательного ввода в нее был предложен механизм прямого доступа внешних устройств к памяти – ПДП или DMA (Direct Memory Access). Суть работы этого механизма заключается в следующем. Для того чтобы какое-либо устройство, кроме процессора, могло за­писать информацию в память или прочитать ее из памяти, необходимо чтобы это устройство могло забрать у процессора управление локальной магистралью для выставления соответствующих сигналов на шины адреса, данных и управления. Для централизации эти обязанности обычно возла­гаются не на каждое устройство в отдельности, а на специальный контрол­лер – контроллер прямого доступа к памяти. Контроллер прямого доступа к памяти имеет несколько спаренных линий – каналов DMA, которые мо­гут подключаться к различным устройствам. Перед началом использова­ния прямого доступа к памяти этот контроллер необходимо запрограмми­ровать, записав в его порты информацию о том, какой канал или каналы предполагается задействовать, какие операции они будут совершать, ка­кой адрес памяти является начальным для передачи информации и какое количество информации должно быть передано. Получив по одной из ли­ний – каналов DMA – сигнал запроса на передачу данных от внешнего уст­ройства, контроллер по шине управления сообщает процессору о желании взять на себя управление локальной магистралью. Процессор (возможно, через некоторое время, необходимое для завершения его действий с маги­стралью) передает управление маги­стралью контроллеру DMA, известив его специ­альным сигналом. Контроллер DMA выставляет на адресную шину адрес памяти для передачи очередной порции информации и по второй линии канала прямого доступа к памяти сообщает устройству о готовности маги­страли к передаче данных. После этого, используя шину данных и шину управления, контроллер DMA, устройство ввода-вывода и память осуще­ствляют процесс обмена информацией. Затем контроллер прямого досту­па к памяти извещает процессор о своем отказе от управления магистра­лью, и тот берет руководящие функции на себя. При передаче большого количества данных весь процесс повторяется циклически.
При прямом доступе к памяти процессор и контроллер DMA по очереди управляют локальной магистралью. Это, конечно, несколько снижает производительность процессора, так как при выполнении некоторых команд или при чтении очередной порции команд во внутренний кэш он должен дожидаться освобождения магистрали, но в целом производительность ВМ существенно возрастает.
При подключении к системе нового устройства, которое умеет ис­пользовать прямой доступ к памяти, обычно необходимо программно или аппаратно задать номер канала DMA, к которому будет приписано уст­ройство. В отличие от прерываний, где один номер прерывания может соот­ветствовать нескольким устройствам, каналы DMA всегда находятся в монопольном владении устройств.
2.3. Управление файлами и файловая система

Под файлом обычно понимают набор данных, организованных в виде совокупности записей одинаковой структуры. Для управления этими дан­ными создаются соответствующие системы управления файлами. Возможность иметь дело с логическим уровнем структуры данных и операций, выполняемых над ними в процессе их обработки, предоставляет файловая система. Таким об­разом, файловая система – это набор спецификаций и соответствующее им про­граммное обеспечение, которые отвечают за создание, уничтожение, организацию, чтение, запись, модификацию и перемещение файловой информации, а также за управление доступом к файлам и за управление ресурсами, которые используют­ся файлами. Именно файловая система определяет способ организации данных на диске или на каком-нибудь ином носителе данных.
Следует различать файловую систему и систе­му управления файлами. Система управления файлами является основной подсистемой в абсолютном большинстве современных ОС (хотя в принципе можно обхо­диться и без нее). Во-первых, через систему управления файлами связываются по данным все системные обрабатывающие программы. Во-вторых, с помощью этой системы решаются проблемы централизованного распределения дискового про­странства и управления данными. В-третьих, благодаря использованию той или иной системы управления файлами пользователям предоставляются следующие возможности:
– создание, удаление, переименование (и другие операции) именованных набо­ров данных (именованных файлов) из своих программ или посредством спе­циальных управляющих программ, реализующих функции интерфейса пользо­вателя с его данными и активно использующих систему управления файлами;
– работа с не дисковыми периферийными устройствами как с файлами;
– обмен данными между файлами, между устройствами, между файлом и уст­ройством (и наоборот);
– работа с файлами с помощью обращений к программным модулям системы управления файлами;
– защита файлов от несанкционированного доступа.
В некоторых ОС может быть несколько систем управления файлами, что обеспе­чивает им возможность работать с несколькими файловыми системами. Очевид­но, что системы управления файлами, будучи компонентом ОС, не являются не­зависимыми от этой ОС, поскольку они активно используют соответствующие вызовы прикладного программного интерфейса API (application program interface). С другой стороны, системы управления файлами сами дополняют API новыми вызовами. Можно сказать, что основное назначение файловой системы и соот­ветствующей ей системы управления файлами – организация удобного доступа к данным, организованным как файлы, то есть вместо низкоуровневого доступа к данным с указанием конкретных физических адресов нужной записи ис­пользуется логический доступ с указанием имени файла и записи в нем.
Другими словами, термин «файловая система» определяет, прежде всего, прин­ципы доступа к данным, организованным в файлы. Этот же термин часто исполь­зуют и по отношению к конкретным файлам, расположенным на том или ином носителе данных. А термин «система управления файлами» следует употреблять по отношению к конкретной реализации файловой системы. То есть система управления файлами – это ком­плекс программных модулей, обеспечивающих работу с файлами в конкретной операционной системе.
Еще раз подчеркнем, что любая система управления файлами не существу­ет сама по себе – она разработана для функционирования в конкретной ОС. То есть, для работы с файла­ми, организованными в соответствии с некоторой файловой системой, для каж­дой ОС должна быть разработана соответствующая система управления файлами. Эта система управления файлами будет работать только в той ОС, для которой она и создана. Но при этом она позволит работать с файлами, созданными с по­мощью системы управления файлами другой ОС и организованными в файловую систему по тем же основ­ным принципам.
Для того чтобы можно было загрузить с магнитного диска собственно саму ОС, а уже с ее помощью и организовать работу той или иной системы управления файлами, были приняты специальные системные соглашения о структуре диска. Информация на магнитных дисках размещается и передается бло­ками. Каждый такой блок называется сектором (sector), секторы расположены на концентрических дорожках поверхности диска. Каждая дорожка (track) обра­зуется при вращении магнитного диска под зафиксированной в некотором пред­определенном положении магнитной головкой чтения-записи (head). Накопитель на жестких магнитных дисках (НЖМД) содержит один или более дисков. Обычно под термином «жесткий диск» понимают весь пакет магнитных дисков. Группы дорожек (треков) одного радиуса, расположенных на поверхностях разных маг­нитных дисков, образуют так называемые цилиндры (cylinder).
Каждый сектор состоит из поля данных и поля служебной информации, ограни­чивающей и идентифицирующей его. Размер сектора (точнее – емкость поля данных) устанавливается контроллером или драйвером. Физический адрес сектора на магнитном диске определяется с помощью трех «координат», то есть представляется триадой [c–h–s], где с – номер цилиндра (дорожки на по­верхности диска), h – номер рабочей поверхности диска (магнитной го­ловки), s – номер сектора на дорожке. Номер цилиндра лежит в диапа­зоне 0 . . . С–1, где С – количество цилиндров. Номер рабочей поверхности диска находится в диапазоне 0 . . . Н–1, где Н – число магнитных головок в накопителе. Номер сектора на дорожке определяется в диапазоне 1 . . . S, где S – количество секторов на дорожке. Например, триада [1–0–2] адресует сектор 2 на рабочей поверхности 0 цилиндра 1.
Обмен информацией между оперативной памятью и дисками физически осуществ­ляется только секторами. Вся совокупность физических секторов на НЖМД представляет его так называемую неформатированную емкость.
Жесткий диск может быть разбит на несколько разделов (partition), которые затем могут использоваться либо одной ОС, либо различными ОС. Причем в каждом разделе может быть органи­зована своя файловая система. Однако для организации даже единственной файловой системы необходимо определить, по крайней мере, один раздел.
Разделы диска могут быть двух типов – primary (обычно этот термин переводят как первичный) и extended (расширенный). При этом на диске обязательно должен быть по крайней мере один primary-раздел. Если primary-разделов несколько, то только один из них может быть активным. Именно загрузчику, расположенному в активном разделе, передается управление при включении ВМ и загрузке ОС.
Файлы идентифицируются именами. Пользователи дают файлам символьные имена, при этом учитываются ограничения ОС как на используемые символы, так и на длину имени. До недавнего времени эти границы были весьма узкими. Современные файловые системы, как правило, поддерживают длинные символьные имена файлов. При переходе к длинным именам возникает проблема совместимости с ранее созданными приложениями, использующими короткие имена. Чтобы приложения могли обращаться к файлам в соответствии с принятыми ранее соглашениями, файловая система должна уметь предоставлять эквивалентные короткие имена (псевдонимы) файлам, имеющим длинные имена. Таким образом, одной из важных задач становится проблема генерации соответствующих коротких имен.
Обычно разные файлы могут иметь одинаковые символьные имена. В этом случае файл однозначно идентифицируется так называемым составным именем, представляющем собой последовательность символьных имен каталогов. В некоторых системах одному и тому же файлу не может быть дано несколько разных имен, а в других такое ограничение отсутствует. В последнем случае операционная система присваивает файлу дополнительно уникальное имя, так, чтобы можно было установить взаимнооднозначное соответствие между файлом и его уникальным именем. Уникальное имя представляет собой числовой идентификатор и используется программами операционной системы.
Файлы бывают разных типов: обычные файлы, специальные файлы, файлы-каталоги.
Обычные файлы в свою очередь подразделяются на текстовые и двоичные. Текстовые файлы состоят из строк символов, представленных в ASCII-коде. Это могут быть документы, исходные тексты программ и т.п. Текстовые файлы можно прочитать на экране и распечатать на принтере. Двоичные файлы не используют ASCII-коды, они часто имеют сложную внутреннюю структуру, например, объектный код программы или архивный файл. Все операционные системы должны уметь распознавать хотя бы один тип файлов – их собственные исполняемые файлы.
Специальные файлы – это файлы, ассоциированные с устройствами ввода-вывода, которые позволяют пользователю выполнять операции ввода-вывода, используя обычные команды записи в файл или чтения из файла. Эти команды обрабатываются вначале программами файловой системы, а затем на некотором этапе выполнения запроса преобразуются ОС в команды управления соответствующим устройством. Специальные файлы, так же как и устройства ввода-вывода, делятся на блок-ориентированные и байт-ориентированные.
Каталог – это, с одной стороны, группа файлов, объединенных пользователем исходя из некоторых соображений (например, файлы, содержащие программы игр, или файлы, составляющие один программный пакет), а с другой стороны – это файл, содержащий системную информацию о группе файлов, его составляющих. В каталоге содержится список файлов, входящих в него, и устанавливается соответствие между файлами и их характеристиками (атрибутами).
В разных файловых системах могут использоваться в качестве атрибутов файлов разные характеристики, такие, например, как информация о разрешенном доступе, пароль для доступа к файлу, владелец файла, создатель файла, признак «только для чтения», признак «скрытый файл», признак «системный файл», признак «архивный файл», признак «двоичный/символьный», признак «временный», признак блокировки, длина записи, указатель на ключевое поле в записи, длина ключа, время создания, время последнего доступа, время последнего изменения, текущий размер файла, максимальный размер файла.
Каталоги могут непосредственно содержать значения характеристик файлов или ссылаться на таблицы, содержащие эти характеристики. Каталоги могут образовывать иерархическую структуру за счет того, что каталог более низкого уровня может входить в каталог более высокого уровня.
Структурная организация каталогов может быть представлена в виде иерархического дерева или сети. Каталоги образуют дерево, если файлу разрешено входить только в один каталог, и сеть – если файл может входить сразу в несколько каталогов. Как и любой другой файл, каталог имеет символьное имя и однозначно идентифицируется составным именем, содержащим цепочку символьных имен всех каталогов, через которые проходит путь от корня до данного каталога.
Программист имеет дело с логической организацией файла, представляя файл в виде определеннымобразом организованных логических записей. Логическая запись – это наименьший элемент данных, которым может оперировать программист при обмене с внешним устройством. Даже если физический обмен с устройством осуществляется большими единицами, операционная система обеспечивает программисту доступ к отдельной логической записи. Записи могут быть фиксированной длины или переменной длины. Записи могут быть расположены в файле последовательно (последовательная организация) или в более сложном порядке, с использованием так называемых индексных таблиц, позволяющих обеспечить быстрый доступ к отдельной логической записи (индексно-последовательная организация). Для идентификации записи может быть использовано специальное поле записи, называемое ключом.
Физическая организация файла описывает правила расположения файла на устройстве внешней памяти, в частности, на диске. Файл состоит из физических записей – блоков. Блок (как уже было отмечено выше) – наименьшая единица данных, которой внешнее устройство обменивается с оперативной памятью. В некоторых ОС такая наименьшая единица обмена называется кластером. При этом кластер может состоять из нескольких блоков.
Непрерывное размещение – простейший вариант физической организации, при котором файлу предоставляется последовательность блоков диска, образующих единый непрерывный участок дисковой памяти. Для задания адреса файла в этом случае достаточно указать только номер начального блока. Другое достоинство этого метода – простота. Но имеются и два существенных недостатка. Во-первых, во время создания файла заранее не известна его длина, а значит не известно, сколько памяти надо зарезервировать для этого файла, во-вторых, при таком порядке размещения неизбежно возникает фрагментация, и пространство на диске используется не эффективно, так как отдельные участки маленького размера (минимально 1 блок) могут остаться не используемыми.
Другой способ физической организации файлов – размещение файлов в виде связанного списка блоков дисковой памяти. При таком способе в начале каждого блока содержится указатель на следующий блок. В этом случае адрес файла также может быть задан одним числом – номером первого блока. В отличие от предыдущего способа, каждый блок может быть присоединен в цепочку какого-либо файла, следовательно фрагментация отсутствует. Файл может изменяться во время своего существования, наращивая число блоков. Недостатком является сложность реализации доступа к произвольно заданному месту файла: например, для того, чтобы прочитать пятый по порядку блок файла, необходимо последовательно прочитать четыре первых блока, прослеживая цепочку номеров блоков. Кроме того, при этом способе количество данных файла, содержащихся в одном блоке, не равно степени числа 2 (одно слово израсходовано на номер следующего блока), а многие программы читают данные блоками, размер которых равен степени числа 2.
Следующим способом физической организации файлов является использование так называемого связанного списка индексов. При этом с каждым блоком связывается некоторый элемент – индекс. Индексы располагаются в отдельной области диска. Если некоторый блок распределен некоторому файлу, то индекс этого блока содержит номер следующего блока данного файла. При такой физической организации сохраняются все достоинства предыдущего способа, но снимаются оба отмеченных недостатка: во-первых, для доступа к произвольному месту файла достаточно прочитать только блок индексов, отсчитать нужное количество блоков файла по цепочке и определить номер нужного блока, и, во-вторых, данные файла занимают блок целиком (за исключением последних блоков файла).
Еще один способ физической организации файлов заключается в простом перечислении номеров кластеров (блоков), занимаемых данным файлом. Этот перечень и служит адресом файла. Недостаток такого способа в том, что длина адреса зависит от размера файла и для относительно большого файла может составить значительную величину. Достоинство же является высокая скорость доступа к произвольному кластеру (блоку) файла, так как в этом случае применяется прямая адресация, которая исключает просмотр цепочки указателей при поиске адреса произвольного кластера (блока) файла. Фрагментация на уровне кластеров (блоков) при этом способе отсутствует. В некоторых файловых системах для сокращения объема адресной информации прямой способ адресации сочетается с косвенным. При этом используется дерево таблиц кластеров.
Рассмотрим понятие прав доступа к файлу. Определить права доступа к файлу – значит определить для каждого пользователя набор операций, которые он может применить к данному файлу. В разных файловых системах может быть определен свой список дифференцируемых операций доступа. Этот список может включать следующие операции: создание файла, уничтожение файла, открытие файла, закрытие файла, чтение файла, запись в файл, дополнение файла, поиск в файле, получение атрибутов файла, установление новых значений атрибутов, переименование, выполнение файла, чтение каталога и другие операции с файлами и каталогами.
В файловых системах пользователи могут быть разделены на отдельные категории. Для всех пользователей одной категории определяются единые права доступа.
Различают два основных подхода к определению прав доступа:
1) избирательный доступ, когда для каждого файла и каждого пользователя сам владелец может определить допустимые операции;
2) мандатный доступ, когда система наделяет пользователя определенными правами по отношению к каждому разделяемому ресурсу (в данном случае файлу) в зависимости от того, к какой группе пользователь отнесен.
В некоторых файловых системах запросы к внешним устройствам, в которых адресация осуществляется блоками (диски, ленты), перехватываются промежуточным программным слоем – подсистемой буферизации. Подсистема буферизации представляет собой буферный пул, располагающийся в оперативной памяти, и комплекс программ, управляющих этим пулом. Каждый буфер пула имеет размер, равный одному блоку. При поступлении запроса на чтение некоторого блока подсистема буферизации просматривает свой буферный пул и, если находит требуемый блок, то копирует его в буфер запрашивающего процесса. Операция ввода-вывода считается выполненной, хотя физического обмена с устройством не происходило. Очевиден выигрыш во времени доступа к файлу. Если же нужный блок в буферном пуле отсутствует, то он считывается с устройства и одновременно с передачей запрашивающему процессу копируется в один из буферов подсистемы буферизации. При отсутствии свободного буфера на диск вытесняется наименее редко используемая информация. Таким образом, подсистема буферизации работает по принципу кэш-памяти.
Функционирование любой файловой системы можно представить ой моделью, в которой каждый уровень предоставляет некоторый интерфейс (набор функций) вышележащему уровню, а сам, в свою очередь, для выполнения своей работы использует интерфейс нижележащего уровня.
Задачей символьного уровня является определение по символьному имени файла его уникального имени. В файловых системах, в которых каждый файл может иметь только одно символьное имя, этот уровень отсутствует, так как символьное имя, присвоенное файлу пользователем, является одновременно уникальным и может быть использовано операционной системой. В других файловых системах, в которых один и тот же файл может иметь несколько символьных имен, на данном уровне просматривается цепочка каталогов для определения уникального имени файла.
На следующем, базовом, уровне по уникальному имени файла определяются его характеристики: права доступа, адрес, размер и другие. Как уже было сказано, характеристики файла могут входить в состав каталога или храниться в отдельных таблицах. При открытии файла его характеристики перемещаются с диска в оперативную память, чтобы уменьшить среднее время доступа к файлу. В некоторых файловых системах при открытии файла вместе с его характеристиками в оперативную память перемещаются несколько первых блоков файла, содержащих данные.
Следующим этапом реализации запроса к файлу является проверка прав доступа к нему. Для этого сравниваются полномочия пользователя или процесса, выдавших запрос, со списком разрешенных видов доступа к данному файлу. Если запрашиваемый вид доступа разрешен, то выполнение запроса продолжается, если нет, то выдается сообщение о нарушении прав доступа.
На логическом уровне определяются координаты запрашиваемой логической записи в файле, то есть определяется, на каком расстоянии (в байтах) от начала файла находится требуемая логическая запись. При этом абстрагируются от физического расположения файла: он представляется в виде непрерывной последовательности байт. Алгоритм работы данного уровня зависит от логической организации файла.
На физическом уровне файловая система определяет номер физического блока, который содержит требуемую логическую запись, и смещение логической записи в физическом блоке. Для решения этой задачи используются результаты работы логического уровня – смещение логической записи в файле, адрес файла на внешнем устройстве, а также сведения о физической организации файла, включая размер блока. Подчеркнем, что задача физического уровня решается независимо от того, как был логически организован файл. После определения номера физического блока, файловая система обращается к системе ввода-вывода для выполнения операции обмена с внешним устройством. В ответ на этот запрос в буфер файловой системы будет передан нужный блок, в котором на основании смещения, полученного при работе физического уровня, выбирается требуемая логическая запись.
По сравнению с доступом к памяти, традиционный доступ к файлам представляется сложным и неудобным. По этой причине некоторые ОС обеспечивают отображение файлов в адресное пространство выполняемого процесса. Это выражается в появлении двух системных вызовов: «отобразить» и «отменить отображение». Первый вызов передает операционной системе в качестве параметров имя файла и виртуальный адрес, и ОС отображает указанный файл в виртуальное адресное пространство по указанному адресу. Отображение файлов лучше всего работает в системе, которая поддерживает сегментацию. В такой системе каждый файл может быть отображен в свой собственный сегмент. С этого момента процесс может копировать сегмент-источник в сегмент-приемник с помощью обычного программного цикла, использующего команды пересылки в памяти. После выполнения копирования процесс может выполнить вызов «отменить отображение» для удаления файла из адресного пространства, а затем завершиться. Выходной файл будет существовать на диске, как если бы он был создан обычным способом.
Хотя отображение файлов исключает потребность в выполнении ввода-вывода и тем самым облегчает программирование, этот способ порождает и некоторые новые проблемы. Во-первых, для системы сложно узнать точную длину выходного файла. Проще указать наибольший номер записанной страницы, но нет способа узнать, сколько байт в этой странице было записано. Вторая проблема проявляется (потенциально), если один процесс отображает файл, а другой процесс открывает его для обычного файлового доступа. Если первый процесс изменяет страницу, то это изменение не будет отражено в файле на диске до тех пор, пока страница не будет вытеснена на диск. От системы в этом случае требуется поддержание согласованности данных файла для этих двух процессов. Третья проблема состоит в том, что файл может быть больше, чем сегмент, и даже больше, чем все виртуальное адресное пространство. Единственный способ ее решения состоит в реализации вызова «отобразить» таким образом, чтобы он мог отображать не весь файл, а его часть. Такая работа, очевидно, менее удобна, чем отображение целого файла.
Разработчики ОС стремятся обеспечить пользователя возможностью работать сразу с несколькими файловыми системами. В новом понимании файловая система состоит из многих составляющих, в число которых входят и файловые системы в традиционном понимании. Современные файловые системы располагают так называемым переключателем файловых систем. Он обеспечивает интерфейс между запросами приложения и конкретной файловой системой, к которой обращается это приложение. Переключатель файловых систем преобразует запросы в формат, воспринимаемый следующим уровнем – уровнем файловых систем.
Каждый компонент уровня файловых систем выполнен в виде драйвера соответствующей файловой системы и поддерживает определенную организацию файловой системы. Переключатель является единственным модулем, который может обращаться к драйверу файловой системы. Приложение не может обращаться к нему напрямую. Драйвер файловой системы может быть написан в виде, позволяющем сразу нескольким приложениям выполнять операции с файлами. Каждый драйвер файловой системы в процессе собственной инициализации регистрируется у переключателя, передавая ему таблицу точек входа, которые будут использоваться при последующих обращениях к файловой системе.
Для выполнения своих функций драйверы файловых систем обращаются к подсистеме ввода-вывода, образующей следующий слой файловой системы новой архитектуры. Подсистема ввода-вывода – это составная часть файловой системы, которая отвечает за загрузку, инициализацию и управление всеми модулями низших уровней файловой системы. Обычно эти модули представляют собой драйверы портов, которые непосредственно занимаются работой с аппаратными средствами. Кроме этого подсистема ввода-вывода обеспечивает некоторый сервис драйверам файловой системы, что позволяет им осуществлять запросы к конкретным устройствам. Подсистема ввода-вывода должна постоянно присутствовать в памяти и организовывать совместную работу иерархии драйверов устройств. В эту иерархию могут входить драйверы устройств определенного типа (драйверы жестких дисков или накопителей на лентах); драйверы, которые перехватывают запросы к блочным устройствам и могут частично изменить поведение существующего драйвера этого устройства, например, зашифровать данные; драйверы портов, которые управляют конкретными адаптерами.
Большое число уровней архитектуры файловой системы обеспечивает авторам драйверов устройств большую гибкость: драйвер может получить управление на любом этапе выполнения запроса – от вызова приложением функции, которая занимается работой с файлами, до того момента, когда работающий на самом низком уровне драйвер устройства начинает просматривать регистры контроллера. Многоуровневый механизм работы файловой системы реализован посредством цепочек вызова устройств. В ходе инициализации драйвер устройства может добавить себя к цепочке вызова некоторого устройства, определив при этом уровень последующего обращения. Подсистема ввода-вывода помещает адрес целевой функции в цепочку вызова устройства, используя заданный уровень для того, чтобы должным образом упорядочить цепочку. По мере выполнения запроса, подсистема ввода-вывода последовательно вызывает все функции, ранее помещенные в цепочку вызова. Внесенная в цепочку вызова процедура драйвера может передать запрос дальше – в измененном или в неизмененном виде – на следующий уровень, или, если это возможно, процедура может удовлетворить запрос, не передавая его дальше по цепочке.


Резюме

Функциями ОС по управлению памятью являются: отслеживание свободной и занятой памяти, выделение памяти процессам и освобождение памяти при завершении процессов, вытеснение процессов из оперативной памяти на диск и возвращение их в оперативную память, настройка адресов программы на конкретную область физической памяти.
Для идентификации команд и переменных используются символьные имена (метки), виртуальные адреса и физические адреса.
Самым простым способом управления оперативной памятью является разделение ее на несколько разделов фиксированной величины. При распределении памяти разделами переменной величины память ВМ заранее не делится на разделы и сначала вся оперативная память свободна. Каждому вновь поступающему процессу выделяется необходимая память.
Эффективным методом управления памятью является применение механизма так называемой виртуальной памяти с использованием, например, дискового пространства. Наиболее распространенными способами реализации виртуальной памяти является страничное, сегментное и странично-сегментное распределение памяти, а также свопинг, когда некоторые процессы, находящиеся в состоянии ожидания, временно целиком выгружаются на диск.
Память ВМ представляет собой иерархию запоминающих устройств, включающую внутренние регистры процессора, различные типы сверхоперативной, оперативной и постоянной памяти, внешнюю память на магнитных дисках и других типах устройств. Разные типы запоминающих устройств отличаются средним временем доступа и стоимостью хранения данных в расчете на один бит.
Кэширование информации – это способ организации совместного функционирования двух типов запоминающих устройств, отличающихся временем доступа и стоимостью хранения данных, который позволяет уменьшить среднее время доступа к данным за счет динамического копирования наиболее часто используемой информации из относительно более «медленного» ЗУ в более «быстрое» ЗУ (называемое кэш-памятью). Кэширование выполняется автоматически системными средствами.

Одной из главных функций ОС является управление всеми устройствами ввода-вывода ВМ, а именно передача устройствам соответствующих команд, перехват прерываний, обработка ошибок, обеспечение интерфейса между устройствами ввода-вывода и остальной частью машины. Устройства ввода-вывода делятся на блок-ориентированные устройства (которые хранят информацию в блоках фиксированного размера и каждый блок имеет свой собственный адрес) и байт-ориентированные устройства (которые не адресуемы и не позволяют производить операцию поиска, а генерируют или потребляют последовательность байтов).
Основная идея организации программного обеспечения ввода-вывода состоит в разбиении его на несколько уровней, причем нижние уровни обеспечивают экранирование особенностей аппаратуры от верхних, а те, в свою очередь, обеспечивают удобный интерфейс для пользователей. При этом ключевым принципом является как можно большая независимость программного обеспечения от конкретного типа устройства ввода-вывода. Весь зависимый от устройства программный код помещается в драйвер устройства. Каждый драйвер управляет устройствами одного типа или, может быть, одного класса.
Устройства ввода-вывода могут быть разделяемыми, допускающими одновременный доступ нескольких пользователей к устройству (например, дисковые устройства), и выделенными, не допускающими одновременную работу с ними разными пользователями (например, устройства печати – принтеры).
Для освобождения процессора от операций последовательного вывода данных из оперативной памяти или последовательного ввода в нее используется механизм прямого доступа внешних устройств к памяти – ПДП или DMA. При этом специальный контрол­лер DMA забирает у процессора управление локальной магистралью для выставления соответствующих сигналов за­писи информации в память или чтения информации из памяти на шины адреса, данных и управления. Контроллер DMA имеет несколько каналов DMA, которые мо­гут подключаться к различным устройствам ввода-вывода.
Под файлом обычно понимают набор данных, организованных в виде совокупности записей одинаковой структуры. Для управления этими дан­ными создаются соответствующие системы управления файлами. Возможность иметь дело с логическим уровнем структуры данных и операций, выполняемых над ними в процессе их обработки, предоставляет файловая система. Файловая система – это набор спецификаций и соответствующее им про­граммное обеспечение, которые отвечают за создание, уничтожение, организацию, чтение, запись, модификацию и перемещение файловой информации, а также за управление доступом к файлам и за управление ресурсами, которые используют­ся файлами. Файлы идентифицируются символьными именами, которые дают им пользователи. При этом учитываются ограничения ОС как на используемые символы, так и на длину имени.
Файлы подразделяются на обычные, специальные и файлы-каталоги. Обычные файлы в свою очередь подразделяются на текстовые и двоичные.
Различают логическую и физическую организации файла. Логическая организация представляет файл в виде определенным образом организованных логических записей. Физическая организация файла описывает правила расположения файла на устройстве внешней памяти, в частности на диске.
Информация на магнитных дисках размещается и передается бло­ками. В некоторых ОС такая наименьшая единица обмена называется кластером. При этом кластер может состоять из нескольких блоков.
Наиболее известными способами физической организации файлов являются непрерывное размещение блоков данного файла на диске, размещение в виде связанного списка блоков дисковой памяти, использование связанного списка индексов, простое перечисление номеров блоков файла.
В разных файловых системах может быть определен свой список прав доступа к файлам, то есть набор операций, которые пользователь может применить к данному файлу.
Функционирование любой файловой системы можно представить многоуровневой моделью, в которой каждый уровень предоставляет некоторый интерфейс (набор функций) вышележащему уровню, а сам, в свою очередь, для выполнения своей работы использует интерфейс нижележащего уровня.
Некоторые ОС обеспечивают отображение файлов в адресное пространство выполняемого процесса.




Контрольные вопросы и задания

1. Охарактеризуйте существующие методы управления оперативной памятью.
2. Какие способы распределения виртуальной памяти чаще всего применяются, в чем их недостатки и преимущества?
3. Укажите отличие блок-ориентированных устройств ввода-вывода от байт-ориентированных?
4. В чем заключается смысл разбиения программного обеспечения ввода-вывода на несколько уровней?
5. Приведите примеры разделяемых и выделенных устройств ввода-вывода.
6. Опишите механизм прямого доступа устройств к памяти.
7. Дайте определение понятиям «система управления файлами» и «файловая система».
8. Какие функции в операционных системах выполняет система управления файлами?
9. Опишите структуру магнитного диска.
10. Дайте понятие логической организации файлов.
11. Охарактеризуйте наиболее известные способы физической организации файлов.
12. Какие уровни составляют многоуровневую модель функционирования файловой системы?
3. Управление процессами и ресурсами в автономных многопроцессорных вычислительных машинах

3.1. Реализация операционных систем многопроцессорных вычислительных машин

В предыдущих разделах рассматривались вопросы реализации ОС, функционирующих на автономных (или так называемых «центра-лизованных») однопроцессорных ВМ. Для решения ряда сложных задач обработки информации спроектированы и практически используются многопроцессорные вычислительные машины (часто для краткости называемые «мультипроцессорами»), в которых устанавливается несколько центральных процессоров, имеющих полный доступ к общей разделяемой памяти. По своему основному содержанию операционные системы для таких многопроцессорных машин представляют собой обычные ОС, выполняющие традиционные функции по обработке системных вызовов, управлению памятью, обслуживанию файловой системы, управлению устройствами ввода-вывода. Главным отличием в реализации ОС для многопроцессорных ВМ является изменение содержания состояния процессов «выполнение». В этом состоянии может находиться не один процесс (как в однопроцессорных ВМ), а сразу несколько процессов, выполняющихся на разных процессорах многопроцессорной ВМ. Это требует более сложных алгоритмов планирования и синхронизации процессов, а также специфических методов организации и управления ресурсами в операционных системах для многопроцессорных ВМ.
Возможны различные варианты организации операционных систем многопроцессорных машин.
Простейший способ организации таких ОС состоит в том, чтобы статически разделить оперативную память по числу цент­ральных процессоров и дать каждому центральному процессору свою собственную память с собственной копией операционной системы. В результате N центральных процессоров будут работать как N независимых машин. В качестве оче­видного варианта оптимизации можно позволить всем центральным процессо­рам совместно использовать код операционной системы и хранить только ин­дивидуальные копии данных. Такая схема лучше, чем N независимых машин, так как она позволяет всем машинам совместно использовать набор дисков и других устройств ввода-вывода, а также обеспечивает гибкое совместное использование памяти. Например, если требуется запустить большую программу, одному из центральных процессоров может быть выделена большая порция памяти на время выполнения этой программы. Кроме того, процессы могут эффективно общаться друг с дру­гом, если одному процессу будет позволено писать данные в память, а другой про­цесс будет их считывать в этом месте. Но с точки зрения теории операционных систем наличие ОС у каждого центрального процессора является крайне примитивным подходом.
Следует отметить следующие аспекты данной схемы.
Во-первых, когда процесс обращается к системному вызову, системный вызов перехватывается и обрабатывается его собственным центральным процес­сором при помощи структур данных в таблицах операционной системы.
Во-вторых, поскольку у каждой операционной системы есть свои собственные таблицы, у нее есть также и свой набор процессов, которые она сама планирует. Совместного использования процессов при этом нет. Если пользователь регистрируется на центральном процессоре 1, то и все его процессы работают на центральном процес­соре 1. В результате может случиться так, что центральный процессор 1 окажется загружен работой, тогда как центральный процессор 2 будет простаивать.
В-третьих, совместного использования страниц также нет. Может случиться так, что у центрального процессора 2 много свободных страниц, в то время как центральный процессор 1 будет постоянно заниматься свопингом. При этом нет никакого способа занять свободные страницы у соседнего процессора, так как выделение памяти статически фиксировано.
В-четвертых, (и этот аспект наиболее неприятен) если ОС поддерживавает буферный кэш недавно использованных дисковых блоков, то каждая операционная система будет выполнять это независимо от остальных. Таким образом, может случиться так, что некоторый блок диска будет присутствовать в нескольких буферах одновременно, причем в нескольких буферах сразу он может оказаться модифицированным, что приведет к порче данных на диске. Единственный способ избежать этого заключается в полном отказе от блочного кэша, что значительно снизит производительность системы.
По причине приведенных выше соображений такая модель в настоящее время используется редко, хотя она применялась на первоначальном этапе становления многопроцессорных ВМ, когда преследовалась цель быстрого простого переноса существующих операционных систем на какой-либо новый мультипроцессор.
При другом способе организации ОС для многопроцессорных ВМ используется всего одна копия операционной системы, работающая только на центральном процессоре 1. Все системные вызовы перенаправляются для обработки на центральный процессор 1. Центральный процессор 1 может также выполнять процессы пользователя, если у него будет оставаться для этого время. Такая схема называется «ведущий-ведомый», так как центральный процессор 1 является ведущим (или главным), а все остальные процессоры – или ведомыми (или подчиненными).
Модель системы «ведущий-ведомый» позволяет решить большинство проблем первой модели. В этой модели используется единая структура дан­ных (например, один общий список или набор приоритетных списков), учитыва­ющая готовые процессы. Когда центральный процессор переходит в состояние простоя, он запрашивает у операционной системы процесс, который можно обра­батывать, и при наличии готовых процессов операционная система назначает этому процессору процесс. Поэтому при такой организации никогда не может случиться так, что один центральный процессор будет простаивать, в то время как другой центральный процессор перегружен. Страницы памяти могут динамически предоставляться всем процессам. Кроме того, в такой системе есть всего один общий буферный кэш блочных устройств, поэтому дискам не грозит порча данных, как в предыдущей модели при попытке использования блочного кэша.
Недостаток этой модели состоит в том, что при большом количестве централь­ных процессоров «ведущий» может стать узким местом системы. Ведь ему приходится обрабатывать все системные вызовы от всех центральных процессоров. Следова­тельно, такая модель проста и работоспособна для небольших мультипроцессоров, но на машинах с относительно большим числом процессоров она будет работать крайне неэффективно.
Третья модель, представляющая собой модель так называемых «симметричных мультипроцессоров» SMP (Symmetric Multiprocessor), позволяет устранить недостатки вышеописанной модели. Как и в предыдущей схеме, в данной модели в памяти находится всего одна копия операционной сис­темы, но выполнять ее может любой процессор. При системном вызове на цент­ральном процессоре, обратившемся к системе с системным вызовом, происходит прерывание с переходом в режим ядра и обработкой системного вызова. При этом модель обеспечивает динамический баланс процессов и памяти, поскольку в ней имеется всего один набор таблиц операционной системы. Она также позво­ляет избежать простоя системы, связанного с перегрузкой ведущего центрального процессора, так как в ней его нет. И все же данная модель имеет собственные проблемы. В частности, если код опе­рационной системы будет выполняться одновременно на двух или более центральных процессорах, произойдет «катастрофа» (например, если два центральных про­цессора одновременно берут один и тот же процесс для запуска или запраши­вают одну и ту же свободную страницу памяти).
Простейший способ разреше­ния подобных проблем заключается в связывании мьютекса (то есть блокировки) с ОС, в результате чего вся система превращается в одну боль­шую критическую область. Когда центральный процессор хочет выполнять код операционной системы, он должен сначала получить мьютекс. Если мьютекс за­блокирован, процессор вынужден ждать. Таким образом, любой центральный про­цессор может выполнить код операционной системы, но в каждый момент време­ни только один из них будет делать это.
Существенными недостатками такой модели при наличии уже более десяти центральных процессоров являются значительные по времени простои процессоров в длинных очередях в ожидании разрешения доступа к мьютексу. Эта проблема разрешается следующим образом. Так как многие части ОС независимы друг от друга (например, один центральный процессор может заниматься планированием, в то время как другой центральный процессор будет выполнять обращение к файловой системе, а третий обрабатывать страничное прерывание), возможно произвести «расщепление» операционной системы на неза­висимые критические области, не взаимодействующие друг с другом. В этом случае у каждой критической области есть свой мьютекс, поэтому только один центральный процес­сор может выполнять ее в каждый момент времени. Таким образом можно достичь большей степени распараллеливания. Однако может случиться, что некоторые таб­лицы, например таблица процессов, используются в нескольких критических облас­тях. Например, таблица процессов требуется для планирования, но также для выполнения системного вызова и для обработки сигналов. Тогда для каждой таблицы, исполь­зующейся несколькими критическими областями, назначается свой собственный мьютекс. В результате этого, в каждый момент времени каждая критическая область может вы­полняться только одним центральным процессором, а к каждой таблице может быть предоставлен доступ только одному центральному процессору.
Подобная организация используется в большинстве современных многопроцессорных ВМ. Сложность написания ОС для таких машин заключается не в том, что программный код сильно отличается от обычной операци­онной системы. Самым сложным является расщепление операционной сис­темы на критические области, которые могут выполняться параллельно на разных центральных процессорах, не мешая друг другу даже косвенно. Кроме того, каж­дая таблица, используемая двумя и более критическими областями, должна быть отдельно защищена мьютексом, а все программы, пользующиеся этой таблицей, должны корректно использовать мьютекс. Следует также уделить особое внимание вопросу избежания взаимобло­кировок. Если двум критическим областям потребуются таблица А и таблица В, то рано или поздно возникнет взаимоблокировка, причину появления которой будет очень трудно определить. Теоретически всем таблицам можно поставить в соответствие целые числа и потребовать от всех критических областей запрашивать эти таблицы по порядку номеров. Такая стратегия позволяет избежать взаимоблокировок, но она требует от программиста детального исследования того, какие таблицы потребу­ются каждой критической области, чтобы запросить их в правильном порядке. При изменении программы критической области может потребо­ваться новая таблица, которая не была нужна ранее. Этот факт также должен быть корректно учтен программистом, чтобы избежать взаимоблокировок, выглядящих с точки зрения пользователя как «зависание» системы.
3.2. Планирование и синхронизация в многопроцессорных вычислительных машинах

На однопроцессорной ВМ планирование одномерно. Единственный вопрос, на который должен быть каждый раз получен ответ, – какой процесс должен быть запущен следующим? На мультипроцессоре планирование двумерно. Планировщик должен решить, какой процесс и на котором центральном процессоре запустить. Это дополнительное измерение существенно усложняет планирование процессов на многопроцессорных ВМ. Другой усложняющий фактор состоит в том, что в ряде случаев все процессы являются независимыми, тогда как в других случаях они формируют груп­пы.
Рассмотрим планирование независимых процессов.
Простейший алгоритм планирова­ния независимых процессов (или потоков) состоит в поддержании единой струк­туры данных для готовых процессов, возможно, просто списка, но скорее всего множества списков для процессов с различными приоритетами. Наличие единой структуры данных планирования, используемой всеми цент­ральными процессорами, обеспечивает процессорам режим разделения времени подобно тому, как это выполняется на однопроцессорной системе. Кроме того, та­кая организация позволяет автоматически балансировать нагрузку, то есть она исключает ситуацию, при которой один центральный процессор простаивает, в то время как другие процессоры перегружены. Два недостатка такой схемы представ­ляют собой, во-первых, потенциальный рост конкуренции за структуру данных планирования по мере увеличения числа центральных процессоров и, во-вторых, наличие обычных накладных расхо­дов на выполнение переключения контекста, когда процесс блокируется, ожидая выполнения операции ввода-вывода. Переключение контекста также может случиться, когда истекает квант времени про­цесса.
Мультипроцессоры обладает рядом определенных свойств, которые отсутствуют в однопроцессорных ВМ. Например, на мультипроцессорax часто встречается удержание процессом спин-блокировки. При этом другие центральные процессоры, ждущие освобождения спин-блокировки, просто теряют время в циклах ожидания, пока этот процесс не будет запущен снова и не отпустит блокировку. В однопроцессорных ВМ спин-блокировка применяется редко. Поэтому если процесс, удерживающий мьютекс, блокируется и запускается другой процесс, то при попытке заполучить мьютекс второй процесс будет тут же блокирован, и много времени потеряно не будет.
Чтобы решить данную проблему, в некоторых системах применяется так называемое «умное» планирование, при котором процесс, захватывающий спин-блокировку, устанавливает флаг, демонстрирующий, что он в данный момент обладает спин-блокировкой. Когда процесс освобождает спин-блокировку, он также очищает и флаг. Таким образом, планировщик не останавливает процесс, удерживающий спин-блокировку, а, напротив, дает ему еще немного времени, чтобы тот завершил выполнение критической области и отпустил мьютекс.
Другая проблема, играющая важную роль в планировании, заключается в том факте, что хотя все центральные процессоры равны, но некоторые центральные процессоры как бы «равнее» других. В частности, когда процесс А достаточно долго работает на центральном процессоре K, кэш процессора K будет во многом заполнен блоками процесса А. Если процесс А должен быть снова вскоре запущен, его производительность может оказаться выше, если он будет запущен опять на центральном процессоре K, так как кэш процессора K может все еще содержать некоторые блоки процесса А. Наличие блоков в кэше увеличит частоту попаданий в кэш и, таким образом, увеличит скорость выполнения процесса. Кроме того, буфер ассоциативной памяти также может содержать «правильные» страницы памяти, что снизит количество прерываний из-за ошибок буфера. Некоторые мультипроцессорные ОС учитывают данные соображения и используют так называемое «родственное планирование». Основная идея этого метода заключается в приложении серьезных усилий для того, чтобы процесс был запущен на том же центральном процессоре, что и в прошлый раз. Один из способов реализации этого метода состоит в использовании двухуровневого алгоритмам планирования. В момент создания процесс назначается конкретному центральному процессору, например, наименее загруженному в данный момент. Такое назначение процессов процессорам представляет собой верхний уровень алгоритма. В результате каждый центральный процессор получает свой набор процессов. Действительное планирование процессов находится на нижнем уровне алгоритма. Оно выполняется отдельно каждым центральным процессором при помощи приоритетов или других средств. Старания удерживать процессы на одном и том же центральном процессоре максимизируют родственность кэша. Однако если у какого-либо центрального процессора нет работы, у загруженного работой процессора отнимается процесс и отдается ему.
Двухуровневое планирование обладает тремя преимуществами. Во-первых, оно довольно равномерно распределяет нагрузку среди имеющихся центральных процессоров. Во-вторых, двухуровневое планирование по возможности использует преимущество родственности кэша. В-третьих, поскольку у каждого центрального процессора при таком варианте планирования есть свой собственный список свободных процессов, конкуренция за списки свободных процессов минимизируется, так как попытки использования списка другого процессора происходят относительно редко.
Другой подход к планированию мультипроцессоров может быть использован, если процессы связаны друг с другом каким-либо способом. Часто также случается, что один процесс создает множество потоков, работающих совместно. Для нашего рассмотрения задание, состоящее из нескольких связанных процессов, или процесс, состоящий из нескольких потоков, представляют собой, по сути, одно и то же. Будем называть планируемые объекты потоками, но все сказанное здесь в равной мере справедливо и для процессов. Планирование нескольких потоков на нескольких центральных процессорах называется совместным использованием пространства или разделением пространства.
Простейший алгоритм разделения пространства работает следующим образом. Предположим, что сразу создается целая группа связанных потоков. В момент их создания планировщик проверяет, есть ли свободные центральные процессоры по количеству создаваемых потоков. Если свободных процессоров достаточно, каж­дому потоку выделяется собственный центральный процессор (то есть работающий в однозадачном режи­ме), и все потоки запускаются. Если процессоров недо­статочно, ни один из потоков не запускается, пока не освободится достаточное количество центральных процессоров. Каждый поток выполняется на своем про­цессоре вплоть до завершения, после чего все центральные процессоры возвраща­ются в пул свободных процессоров. Если поток оказывается заблокированным операцией ввода-вывода, он продолжает удерживать центральный процессор, который простаивает до тех пор, пока поток не сможет продолжать свою работу. При появлении следующего пакета потоков применяется тот же алгоритм. В любой момент времени множество центральных процессоров статически раз­деляется па несколько подмножеств, на каждом из которых выполняются потоки одного процесса. Со временем, по мере завершения работы одних процессов и появления новых процессов, количество и размеры групп центральных процессоров изменяются.
Очевидное преимущество разделения пространства заключается в устраненении многозадачности, что снижает накладные расходы по переключению контекста. Однако ее недостаток состоит в потерях времени при блокировке центральных процессоров. С целью устранения проблем описанного выше метода проводятся активные поиски алгоритмов, способных планировать одновременно в пространстве и времени, особенно для процессов, создающих большое количество потоков, которым, как правило, требуется возможность общения друг с другом. Известные разработки в этом направлении представляют собой достаточно сложные алгоритмы и их рассмотрение выходит за рамки данного учебного пособия.
В многопроцессорных ВМ часто требуется синхронизация центральных процессоров, реализация которой в этом случае далеконе тривиальна.
Если процесс в однопроцессорной ВМ обращается к системному вызову, ко­торый требует доступа к некой критической таблице, программа ядра может про­сто запретить прерывания, прежде чем обратиться к таблице. Однако на мульти­процессоре запрет прерывания повлияет только на один центральный процессор, выполнивший команду запрета прерываний. Остальные центральные процессоры продолжат свою работу и смогут получить доступ к критической таблице. Требу­ется специальный мьютекс-протокол, который будет выполняться всеми централь­ными процессорами, чтобы гарантировать работу взаимного исключения.
Основой любого практического мьютекс-протокола является команда процессо­ра, позволяющая исследовать и изменить слово в памяти за одну операцию. Для этого можно использовать команду TSL (Test and Set Lock – проверить и установить блокировку), которая считывает слово памяти в регистр процессора. Одновременно она записывает 1 (или другое ненулевое значение) в слово памяти. Конечно, для выполнения операций чтения и записи памяти требуется два цикла шины. В однопроцессорной ВМ, поскольку одна команда процессора не мо­жет быть прервана на полпути, команда TSL работает должным образом. Для решения проблемы взаимного исключения в многопроцессорной ВМ команда TSL сначала должна блокировать шину, не допуская обращения к ней других центральных процессоров, затем выполнить оба обращения к памяти, после чего разблокировать шину. Как правило, для блоки­ровки шины сначала выполняется обычный запрос шины по стандартному прото­колу, затем устанавливается в 1 некая специальная линия шины. Пока эта линия шины установлена в 1, никакой другой центральный процессор не может получить к ней доступ. Такая команда может быть выполнена только на шине, у которой есть необходимые специальные линии и аппаратный протокол для их использования (современные шины обладают подобными свойствами). Если команда TSL корректно реализована и применяется, она гарантирует пра­вильную работу взаимного исключения. Однако подобный способ реализации взаимного исключения использует спин-блокировку, так как запрашивающий центральный процессор находится в цикле опроса блокировки с максимальной скоростью. Этот метод является не только тратой времени запрашивающего цент­рального процессора (или процессоров), но он также может накладывать значительную нагрузку на шину или память, существенно снижая скорость работы всех остальных центральных процессоров, пытающихся выполнять свою обычную работу.
Наличие кэширования не устра­няет проблему конкуренции за шину. Теоретически, как только за­прашивающий центральный процессор прочитал слово блокировки, он должен по­лучить его копию в свой кэш. Пока ни один другой центральный процессор не предпринимает попыток использовать это слово, запрашивающий центральный процессор может работать с ним в своем кэше. Когда центральный процессор, владеющий словом блокировки, записывает в него 1, протокол кэша автомати­чески помечает как недействитель-ные все копии этого слова в удаленных кэшах, требуя получения правильных значений. Однако проблема заключается в том, что кэш оперирует блоками по 32 или 64 байт. Обычно слова, окружающие слово блокировки, нужны центральному процессору, удерживающему это слово. Поскольку команда TSL представляет собой запись (так как она модифицирует слово блокировки), ей требуется монопольный доступ к блоку кэша, содержащему слово блокировки. Таким образом, каждая команда TSL помечает блок кэша владельца блокировки как недействительный и получает при­ватную, эксклюзивную копию для запрашивающего центрального процессора. Как только владелец блокировки изменит слово, соседнее с блокировкой, блок кэша перемещается на его процессор. В результате весь блок кэша, содержащий слово блокировки, постоянно челночно перемещается от центрального про­цессора, удерживающего блокировку, к центральному процессору, пытающемуся ее получить. Все это создает довольно значительный и совершенно излишний трафик шины.
Проблему можно было бы решить, если бы удалось избавиться от всех опера­ций записи, вызванных командой TSL запрашивающей стороны. Это можно сделать, если запрашивающая сторона сначала будет выполнять простую операцию чтения, чтобы убедиться, что мьютекс свободен. Только убедившись, что он свободен, цен­тральный процессор выполняет команду TSL, чтобы захватить его. В результате большинство операций опроса представляют собой операции чтения, а не опера­ции записи. Когда мьютекс считан, владелец выполняет операцию записи, для чего требуется монопольный доступ. При этом все остальные копии этого блока кэша объявляются недействительными. При следующей операции чтения запрашива­ющий центральный процессор перезагрузит этот блок кэша. При одновременном споре двух центральных процессоров за один и тот же мьютекс может случиться, что они одновременно увидят, что мьютекс свободен, и одновременно выполнят команду TSL. Ничего катастрофичного в этом случае не про­изойдет, так как выполнена будет только одна команда TSL и такая ситуация не приведет к состоянию состязания.
Другой способ снижения шинного трафика заключается в использовании алгоритма так называемого двоичного экспоненциального отката, применяемого в сетевой технологии Ethernet. Вместо постоянного опроса, о чем было сказано ранее, между опросами может быть вставлен цикл задержки. Вначале задержка представляет собой одну команду. Если мьютекс занят, задержка удваивается, учетверяется и т.д. до некоторого мак­симального уровня. При низком уровне получается быстрая реакция при осво­бождении мьютекса, зато требуется больше обращений к шине для чтения бло­ка кэша. Высокий максимальный уровень позволяет уменьшить число лишних обращений к шине за счет более медленной реакции программы на освобождение мьютекса. Использование алгоритма двоичного экспоненциального отката не за­висит от применения операций чистого чтения перед командой TSL.
Еще более эффективный способ заключается в том, чтобы каждому центральному про­цессору, желающему заполучить мьютекс, позволить опрашивать свою собствен­ную переменную блокировки. Во избежание конфликтов перемен­ная должна располагаться в не используемом для других целей блоке кэша. Работа алгоритма состоит в том, что центральный процессор, которому не удается запо­лучить мьютекс, захватывает переменную блокировки и присоединяется к концу списка центральных процессоров, ожидающих освобождения мьютекса. Когда цен­тральный процессор, удерживающий мьютекс, покидает критическую область, он освобождает приватную переменную, проверяемую первым центральным процес­сором в списке (в его собственном кэше). Тем самым следующий центральный процессор получает возможность войти в критическую область. Покинув крити­ческую область, этот процессор освобождает переменную блокировки, тестируе­мую следующим процессором и т.д. Хотя такой протокол несколько сложен (это необходимо, чтобы не допустить одновременного присоединения двух централь­ных процессоров к концу списка), однако его практическое применение достаточно результативно.
До сих пор предполагалось, что центральный процессор, которому требуется мьютекс, просто ждет, пока тот не освободится, опрашивая его состояние посто­янно или периодически, либо присоединяясь к списку ожидающих процессоров. В некоторых случая альтернативы простому ожиданию нет. Например, какой-либо центральный процессор простаивает и ему нужен доступ к общему списку готовых процессов, чтобы выбрать оттуда процесс и запустить его. Если список готовых процессов заблокирован, простаивающему центральному процессору будет просто более нечем себя занять. Он должен ждать, пока список готовых процессов не освободится. Однако в некоторых случаях у процессора может быть выбор. Например, если какому-либо потоку на центральном процессоре потребуется доступ к блочному кэшу файловой системы, заблокированному в данный момент, центральный про­цессор может решить не ждать, а переключиться на другой поток. Вопрос приня­тия решения о том, ждать или переключиться на другой поток, в однопроцессорной ОС не возника­ет, так как опрос в цикле ни имеет смысла при отсутствии другого центрального процессора, способного освободить мьютекс. Если поток пытается получить мьютекс и ему это не удается, он всегда блокируется, чтобы дать возможность владельцу мьютекса работать и освободить мьютекс.
Если допустить, что оба варианта (опрос в цикле и переключение потоков) вы­полнимы, возникает следующая ситуация. Циклический опрос напрямую расхо­дует процессорное время. Периодическая проверка состояния мьютекса не явля­ется продуктивной работой. Переключение процессов, однако, также расходует циклы процессора, так как для этого требуется сохранить текущее состояние пото­ка, получить мьютекс списка свободных процессов, выбрать из этого списка поток, загрузить его состояние и передать ему управление. Более того, кэш центрального процессора будет содержать не те блоки, поэтому после переключения потоков возможно много промахов кэша. Также вероятны ошибки TLB. Наконец, потре­буется переключение обратно на исходный поток, результатом которого снова бу­дут промахи кэша. Циклы процессора, потраченные на эти два переключения кон­текста, плюс промахи кэша представляют собой существенные накладные расходы.
Одно из решений этой проблемы заключается в том, чтобы всегда использовать циклический оп­рос. Вторым подходом является использование только переключений. Третий ва­риант состоит в том, чтобы каждый раз принимать отдельное решение. В момент принятия решения не известно, что лучше – переключаться или ждать, но в лю­бой системе можно отследить активность процессов и затем проанализировать ее в автономном режиме. При этом можно сказать, какое решение было бы лучшим в том или ином случае и сколько процессорного времени было потрачено. Таким образом, ретроспективный алгоритм становится эталоном, относительно которо­го может измеряться эффективность реальных алгоритмов.
Другим способом разрешения указанной проблемы является использование модели, в которой поток, не заполучивший мьютекс, какое-то время опрашивает состояние мьютекса в цикле. Если пороговое значение времени ожидания превышается, он переключается. В некоторых случаях пороговое зна­чение фиксировано, как правило, оно равно известному времени, требующемуся на переключение на другой поток и обратно. В других случаях эта величина меня­ется динамически, в зависимости от истории состояния ожидаемого мьютекса. Лучшие результаты достигаются тогда, когда система учитывает несколько по­следних интервалов ожидания и предполагает, что следующий интервал ожидания будет близок по величине к предыдущим.









Резюме

По своему основному содержанию операционные системы для многопроцессорных ВМ представляют собой обычные ОС, выполняющие традиционные функции по обработке системных вызовов, управлению памятью, обслуживанию файловой системы, управлению устройствами ввода-вывода. Главным отличием в реализации ОС для многопроцессорных ВМ является изменение содержания состояния «выполнение» процессов. В этом состоянии может находиться не один процесс (как в однопроцессорных ВМ), а сразу несколько процессов, выполняющихся на разных процессорах многопроцессорной ВМ.
Простейший способ организации таких ОС состоит в том, чтобы статически разделить оперативную память по числу цент­ральных процессоров и дать каждому центральному процессору свою собственную память с собственной копией операционной системы. Такой примитивный подход обладает рядом существенных недостатков и в настоящее время используется редко, хотя широко применялся на первоначальном этапе становления многопроцессорных ВМ.
При другом способе организации ОС для многопроцессорных ВМ используется всего одна копия операционной системы, работающая только на одном центральном процессоре, называемом «ведущим». Все системные вызовы перенаправляются для обработки на этот центральный процессор. Остальные процессоры в этой схеме – «ведомые». Такая модель системы позволяет решить большинство проблем предыдущего способа организации ОС, но ее недостаток состоит в том, что при относительно большом количестве централь­ных процессоров «ведущий» может стать узким местом всей системы.
Устранить эти недостатки позволяет схема, при которой в памяти также находится всего одна копия ОС, но выполнять ее может любой процессор. Эта модель обеспечивает динамический баланс процессов и памяти, поскольку в ней имеется всего один набор таблиц ОС.
Способ разреше­ния указанных выше проблем состоит в связывании мьютекса (то есть блокировки) с ОС, в результате чего вся система превращается в одну боль­шую критическую область. Эффективным механизмом также является «расщепление» ОС на неза­висимые критические области, не взаимодействующие друг с другом.
Сложность планирования процессов в многопроцессорной ВМ заключается в том, что планировщик решает вопросы не только очередности запуска процессов на выполнение, но и выбора центрального процессора, на котором должен быть запущен тот или иной процесс. Другим фактором, усложняющим планирование, является то, что в ряде случаев все процессы являются независимыми, тогда как в других случаях они формируют груп­пы.
Простейший алгоритм планирова­ния независимых процессов – поддержание единой струк­туры данных для готовых процессов.
В многопроцессорных ВМ часто встречается удержание процессом спин-блокировки. Чтобы решить данную проблему, в некоторых системах планировщик не останавливает процесс, удерживающий спин-блокировку, а, напротив, дает ему еще немного времени, чтобы тот завершил выполнение критической области и отпустил мьютекс.
Для увеличения скорости выполнения процессов применяется двухуровневый алгоритм планирования, удерживающий выполнение процессов на одном и том же центральном процессоре, что позволяет прежде всего использовать преимущество «родственности» содержимого кэш-памяти.
При планировании процессов, связанных друг с другом каким-либо способом, используется алгоритм «совместного использования (или разделения) пространства».
Для синхронизации центральных процессоров в многопроцессорных ВМ применяется специальный мьютекс-протокол, основой которого является команда процессо­ра TSL, позволяющая проверить и установить блокировку. Один из способов снижения существенного шинного трафика, неизбежно создаваемого командой TSL, заключается в использовании алгоритма двоичного экспоненциального отката, применяемого в сетевых технологиях. Более эффективный способ состоит в том, чтобы каждому центральному про­цессору, желающему заполучить мьютекс, позволить опрашивать свою собствен­ную переменную блокировки.
Важной проблемой планирования и синхронизации процессов в многопроцессорных ВМ является решение вопроса ожидания освобождения мьютекса или переключения на другой поток. Одно из решений этой проблемы заключается в том, чтобы всегда использовать циклический оп­рос. Вторым подходом является использование только переключений. Третий ва­риант состоит в том, чтобы каждый раз принимать, по-возможности, наиболее эффективное решение.

Контрольные вопросы и задания

1. В чем заключается основное отличие в реализации ОС для многопроцессорных ВМ от ОС однопроцессорных ВМ?
2. Охарактеризуйте особенности функционирования ОС многопроцессорных ВМ, организованных путем статического разделения оперативной памяти по числу цент­ральных процессоров и выделении каждому центральному процессору собственной копии ОС.
3. Какими достоинствами и недостатками обладает способ организации ОС многопроцессорных ВМ по схеме «ведущий-ведомый»?
4. Опишите механизмы преодоления проблем в модели реализации ОС многопроцессорных ВМ, позволяющей любому процессору выполнять процедуры единственной копии операционной сис­темы, находящейся в памяти.
5. Какие факторы усложняют планирование процессов в многопроцессорных ВМ по сравнению с аналогичным планированием в однопроцессорных машинах?
6. Какой механизм применяется для преодоления удержания процессом спин-блокировки?
7. Представьте достоинства использования двухуровневого алгоритма планирования процессов в многопроцессорных ВМ.
8. Опишите алгоритм «совместного использования пространства», его преимущества и недостатки.
9. Каким образом выполняется синхронизация центральных процессоров в многопроцессорных ВМ?










4. Управление процессами и ресурсами
в многомашинных вычислительных системах

4.1. Способы организации управления процессами
и ресурсами в многомашинных вычислительных системах

Одним из эффективнейших направлений развития вычислитель-ной техники стало построение так называемых многомашинных вычислительных систем (далее – ММВС). Принципиальным отличием ММВС от многопроцессорных ВМ является то, что каждая машина, входящая в состав ММВС, имеет свою собственную оперативную память. Всю совокупность ММВС обычно разделяют на два крайних по архитектуре класса, которые до сих пор не имеют общепринятого и устоявшегося наименования. Несмотря на отсутствие единой терминологии, один из этих классов чаще всего называют классом «многомашинных вычислительных систем сосредоточенного типа», а другой – классом «многомашинных вычислительных систем распределенного типа» или «распределенных вычислительных систем».
Под ММВС сосредоточенного типа понимают многомодульную (или многоузловую) систему, каждый модуль которой включает центральный процессор, оперативную память, интерфейсное устройство и, возможно, дисковую память для свопинга. Такие модули обычно называют «вычислительными модулями». Все вычислительные модули, как правило, имеют общие периферийные устройства. Для связи отдельных модулей (узлов) используются выделенные интерфейсные линии. Вся система чаще всего располагается в одном помещении и администрируется одной организацией. Такие системы в свою очередь принято подразделять на два типа: «кластер­ные системы» (claster – гроздь), которые состоят из нескольких единиц или десятков (как правило, не более четырех десятков) модулей, и «массово-паралллельные вычислительные системы» или «массивно-параллельные системы» MPP (Massive parallel processing), которые включают более 100 единиц вычислительных модулей. Основными ком­понентами таких систем, то есть отдельными вычислительными модулями чаще всего являются «усеченные» варианты обычных ВМ.
ММВС распределенного типа или распределенные вычислительные системы имеют существенное сходство с ММВС сосредоточенного типа в том, что в них также нет общей физической памяти, а у каждого узла есть своя па­мять. Но в отличие от ММВС сосредоточенного типа, узлы распределенной вычислительной системы связаны друг с другом не столь жестко. Кроме того, узел распределенной системы, как правило, представляет собой полноценную ВМ с полным набором пери­ферийных устройств. Узлы распределенной системы могут располагаться на значительном расстоянии друг от друга, в пределе, по всему миру, и администрироваться разными организациями. Наиболее ярким примером распределенной системы является Интернет. Распределенные вычислительные системы обычно называют вычислительными (или компьютерными) сетями.
Важнейшим отличием ММВС от автономных (централизованных) ВМ является способ организации межпроцессной взаимосвязи. В автономных машинах связь между процессами, как правило, предполагает наличие общей разделяемой памяти. В ММВС (при отсутствии какой бы то ни было разделяемой памяти) основой межпроцессного взаимодействия может служить только передача по сети так называемых сообщений посредством некоторой коммуникационной среды. Сообщение представляет собой некоторый блок информации, отформатированный процессом-отправителем таким образом, чтобы он был понятен процессу-получателю.
Итак, процессы на разных центральных процессорах ММВС общают­ся, отправляя друг другу сообщения. В своем простейшем виде этим обменом сооб­щениями явно занимаются процессы пользователя. Другими словами, операционная система предоставляет способ отправки и получения сообщения, а библиотечные процедуры обеспечивают доступность этих системных вызовов для пользователь­ских процессов.
Таким образом, в самом простом случае системные средства обеспечения связи могут быть сведены к двум основным системным вызовам (примитивам): один – для отправки сообщения, другой – для получения сообщения. Соответствующие библиотечные процедуры могут иметь следующий формат:
1) вызов для отправки сообщения send(dest, &mptr);
2) вызов для получения сообщения receive(addr, &mptr).
Первая процедура посылает сообщение, на которое указывает указатель mptr, процессу dest (идентификатор процесса) и блокирует вызывающий ее процесс до тех пор, пока сообщение не будет отправлено. Вторая процедура вызывает блоки­ровку процесса вплоть до получения сообщения. Когда сообщение приходит, оно копируется в буфер, на который указывает mptr, и процесс, вызвавший эту биб­лиотечную процедуру, разблокируется. Параметр addr указывает адрес, от кото­рого вызывающий процесс ожидает прихода сообщения.
Отметим, что возможны различные варианты этих двух процедур и их параметров.
Описанные выше вызовы представляют собой блокирующие вызовы (иногда на­зываемые синхронными вызовами). Когда процесс обращается к процедуре send, он указывает адресат и буфер, данные из которого следует послать указанному адресату. Пока сообщение посылается, передающий процесс блокирован (то есть приостановлен). Команда, следующая за обращением к процедуре send, не выпол­няется до тех пор, пока все сообщение не будет послано. Аналогично, обращение к процедуре receive не возвращает управления, пока сообщение не бу­дет получено целиком и положено в буфер, адрес которого указан в параметре. Процесс остается приостановленным, пока не прибудет сообщение, даже если на это уйдет несколько часов. В некоторых системах получатель может указать, от кого именно он ожидает прихода сообщения. В этом случае процесс будет блоки­рован, пока не прибудет сообщение от указанного отправителя.
Альтернативу блокирующим вызовам составляют неблокирующие вызовы (иногда называемые асинхронными вызовами). Если процедура send является неблокирующей, то она возвращает управление вызывающему ее процессу прак­тически немедленно, прежде чем сообщение будет отправлено. Преимущество этой схемы состоит в том, что отправляющий процесс может продолжать вычисления параллельно с передачей сообщения, что позволяет избежать простоя централь­ного процессора (при условии, что других готовых к работе процессов нет). Выбор между блокирующим и неблокирующим примитивами обычно делается проекти­ровщиками системы (то есть, как правило, доступен либо один примитив, либо другой), хотя в некоторых системах бывают доступны оба примитива и право вы­бора предоставляется пользователю.
Однако помимо преимущества высокой производительности, с неблокирующи­ми примитивами связана более сложная организация программы. Отправителю нельзя изменять содержимое буфера сообщения до тех пор, пока это сообщение не будет полностью отправлено. Более того, если у отправляющего процесса нет возможности узнать, что передача уже выполнена, то он никогда не будет уверен, можно ли уже пользоваться буфером.

Возможны три метода решения этой проблемы. Первое решение заключается в копировании ядром сообщения в свой буфер, после чего процессу позволяется продолжать работу. С точки зрения отправителя эта схема аналогич­на блокирующему вызову, потому что как только отправитель получает управление, он может снова пользоваться буфером. Разумеется, сообщение еще не будет от­правлено, но отправителю это не мешает. Недостаток этого метода состоит в необ­ходимости копирования каждого исходящего сообщения из пространства пользо­вателя в буфер ядра. Во многих сетевых интерфейсах сообщение все равно будет скопировано в аппаратный буфер передачи, поэтому первое копирование пред­ставляет собой просто потерю времени. Лишняя операция копирования может существенно снизить производительность системы.
Второе решение заключается в прерывании отправителя, когда сообщение будет отправлено, чтобы известить его об этом факте. В этом случае операции копиро­вания не требуется, что сохраняет время, но прерывания на уровне пользователя значительно усложняют программы и могут привести к внутренним конфликтам в программе, в результате чего такие программы будет почти невозможно отладить.
Третье решение состоит в том, чтобы копировать содержимое буфера при за­писи. Буфер помечается как доступный только для чтения до тех пор, пока сооб­щение не будет отправлено. Если буфер используется повторно прежде, чем будет отправлено сообщение, создается копия буфера. Недостаток этого варианта за­ключается в том, что если только буферу не выделена целиком собственная страни­ца, операции записи с соседними переменными также будут вызывать копирова­ние страницы. Кроме того, потребуются дополнительные административные меры, так как теперь отправка сообщения неявно изменяет статус чтения-записи стра­ницы. Наконец, раньше или позже, страница опять может быть записана, что при­ведет к появлению еще одной копии страницы.
Таким образом, у отправителя имеется следующий выбор:
1. Блокирующая операция send (центральный процессор простаивает во вре­мя передачи сообщения).
2. Неблокирующая операция send с копированием (время центрального про­цессора теряется на создание дополнительной копии).
3. Неблокирующая операция send с прерыванием (усложняет программу).
4. Копирование при записи (в конечном итоге требуется дополнительная опе­рация копирования).
Первый вариант является лучшим, особенно при наличии нескольких потоков. Пока один поток блокирован, ожидая отправ­ки сообщения, остальные потоки могут продолжать работу. Для этого метода также не требуется буфера в ядре, а передача сообщения занимает меньше времени, если не требуется дополнительного копирования.
Еще один способ заключается в том, что при прибытии сообщения в адресном пространстве получающего процесса создается новый поток. Такой поток называется всплывающим или временным потоком. Он выполняет заранее указанную процедуру, в качестве параметра которой передается указатель на прибывшее сообщение. После обработки сообщения этот процесс просто прекращает свое существование.
Вариантом этой идеи является запуск программы получателя прямо в обработ­чике прерываний, что позволяет избежать создания временного потока. Чтобы еще ускорить эту схему, в само сообщение можно включить адрес обработ­чика, поэтому, когда оно прибудет, обработчик будет вызван с помощью всего не­скольких команд процессора. Большой выигрыш данной схемы заключается в том, что копирование вообще не нужно. Обработчик получает сообщение от интерфейс­ной платы и обрабатывает его на лету. Такая схема называется «активными сооб­щениями». Поскольку каждое сообщение содержит адрес обработчика, эта схема может работать только в том случае, когда отправители и получатели пол­ностью доверяют друг другу.
В более сложной форме, чем рассмотрено выше, передача сообщений скрыта от пользовате­ля под видом вызова удаленной процедуры RPC (Remote Procedure Call).
Идея вызова удаленных процедур состоит в расширении хорошо известного механизма передачи управления и данных внутри программы, выполняющейся на одной ВМ, на передачу управления и данных через коммуникационные каналы, связывающие разные ВМ. Другими словами, программам разрешается вызывать процедуры, расположенные на других ВМ. Когда процесс на ВМ 1 вызывает процедуру на ВМ 2, вызывающий процесс на ВМ 1 приостанавливается, а на ВМ 2 выполняется вызванная процедура. Информация между вызывающим процессом и вызываемой процедурой может передаваться через параметры, а также возвращаться в результате процедуры.
Средства вызова удаленных процедур предназначены для облегчения организации распределенных вычислений. Наибольшая эффективность использования RPC достигается в тех приложениях, в которых существует интерактивная связь между удаленными компонентами с небольшим временем ответов и относительно малым количеством передаваемых данных. Такие приложения называют RPC-ориентированными.
Реализация удаленных вызовов существенно сложнее реализации вызовов локальных процедур. Для вызова локальных процедур характерны асимметричность (то есть одна из взаимодействующих сторон является инициатором) и синхронность (то есть выполнение вызывающей процедуры приостанавливается с момента выдачи запроса и возобновляется только после возврата из вызываемой процедуры).
При удаленных вызовах вызывающая и вызываемая процедуры выполняются на разных ВМ, следовательно они имеют разные адресные пространства, и это создает проблемы при передаче параметров и результатов, особенно если ВМ не идентичны. Так как RPC не может рассчитывать на разделяемую память, то это означает, что параметры RPC не должны содержать указателей на ячейки нестековой памяти и что значения параметров должны копироваться с одной ВМ на другую. Следующим отличием RPC от локального вызова является то, что он обязательно использует нижележащую систему связи, однако это не должно быть явно видно ни в определении процедур, ни в самих процедурах. Удаленность вносит также и дополнительные проблемы. Выполнение вызывающей программы и вызываемой локальной процедуры в одном ВМ реализуется в рамках единого процесса. Но в реализации RPC участвуют как минимум два процесса – по одному в каждой ВМ. В случае, если один из них аварийно завершится, могут возникнуть ситуации, при которых вызывающие процедуры будут безрезультатно ожидать ответа от удаленных процедур.
Кроме того, существует ряд проблем, связанных с неоднородностью языков программирования и операционных сред: структуры данных и структуры вызова процедур, поддерживаемые в каком-либо одном языке программирования, не поддерживаются точно так же в других языках.
Эти и некоторые другие проблемы решаются на основе реализации механизма «прозрачности» RPC: вызов удаленной процедуры должен выглядеть максимально похожим на вызов локальной процеду­ры и вызывающей процедуре не требуется знать, что вызываемая процедура находится на другой ВМ, и наоборот.

Традиционно вызывающую процедуру называют клиентом, а вызываемую – сервером. В дальнейшем изложении будет использоваться именно эта терминология.
RPC достигает прозрачности следующим путем. Когда вызываемая процедура действительно является удаленной, в библиотеку помещается вместо локальной процедуры другая версия процедуры, называемая клиентским стабом (stub – заглушка). Подобно оригинальной процедуре, стаб вызывается с использованием вызывающей последовательности, так же происходит прерывание при обращении к ядру. Только в отличие от оригинальной процедуры он не помещает параметры в регистры и не запрашивает у ядра данные, вместо этого он формирует сообщение для отправки ядру удаленной ВМ.
Взаимодействие программных компонентов при выполнении удаленного вызова процедуры происходит следующим образом. После того, как клиентский стаб был вызван программой-клиентом, его первой задачей является заполнение буфера отправляемым сообщением. В некоторых системах клиентский стаб имеет единственный буфер фиксированной длины, заполняемый каждый раз с самого начала при поступлении каждого нового запроса. В других системах буфер сообщения представляет собой пул буферов для отдельных полей сообщения, причем некоторые из этих буферов уже заполнены. Этот метод особенно подходит для тех случаев, когда пакет имеет формат, состоящий из большого числа полей, но значения многих из этих полей не меняются от вызова к вызову. Затем параметры должны быть преобразованы в соответствующий формат и вставлены в буфер сообщения. К этому моменту сообщение готово к передаче, поэтому выполняется прерывание по вызову ядра. Когда ядро получает управление, оно переключает контексты, сохраняет регистры процессора и карту памяти (дескрипторы страниц), устанавливает новую карту памяти, которая будет использоваться для работы в режиме ядра. Поскольку контексты ядра и пользователя различаются, ядро должно точно скопировать сообщение в свое собственное адресное пространство (так, чтобы иметь к нему доступ, запомнить адрес назначения, и, возможно, другие поля заголовка), а также оно должно передать его сетевому интерфейсу. На этом завершается работа на клиентской стороне. Включается таймер передачи, и ядро может либо выполнять циклический опрос наличия ответа, либо передать управление планировщику, который выберет какой-либо другой процесс на выполнение. В первом случае ускоряется выполнение запроса, но отсутствует мультипрограммирование.
На стороне сервера поступающие биты помещаются принимающей аппаратурой либо во встроенный буфер, либо в оперативную память. Когда вся информация будет получена, генерируется прерывание. Обработчик прерывания проверяет правильность данных пакета и определяет, какому стабу следует их передать. Если ни один из стабов не ожидает этот пакет, обработчик должен либо поместить его в буфер, либо вообще отказаться от него. Если имеется ожидающий стаб, то сообщение копируется ему. Наконец, выполняется переключение контекстов, в результате чего восстанавливаются регистры и карта памяти, принимая те значения, которые они имели в момент, когда стаб сделал вызов.
После этого начинает работу серверный стаб. Он распаковывает параметры и помещает их соответствующим образом в стек. Когда все готово, выполняется вызов сервера. После выполнения процедуры сервер передает результаты клиенту. Для этого выполняются все описанные выше этапы, только в обратном порядке.
В идеале RPC должен функционировать правильно и в случае отказов. Рассмотрим некоторые наиболее часто встречающиеся классы отказов и способы реакции системы на них.
1. Клиент не может определить местонахождения сервера, например, в случае отказа нужного сервера, или из-за того, что программа клиента была скомпилирована давно и использовала старую версию интерфейса сервера. В этом случае в ответ на запрос клиента поступает сообщение, содержащее код ошибки.
2. Потерян запрос от клиента к серверу. Самое простое решение – через определенное время повторить запрос.
3. Потеряно ответное сообщение от сервера клиенту. Один из вариантов решения проблемы в этом случае – последовательная нумерация всех запросов клиентским ядром. Ядро сервера запоминает номер самого последнего запроса от каждого из клиентов, и при получении каждого запроса выполняет анализ: является ли этот запрос первичным или повторным.
4. Сервер потерпел аварию после получения запроса. В данном случае имеет значение, когда произошел отказ – до или после выполнения операции. Но клиентское ядро не может распознать эти ситуации, для него известно только то, что время ответа истекло. Существует три подхода к решению этой проблемы:
а) ждать до тех пор, пока сервер не перезагрузится, и пытаться выполнить операцию снова. Этот подход гарантирует, что RPC был выполнен до конца по крайней мере один раз, а возможно и более.

б) сразу сообщить приложению об ошибке. Этот подход гарантирует, что RPC был выполнен не более одного раза.
в) третий подход не гарантирует ничего. Когда сервер отказывает, клиенту не оказывается никакой поддержки. RPC может быть или не выполнен вообще, или выполнен много раз.
Ни один из этих подходов не является особенно привлекательным. А идеальный вариант, который бы гарантировал ровно одно выполнение RPC, в общем случае не может быть реализован по принципиальным соображениям. Это может быть пояснено на следующем примере.
Пусть удаленной операцией является печать некоторого текста, которая включает загрузку буфера принтера и установку одного бита в некотором управляющем регистре принтера, в результате которой принтер стартует. Авария сервера может произойти как за микросекунду до, так и за микросекунду после установки управляющего бита. Момент сбоя целиком определяет процедуру восстановления, но клиент о моменте сбоя узнать не может. В первом случае крах сервера ведет к краху клиента, и восстановление невозможно. Во втором случае действия по восстановлению системы выполнить и возможно, и необходимо.
Следует подчеркнуть, что при реализации метода вызова удаленных процедур, клиентская процедура, написанная пользователем, выполняет «нормальный» (то есть локальный) процедурный вызов клиентского стаба. Так как клиентская процедура и клиентский стаб находятся в одном адресном пространстве, пара­метры передаются обычным образом. Аналогично, серверная процедура вызыва­ется процедурой в своем адресном пространстве. Таким образом, вместо выполне­ния с помощью процедур send и receive по сути операций ввода-вывода, в методе вызова удаленных процедур связь с удаленными объектами осуществляется при помощи имитации локальных процедурных вызовов.

4.2. Понятия сетевой и распределенной операционных систем

Операционные системы ММВС распределенного типа (то есть распределенных вычислительных систем – вычислительных сетей) обычно называют «сетевыми ОС». В вычислительных сетях есть узкоспециализированные правила, описывающие типы и фор­маты сообщений, которые могут посылаться в этих сетях, а также регламентирую­щие ответы на эти сообщения. Например, при некоторых обстоятельствах (скажем, перенос файла), когда сообщение посылается от источника адресату, адресат дол­жен послать в ответ подтверждение правильного приема сообщения. В другой си­туации подтверждения в ответ отправлять не требуется. Набор указанных правил, с помощью которых машины взаимодействуют в сети, назы­вается сетевым протоколом. Понятие протокола является фундаментальным понятием сетевых ОС, позволяющим определить и описать конкретные функции тех програмных частей операционных систем, которые отвечают за взаимодействие удаленных процессов.
Сетевые средства связи обычно строятся по многослойному (многоуровневому) прин­ципу. Каждый уровень такой многослойной иерархии может взаимодейство­вать непосредственно только со своими вертикальными соседями, ру­ководствуясь четко закрепленными соглашениями – вертикальными протоколами, которые принято называть интерфейсами.
Самым нижним уровнем в многослойных сетевых иерархиях является уровень, на котором реализуется реальная физическая связь между двумя узлами сети. Для обеспечения обмена физическими сигналами между двумя различными узлами сети необходимо, чтобы эти узлы поддерживали определенный протокол физического взаимодействия – горизонтальный протокол.
На самом верхнем уровне находятся пользовательские процессы, которые инициируют обмен данными. Количество и функции промежуточных уровней варьируются от одной системы к другой. Все одинаковые уровни, лежащие выше физического, виртуально обмениваются данными посредством горизонтальных протоколов. Наличие такой виртуальной связи означает, что уровень N машины 2 должен получить ту же самую информацию, которая была отправлена уровнем N машины 1. Хотя в реальности эта информация должна была сначала дойти сверху вниз до уровня 1 машины 1, затем передана уровню 1 машины 2 и только после этого доставлена снизу вверх уровню N этой машины.
Всю совокупность вертикальных и горизонтальных протоколов, достаточную для организации взаимодействия удаленных процессов в вычислительных сетях, принято называть семейством протоколов или стеком протоколов.
Сети, построенные на основе разных стеков протоколов, могут быть объединены между собой с использованием вычислительных устройств, осуществляющих трансляцию из одного стека протоколов в другой.
Наиболее совершенным и перспективным классом ОС являются так называемые распределенные операционные системы, которые сле­дует отличать от традиционных сетевых ОС. В сетевых операционных системах для того, чтобы задействовать ресурсы другой сетевой ВМ, пользователи должны знать о ее наличии и уметь это сделать. Каждая ВМ в сети работает под управлением своей локальной ОС, отличаю­щейся от ОС автономной ВМ наличием дополнительных сетевых средств (программной поддержкой сетевых интерфейсных устройств и механизмов доступа к удаленным ресурсам), но эти дополнения су­щественно не меняют структуру операционной системы.
Распределенная система, напротив, внешне выглядит как обычная автономная система. Пользователь не знает и не должен знать, где его файлы хранятся (на локальной или удаленной ВМ), и где его про­граммы выполняются. Он может вообще не знать, подключена ли его ВМ к сети. Сетевые же операционные системы не создают ощущения работы с единой системой, которое характерно для распределенных ОС. Однако при этом внутреннее строение распределенной операци­онной системы имеет существенные отличия от автономных систем.
В распределенных ОС к лежащей в основе системы вычислитель­ной сети должна быть добавлена некая общая модель, которая спо­собна превратить множество слабосвязанных ВМ в однородную «конструкцию», базирующуюся на единой концепции.

4.3. Варианты реализации распределенных
операционных сис­тем

Наиболее удачным (по современным меркам) способом, с помо­щью которого распределенная система может достичь определенного уровня однородности, несмотря на различие аппаратного обеспече­ния отдельных объектов сети, является установка специального уровня программного обеспечения поверх сетевой операционной сис­темы. Этот уровень, называемый промежуточным программным обеспечением (а также связующим или посредническим программ­ным обеспечением), предназначен для того, чтобы скрыть ге­терогенность и распределенную природу базового набора ВМ. Промежуточное программное обеспечение предоставляет опре­деленные структуры данных и операции, позволяющие процессам и пользователям назначительно удаленных машинах однородно взаи­модействовать друг с другом.
Один из типов такого программного обеспечения представляет собой промежуточное программное обеспечение, основанное на документах. Типичным представителем такого подхода является основная идея «Всемирной паутины» WWW (World Wide Web), которая заключается в том, что распределенная систе­ма должна выглядеть как гигантская коллекция документов, связанных гипер­ссылками.
Другой подход состоит в том, чтобы придать распределенной системе вид огромной всемирной файловой системы. Использование модели файловой системы для распределенной системы озна­чает, что имеется единая глобальная файловая система с пользователями по все­му миру, способными читать и писать файлы, к которым у них есть доступ. Для связи процессов используется файловый обмен. Один процесс записывает данные в файл, а другой процесс считывает их оттуда. В этом случае возможно использование одной из следующих моделей: модели закачивания-скачивания и модели удаленного доступа. В первой модели чтобы получить доступ к файлу, процесс сначала считывает его с удаленного сервера, на котором хранится этот файл. Если для файла разрешено только чтение, то файл читается локально для более высокой производительности. Если файл должен быть записан, он записывается также локально. Когда процесс заканчивает работу с файлом, обновленный файл отправляется обратно на сервер. В модели удаленного доступа файл остается на сервере, а клиент посылает серверу команды для выполнения работы на месте. Преимущество модели закачивания-скачивания заключается в ее простоте и том факте, что перенос файла целиком эффективнее, чем перенос его по частям. К недостаткам данной модели относится необходимость наличия достаточно боль­шого объема памяти для хранения файла целиком локально. К тому же перенос файла целиком, когда требуется только его часть, представляет собой излишние расходы. Наконец, при наличии нескольких конкурирующих пользователей воз­никает проблема непротиворечивости файлов. Примером рассмотренного подхода является файловая система AFS (Andrew File System – файловая система, названная в честь спонсоров проекта ее разработки).
В следующим подходе, связанном с использованием промежуточного программного обеспечения, предлагается называть все, что есть в системе, объектами. При этом объект – это набор переменных, объединенных вместе с набором процедур доступа к ним, на­зываемых методами. Процессам не разрешается получать доступ к переменным напрямую. Вместо этого они должны вызывать методы. Примером объектного промежуточного программного обеспечения является технология CORBA (Common Object Request Broker Architecture – архитектура распределенных объектных приложений). Технология CORBA представляет собой систему типа клиент-сервер, в которой клиентский процесс может осуществлять операции с объектами, расположенны­ми на серверах. Архитектура CORBA была разработана для неоднородной систе­мы, состоящей из разнообразных аппаратных платформ и операционных систем. Чтобы клиент на одной платформе мог вызвать сервер на другой платформе, между клиентом и сервером располага­ются специальные программные посредники.
Еще одним известным подходом является использование промежуточного программного обеспечения, которое называется координационным. Пример этого подхода – новая система связи и синхронизации Linda. В системе Linda независимые процессы общаются через абстрак­тное пространство так называемых кортежей. Это пространство является глобальным по отно­шению ко всей системе, и процессы на любой ВМ могут вставлять кортежи в пространство кортежей или удалять их из него, независимо от того, как и где они хранятся. Для пользователя пространство кортежей выглядит как большая глобальная общая память.
Другими примерами моделей, основанных на координации, являются модель «публикация-подписка» и система Jini.
Модель «публикация-подписка» состоит из нескольких процессов, соединенных широковещательной сетью. Каждый процесс может производить информацию, потреблять информацию, а также делать то и другое. Когда у производителя информации есть новая информация, он рассылает ее всем по сети в виде кортежа. Это действие называется публикацией. Каждый кортеж содержит иерархически структурированную строку темы публикации с полями, разделенными точками. Процессы, которых интересуют определенные темы, могут подписаться на них. Чтобы подписаться на какую-либо тему, нужно сообщить ее специальному демону кортежей, работающему на той же ВМ, что и процесс. Демон кортежей на каждой ВМ копирует все рассылаемые кортежи в оперативную память. Затем он просматривает строку темы сообще­ния, чтобы определить, какие из процессов заинтересованы в получении этой информации, пересылая каждому такому процессу копию полученного сообщения. Кортежи также могут рассылаться по глобальным сетям.
Система Jini состоит из большого количества самодостаточных Jini-устройств, каждое из которых предлагает другим устройствам одну услугу или несколько видов услуг. Jini-устройство может быть установлено в сеть и мгновенно начать предоставление услуг без сложных процедур установки. Отметим, что устройства устанавливаются не в ВМ, как в традиционном случае, а именно в сеть.
Jini-устройство может быть не только ВМ, но также принтером, сотовым телефоном или другим уст­ройством с центральным процессором, оперативной памятью и соединением с сетью (возможно, беспроводным). Система Jini представляет собой свободную федерацию Jini-устройств, которые могут входить в систему и выходить из системы по своему желанию, без централизованного управления. Когда Jini-yстройство хочет присоединиться к федерации, оно передает по ло­кальной сети с помощью широковещания пакет с вопросом о наличии в данном районе службы поиска. Для нахождения службы поиска используется специаль­ный протокол обнаружения (один из нескольких собственных протоколов системы Jini). В качестве альтернативы новое Jini-устройство может ждать, пока не придет одно из периодически рассылаемых объявлений службы поиска. Если служба поиска видит, что новое устройство хочет зарегистрироваться, она посылает в ответ программу, выполняющую регистрацию. За­тем новое устройство исполняет полученную программу, связывающуюся со служ­бой поиска и регистрирующуюся в ней на некий установленный интервал времени, Пока не истек данный интервал времени, устройство может перерегистрировать­ся, если оно того пожелает. Такая схема означает, что Jini-устройство может поки­нуть систему, просто выключившись, и о существовании этого устройства систе­ма вскоре забудет. Таким образом, не требуется ни специальной процедуры выхода из системы, ни централизованного управления. Концепция регистрации на опре­деленный срок называется получением аренды. Обратим внимание, что поскольку программа регистрации устройства загру­жается в устройство по сети, эта программа может изменяться по мере развития системы, для чего не потребуется изменений аппаратного и программного обеспе­чения в самом устройстве.
Более подробное изучение строения распределенных операционных систем выхо­дит за рамки данного учебного пособия.
В заключении отметим, что в литературных публикациях, посвященных операционным системам, наблюдаются разные подходы к терминологии, касающейся ОС многомашинных вычислительных систем. Иногда все операционные системы, обеспечивающие функционирование ММВС (и, в частности, вычислительных сетей), называют распределенными, а иногда, наоборот, сетевыми. Для того, чтобы подчеркнуть те отличия между сетевыми и распределенными операционными системами, которые были рассмотрены в данном разделе, часто применяют термин «истинно распределенные» ОС, значение которого совпадает с принятой в настоящем учебном пособии трактовкой понятия распределенных операционных систем.

Резюме

Эффективным способом повышения производительности и надежности вычислительной техники является объединение отдельных автономных ВМ в многомашинные вычислительные системы.
Различают два класса многомашинных вычислительных систем: ММВС сосредоточенного типа и ММВС распределенного типа (которые обычно называют распределенными вычислительными системами или вычислительными сетями).
Существенным отличием ММВС от автономных (одно- или многопроцессорных) ВМ является то, что каждая машина, входящая в состав ММВС, имеет свою собственную оперативную память. Вследствии такого архитектурного построения механизмы организации межпроцессной взаимосвязи в ММВС и в автономных ВМ принципиально различны. В автономных машинах базой для взаимодействия процессов служит общая разделяемая память. В ММВС при отсутствии какой бы то ни было разделяемой памяти основой межпроцессного взаимодействия служит обмен физическими пакетами данных (так называемыми сообщениями) посредством некоторой коммуникационной среды.
В наиболее простом варианте системные средства обеспечения связи могут быть сведены к двум основным системным вызовам (примитивам): один – для отправки сообщения, другой – для получения сообщения. Системные вызовы могут быть блокирующими (синхронными) или неблокирующими (асинхронными).
В более сложной форме передача сообщений скрыта от пользовате­ля под видом вызова удаленной процедуры RPC. Идея вызова удаленных процедур состоит в расширении механизма передачи управления и данных внутри программы, выполняющейся на одной ВМ, на передачу управления и данных через коммуникационные каналы, связывающие разные ВМ.
Операционные системы вычислительных сетей обычно называют сетевыми ОС. В вычислительных сетях есть узкоспециализированные правила, описывающие типы и фор­маты сообщений, которые могут посылаться в этих сетях, а также регламентирую­щие ответы на эти сообщения. Набор таких правил, с помощью которых машины взаимодействуют в сети, назы­вается протоколом. Понятие протокола является фундаментальным понятием сетевых ОС, позволяющим определить и описать конкретные функции тех програмных частей операционных систем, которые отвечают за взаимодействие удаленных процессов.
Сетевые средства связи обычно строятся по многослойному (многоуровневому) прин­ципу. Каждый уровень такой многослойной иерархии может взаимодейство­вать непосредственно только со своими вертикальными соседями, ру­ководствуясь вертикальными протоколами, которые принято называть интерфейсами.
Самым нижним уровнем в многослойных сетевых иерархиях является уровень, на котором реализуется реальная физическая связь между двумя узлами сети на основе горизонтального протокола физического взаимодействия. Все одинаковые уровни, лежащие выше физического, виртуально обмениваются данными посредством соответствующих горизонтальных протоколов.
Всю совокупность вертикальных и горизонтальных протоколов, достаточную для организации взаимодействия удаленных процессов в вычислительных сетях, принято называть семейством протоколов или стеком протоколов. Сети, построенные на основе разных стеков протоколов, могут быть объединены между собой с использованием вычислительных устройств, осуществляющих трансляцию из одного стека протоколов в другой.
Наиболее совершенным и перспективным классом ОС являются так называемые распределенные операционные системы. Распределенная система создает для пользователя полную иллюзию того, что он работает в обычной автономной системе.
В распределенных ОС к лежащей в основе системы вычислитель­ной сети должна быть добавлена некая общая модель, которая спо­собна превратить множество слабосвязанных ВМ в однородную «конструкцию», базирующуюся на единой концепции.
Одним из наиболее эффективных способов построения распределенных ОС является установка специального промежуточного уровня программного обеспечения поверх сетевой операционной сис­темы. Этот уровень предоставляет однородный уровень для взаимодействующих с ним приложений. Среди различных типов про­межуточного программного обеспечения следует выделить документное, файловое, объектное и координационное. Примерами промежуточного программного обеспече­ния служат такие системы, как WWW, AFS, CORBA, Linda, Jini.

Контрольные вопросы и задания

1. С какими целями ВМ объединяют в многомашинные вычислительные системы?
2. Охарактеризуйте особенности построения и отличия ММВС сосредоточенного и распределенного типов.
3. В чем важнейшее отличие ММВС от автономных (централизованных) ВМ?
4. Посредством чего в ММВС осуществляется межпроцессное взаимодействие?
5. Представьте основные системные вызовы для отправки и получения сообщений.
6. Чем отличаются блокирующие (синхронные) системные вызовы от неблокирующих (асинхронных)?
7. Какие проблемы возникают при организации программ с неблокирующи­ми примитивами и какие методы применяются для разрешения этих проблем?
8. В чем заключается основная идея так называемого вызова удаленных процедур?
9. Опишите механизмы реализации вызовов удаленных процедур.
10. Охарактеризуйте наиболее часто встречающиеся классы отказов механизма вызова удаленных процедур и способы реакции системы на них.
11. Дайте определение понятиям «сетевая операционная система» и «сетевой протокол».
12. По какому прин­ципу строятся сетевые средства связи?
13. Опишите основные функции вертикальных и горизонтальных протоколов.
14. Что понимается под стеком протоколов вычислительной сети?
15. В чем отличие распределенных операционных систем от традиционных сетевых ОС?
16. Охарактеризуйте наиболее эффективные способы реализации распределенных операционных сис­тем.
17. Приведите примеры практического построения разных типов про­межуточного программного обеспечения.
5. Общие концепции разработки
операционных систем

5.1. Основные принципы построения операционных систем
Одним из наиболее важных принципов построения ОС является принцип модульности. Под модулем операционной системы в общем случае понимают функционально законченный элемент системы, выполненный в соответствии с принятыми межмодульными интерфей­сами. По своему определению модуль предполагает возможность относительно легкой замены его на другой при наличии заданных интерфейсов. Способы обособления составных частей ОС в отдельные модули могут существенно различаться, но чаще всего разделение происходит именно по функциональному признаку. В зна­чительной степени разделение системы на модули определяется используемым методом проектирования ОС (снизу вверх или наоборот). Особо важное значение при построении ОС имеют реентерабельные программные модули, так как они позволяют более эффективно использовать ресурсы вычислительной системы (под реентерабельностью понимают свойство программы, позволяющее одновременно выполнять эту программу нескольким процессам). Достижение реентерабельности реализует­ся различными способами. В некоторых системах реентерабельность программы получают автоматически благодаря неизменяемости кодовых частей программ при исполнении (из-за особенностей системы команд машины), а также автома­тическому распределению регистров, автоматическому отделению кодовых час­тей программ от данных и помещению последних в системную область памяти. Естественно, что для этого необходима соответствующая аппаратная поддержка. В других случаях это достигается программистами за счет использования специ­альных системных модулей. Принцип модульности отражает технологические и эксплуатационные свойства системы. Наибольший эффект от его использования достижим в случае, когда принцип распространен одновременно на операционную систему, прикладные программы и аппаратуру.
В ОС выделяется некоторая часть важных программных модулей, которые должны постоянно находиться в оперативной памяти для более эффективной организации вычис­лительного процесса. Эту часть в ОС называют ядром операционной системы, так как это действительно основа системы. При формировании состава ядра необходимо учитывать два про­тиворечивых требования. Во-первых, в состав ядра должны войти наиболее часто исполь­зуемые системные модули. Во-вторых, количество модулей должно быть таковым, чтобы объем памяти, занимаемый ядром, был бы не слишком большим. В состав ядра, как правило, входят модули управления системой прерываний, средства по переводу процессов из состояния выполнения в состояние ожидания, готовности и об­ратно, средства по распределению таких основных ресурсов, как оперативная память и процессор. Помимо программных модулей, входящих в состав ядра и постоянно располагающихся в оперативной памяти, может быть много других системных программных модулей, которые получили название транзитных. Транзитные программные модули операционной системы загружаются в оперативную память только при необходимости и в случае отсутствия свободного пространства могут быть замещены другими транзитными модулями. В качестве синонима термина «транзитный» иногда используется термин «диск-резидентный».
Основное положение принципа генерируемости ОС определяет такой способ исходного пред­ставления центральной системной управляющей программы ОС (ее ядра и ос­новных компонентов, которые должны постоянно находиться в оперативной па­мяти), который позволял бы настраивать эту системную часть исходя из конкретной конфигурации конкретного вычислительного комплекса и круга решаемых задач. Эта процедура проводится редко, перед достаточно про­тяженным периодом эксплуатации ОС. Процесс генерации осуществляется с по­мощью специальной программы-генератора и соответствующего входного языка для этой программы, позволяющего описывать программные возможности сис­темы и конфигурацию машины. В результате генерации получается полная вер­сия ОС. Сгенерированная версия ОС представляет собой совокупность систем­ных наборов модулей и данных. Упомянутый выше принцип модульности положительно проявляется при гене­рации ОС. Он существенно упрощает настройку ОС на требуемую конфигура­цию вычислительного комплекса.
Принцип функциональной избыточности учитывает возможность проведения одной и той же работы раз­личными средствами. В состав ОС может входить несколько модулей супервизора, управляющих тем или другим видом ресурса, несколько систем управления файлами, различные средства организации коммуникаций между вычисли-тельными процессами. Это позволяет пользователям быстро и наиболее адекватно адаптировать ОС к опре­деленной конфигурации вычислительного комплекса, обеспечить максимально эф­фективную загрузку технических средств и получить максимальную производительность при решении конкретного класса за­дач.
Принцип виртуализации позволяет представить структуру системы в виде определенного набора планировщиков процессов и распределителей ресурсов (мониторов) и использовать единую централизован­ную схему распределения ресурсов. Наиболее естественным и законченным проявлением концепции виртуальности является понятие виртуальной машины. По сути, любая операционная система, являясь средством распределения ресурсов и организуя по определенным прави­лам управление процессами, скрывает от пользователя и его приложений реаль­ные аппаратные и иные ресурсы, заменяя их некоторой абстракцией. В результа­те пользователи видят и используют виртуальную машину как некое устройство, способное воспринимать их программы, написанные на определенном языке программирования, выполнять их и выдавать результаты. При таком языковом представлении пользователя совершенно не интересует реальная конфигурация вычислительного комплекса, способы эффективного использования его компонен­тов и подсистем. Он мыслит и работает в терминах используемого им языка и тех ресурсов, которые ему предоставляются в рамках виртуальной ма­шины. Обычно виртуальная машина, предоставляемая пользователю, воспроизводит ар­хитектуру реальной машины, но архитектурные элементы в таком представле­нии выступают с новыми или улучшенными характеристиками, часто упрощаю­щими работу с системой. Характеристики могут быть произвольными, но чаще всего пользователи желают иметь собственную «идеальную» по архитектурным характеристикам машину в следующем составе:
1. Единообразная по логике работы память (виртуальная) практически неогра­ниченного объема. Организация работы с информацией в такой памяти производится в терминах обработки данных (в терминах работы с сегментами данных на уровне выбранного пользователем языка программи­рования);
2. Произвольное количество процессоров (виртуальных), способных работать па­раллельно и взаимодействовать во время работы. Способы управления про­цессорами, в том числе синхронизация и информационные взаимодействия, реализованы и доступны пользователям на уровне используемого языка в терминах управления процессами;
3. Произвольное количество внешних устройств (виртуальных), способных ра­ботать с памятью виртуальной машины параллельно или последовательно, асинхронно или синхронно по отношению к работе того или иного виртуаль­ного процессора, который инициирует работу этих устройств;
4. Информация, передаваемая или хранимая на виртуальных устройствах, не ограничена допус­тимыми размерами. Доступ к такой информации осуществляется на основе либо последовательного, либо прямого способа доступа в терминах соответст­вующей системы управления файлами. Предусмотрено расширение инфор­мационных структур данных, хранимых на виртуальных устройствах.
Степень приближения к «идеальной» виртуальной машине может быть большей или меньшей в каждом конкретном случае. Чем больше виртуальная машина, реализуемая средствами ОС на базе конкретной аппаратуры, приближена к «иде­альной», и, следовательно, чем больше ее архитек­турно-логические характеристики отличны от реально существующих, тем боль­ше степень виртуальности у полученной пользователем машины. Одним из аспектов виртуализации является организация возможности выполне­ния в данной ОС приложений, которые разрабатывались для других ОС. Други­ми словами, речь идет об организации нескольких операционных сред. Реализация этого принципа позволяет такой ОС иметь очень сильное преимущество перед аналогичными ОС, не имеющими такой воз­можности.
Принцип независимости программ от внешних устройств реализуется сейчас в подавляющем большинстве современных ОС. Этот принцип заключается в том, что связь программ с конкретны­ми устройствами производится не на уровне трансляции программы, а в период планирования ее исполнения. В результате перекомпиляция при работе про­граммы с новым устройством, на котором располагаются данные, не требуется. Указанный принцип позволяет осуществлять операции управления внешними устройствами одинаково и независимо от их конкретных физических характеристик. Напри­мер, программе, содержащей операции обработки последовательного набора дан­ных, безразлично, на каком носителе эти данные будут располагаться. Смена носителя и данных, размещаемых на них (при неизменности структурных харак­теристик данных), не привнесет каких-либо изменений в программу, если в систе­ме реализован принцип независимости.
Одним из аспектов принципа совместимости является способность ОС выполнять програм­мы, написанные для других ОС или для более ранних версий данной операцион­ной системы, а также для другой аппаратной платформы. Необходимо разделять вопросы двоичной совместимости и совместимости на уров­не исходных текстов приложений. Двоичная совместимость достигается в том случае, когда можно взять исполняемую программу и запустить ее на выполне­ние под другой ОС. Для этого необходимы: совместимость на уровне команд про­цессора, совместимость на уровне системных вызовов и даже на уровне библио­течных вызовов, если они являются динамически связываемыми. Совместимость на уровне исходных текстов требует наличия соответствующего транслятора в составе системного программного обеспечения, а также совмести­мости на уровне библиотек и системных вызовов. При этом необходима пере­компиляция имеющихся исходных текстов в новый выполняемый модуль. Гораздо сложнее достичь двоичной совместимости между процессорами, осно­ванными на разных архитектурах. Для того, чтобы одна машина выполняла про­граммы другой машины, она должна работать с машинными командами, которые ей изначально непонятны. Выходом в таких случаях является использование так называе­мых прикладных сред или эмуляторов. Учитывая, что основную часть програм­мы, как правило, составляют вызовы библиотечных функций, прикладная среда имитирует библиотечные функции целиком, используя заранее написанную биб­лиотеку функций аналогичного назначения, а остальные команды эмулирует каждую по отдельности. Одним из средств обеспечения совместимости программных и пользовательских интерфейсов является их соответствие стандартам POSIX. Использование стандар­тов POSIX позволяет создавать программы, которые впоследствии могут легко переноситься из одной системы в другую.
Принцип открытой и наращиваемой (модифици­руемой, развиваемой) ОС позволяет не только использовать возможности гене­рации, но и вводить в ее состав новые модули, совершенствовать существующие и т. д. Другими словами, необходимо, чтобы можно было легко внести дополне­ния и изменения, если это потребуется, и не нарушить при этом целостность системы. Хорошие возможности для расширения предоставляет подход к структурирова­нию ОС по типу клиент-сервер с использованием микроядерной технологии (см. подраздел 5.2). В соответствии с этим подходом ОС строится как совокупность привилегиро­ванной управляющей программы и набора непривилегированных услуг – «сер­веров». Основная часть ОС остается неизменной, но в то же время могут быть до­бавлены новые серверы или улучшены старые. Этот принцип иногда трактуют как принцип расширяемости системы.
Принцип мобильности (переносимости) заключается в том, что операционная система должна относительно легко переноситься с процессора одного типа на процессор другого типа и с аппаратной платформы одного типа (которая включает наряду с типом процессора также и способ организации всей аппаратуры машины, иначе говоря, архитектуру ВМ) на ап­паратную платформу другого типа. Заметим, что принцип переносимости очень близок принципу совместимости, хотя это и не одно и то же. Написание переносимой ОС аналогично написанию любого переносимого кода. При этом нужно следовать некоторым правилам. Во-первых, большая часть ОС должна быть написана на языке, который имеется на всех машинах или системах, на которые планируется в дальнейшем ее переносить. Это, прежде всего, означает, что ОС должна быть написана на языке высокого уровня, предпочтительно стандартизованном, на­пример на языке С. Программа, написанная на ассемблере, не является в общем случае переносимой. Во-вторых, важно минимизировать или, если возможно, исключить те части кода, которые непосредственно взаимодействуют с аппарат­ными средствами. Зависимость от аппаратуры может иметь много форм. Неко­торые очевидные формы зависимости включают прямое манипулирование реги­страми и другими аппаратными средствами. Наконец, если аппаратно-зависимый код не может быть полностью исключен, то он должен быть изолирован в не­скольких хорошо локализуемых модулях. Аппаратно-зависимый код не должен быть распределен по всей системе. Например, можно спрятать аппаратно-зависимую структуру в программно задаваемые данные абстрактного типа. Другие модули системы будут работать с этими данными, а не с аппаратурой, используя набор некоторых функций. Когда ОС переносится, то изменяются только эти данные и функции, которые ими манипулируют. Именно введение стандартов POSIX преследовало цель обеспечить переносимость соз­даваемого программного обеспечения.
Принцип обеспечения безопасности при выполнении вычислений является желательным свойством для любой многопользовательской системы. Правила безопасности определяют такие свойства, как защита ресурсов одного пользователя от других и установление квот по ресурсам для предотвращения захвата одним пользова­телем всех системных ресурсов. Обеспечение защиты информации от несанкци-онированного доступа является обязательной функцией операционных систем. В соответствии со стандартами Национального центра компьютерной безопасности США (NCSC – National Computer Security Center) безопасной считается систе­ма, которая «посредством специальных механизмов защиты контролирует доступ к информации таким образом, что только имеющие соответствующие полно­мочия лица или процессы, выполняющиеся от их имени, могут получить доступ на чтение, запись, создание или удаление информации». Иерархия уровней безопасности отмечает низ­ший уровень безопасности как D, а высший – как А. В класс D попадают системы, оценка которых выявила их несоответствие требо­ваниям всех других классов. Основными свойствами, характерными для систем класса (уровня) С, являются наличие подсистемы учета событий, связанных с безопасностью, и избирательный кон­троль доступа. На уровне С должны присутствовать:
а) средства секретного входа, обеспечивающие идентификацию пользователей путем ввода уникального имени и пароля перед тем, как им будет разрешен доступ к системе;
б) избирательный контроль доступа, позволяющий владельцу ресурса опреде­лить, кто имеет доступ к ресурсу и что он может с ним делать (владелец осуществляет это путем предоставляемых прав доступа пользователю или группе пользо­вателей);
в) средства учета и наблюдения, обеспечивающие возможность обна­ружить и зафиксировать важные события, связанные с безопас-ностью, или любые попытки получить доступ или удалить системные ресурсы;
г) защита памяти, заключающаяся в том, что память инициализи-руется перед тем, как повторно используется.
На этом уровне система не защищена от ошибок пользователя, но поведение его может быть проконтролировано по записям в журнале, составленным средствами наблюдения и аудита. Системы уровня В основаны на помеченных данных и распределении пользо­вателей по категориям, то есть реализуют мандатный контроль доступа. Каждо­му пользователю присваивается рейтинг защиты, и он может получать доступ к данным только в соответствии с этим рейтингом. Этот уровень в отличие от уровня С защищает систему от ошибочного поведения пользователя. Уровень А является самым высоким уровнем безопасности и в допол­нение ко всем требованиям уровня В требует выполнения формального, математически обоснованного доказательства соответствия системы требованиям безопасности.

5.2. Архитектурные особенности проектирования
операционных систем
Для удовлетворения жестких требований, предъявляемых к современной ОС, большое значение имеет ее структурное построение. Операционные системы прошли длительный путь развития от монолитных систем до хорошо структурированных модульных систем, способных к развитию, расширению и легкому переносу на новые платформы.
В общем случае «структура» монолитной ОС представляет собой как раз отсутствие структуры. Такая ОС написана как набор процедур, каждая из которых может вызывать другие, когда ей это нужно. При использовании этой техники каждая процедура системы имеет хорошо определенный интерфейс в терминах параметров и результатов, и каждая может вызвать любую другую для выполнения некоторой нужной для нее полезной работы. Для построения монолитной системы необходимо скомпилировать все отдельные процедуры, а затем связать их вместе в единый объектный файл с помощью компоновщика. Каждая процедура видит любую другую процедуру (в отличие от структуры, содержащей модули, в которой большая часть информации является локальной для модуля, и процедуры модуля можно вызвать только через специально определенные точки входа). Однако даже такие монолитные системы могут быть «немного» структурированными. При обращении к системным вызовам, поддерживаемым ОС, параметры помещаются в строго определенные места, такие, как регистры или стек, а затем выполняется специальная команда прерывания, известная как вызов ядра или вызов супервизора. Эта команда переключает машину из режима пользователя в режим ядра, называемый также режимом супервизора, и передает управление ОС. Затем ОС проверяет параметры вызова для того, чтобы определить, какой системный вызов должен быть выполнен. После этого ОС индексирует таблицу, содержащую ссылки на процедуры, и вызывает соответствующую процедуру. Такая организация ОС предполагает следующую структуру:
1. Главная программа, которая вызывает требуемые сервисные процедуры;
2. Набор сервисных процедур, реализующих системные вызовы;
3. Набор утилит, обслуживающих сервисные процедуры.
В этой модели для каждого системного вызова имеется одна сервисная процедура. Утилиты выполняют функции, которые нужны нескольким сервисным процедурам.
Обобщением предыдущего подхода является организация ОС как иерархии уровней. Уровни образуются группами функций операционной системы, таких как файловая система, управление процессами и устройствами и т.п. Каждый уровень может взаимодействовать только со своим непосредственным соседом – выше- или нижележащим уровнем. Прикладные программы или модули самой операционной системы передают запросы вверх и вниз по этим уровням. Такой структурный подход с современной точки зрения воспринимается как монолитный. В системах, имеющих многоуровневую структуру, нелегко удалить один слой и заменить его другим в силу множественности и размытости интерфейсов между слоями. Добавление новых функций и изменение существующих требует значительного объема работ. Поэтому на смену монолитному подходу была предложена модель клиент-сервер и тесно связанная с ней концепция микроядра.
Модель клиент-сервер – это один из возможных подходов к структурированию ОС. В широком смысле модель клиент-сервер предполагает наличие, во-первых, программного компонента – потребителя какого-либо сервиса, то есть клиента, и во-вторых, программного компонента – поставщика этого сервиса, то есть сервера. Взаимодействие между клиентом и сервером стандартизуется, так что сервер способен обслуживать клиентов, реализованных различными способами и, может быть, разными производителями. При этом главным требованием является то, чтобы они запрашивали услуги сервера понятным ему способом. Инициатором обмена обычно является клиент, который посылает запрос на обслуживание серверу, находящемуся в состоянии ожидания запроса. Один и тот же программный компонент может быть клиентом по отношению к одному виду услуг, и сервером для другого вида услуг. Модель клиент-сервер является скорее удобным концептуальным средством ясного представления функций того или иного программного элемента в той или иной ситуации, нежели технологией. Эта модель успешно применяется не только при построении ОС, но и на всех уровнях программного обеспечения, и имеет в некоторых случаях более узкий, специфический смысл, сохраняя, естественно, при этом все свои общие черты. Применительно к структурированию операционной системы идея состоит в разбиении ОС на несколько процессов – серверов, каждый из которых выполняет отдельный набор сервисных функций, например, управление памятью, создание или планирование процессов. Каждый сервер выполняется в пользовательском режиме. Клиент, которым может быть либо другой компонент ОС, либо прикладная программа, запрашивает сервис, посылая сообщение на сервер. Ядро ОС (называемое здесь микроядром), работая в привилегированном режиме, доставляет сообщение нужному серверу, сервер выполняет операцию, после чего ядро возвращает результаты клиенту с помощью другого сообщения. Подход с использованием микроядра заменил вертикальное распределение функций операционной системы на горизонтальное. Компоненты, лежащие выше микроядра, хотя и используют сообщения, пере-сылаемые через микроядро, взаимодействуют друг с другом непосредственно. Микроядро играет роль регулировщика. Оно проверяет сообщения, пересылает их между серверами и клиентами, предоставляет доступ к аппаратуре.
Рассмотренная теоретическая модель является идеализированным описанием системы клиент-сервер, в которой ядро состоит только из средств передачи сообщений. В действительности разные варианты реализации модели клиент-сервер в структуре ОС могут существенно различаться по объему работ, выполняемых в режиме ядра. На одном краю этого спектра находится чисто микроядерная доктрина, состоящая в том, что все несущественные функции ОС должны выполняться не в режиме ядра, а в непривилегированном (пользовательском) режиме. На другом краю спектра – операционные системы, в составе которых имеется исполняющая система, работающая в режиме ядра и выполняющая функции обеспечения безопасности, ввода-вывода и другие.
Микроядро реализует жизненно важные функции, лежащие в основе ОС. Это базис для менее существенных системных служб и приложений. Именно вопрос о том, какие из системных функций считать несущественными, и, соответственно, не включать их в состав ядра, является предметом спора среди соперничающих сторонников идеи микроядра. В общем случае, подсистемы, бывшие традиционно неотъемлемыми частями операционной системы – файловые системы, системы управление окнами и системы обеспечения безопасности ­– становятся периферийными модулями, взаимодействующими с ядром и друг с другом. Главный принцип разделения работы между микроядром и окружающими его модулями – включать в микроядро только те функции, которым абсолютно необходимо исполняться в режиме супервизора и в привилегированном пространстве. Под этим обычно подразумеваются машиннозависимые программы (включая поддержку нескольких процессоров), некоторые функции управления процессами, обработка прерываний, поддержка пересылки сообщений, некоторые функции управления устройствами ввода-вывода, связанные с загрузкой команд в регистры устройств. Эти функции операционной системы трудно, если не невозможно, выполнить программам, работающим в пространстве пользователя.
Известны два пути решения этой проблемы. Один из этих путей – размещение нескольких серверов, чувствительных к режиму работы процессора, в пространстве ядра, что обеспечивает им полный доступ к аппаратуре и, в то же время, связь с другими процессами посредством обычного механизма сообщений. Другой путь заключается в том, чтобы оставить в ядре только небольшую часть сервера, представляющую собой механизм реализации решения, а часть, отвечающую за принятие решения, переместить в пользовательскую область. Этот подход требует тесного взаимодействия между внешним планировщиком и резидентным диспетчером. Важно подчеркнуть логику последнего подхода. Несмотря на то, что, вообще говоря, запуск процесса или потока требует доступа к аппаратуре и обычно является функцией ядра, но при этом ядру все равно, какой из процессов запускать, поэтому решения о приоритетах процессов и дисциплине постановки в очередь может принимать работающий вне ядра планировщик.
Как и управление процессами, управление памятью может распределяться между микроядром и сервером, работающим в пользовательском режиме. Система управления страничной памятью (пейджер), работающая вне ядра, определяет стратегию замещения страниц (т.е. решает, какие страницы следует удалить из памяти для размещения страниц, выбранных с диска в ответ на прерывание по отсутствию необходимой страницы), а микроядро выполняет перемещение выбранных пейджером страниц. Как и планировщик процессов, пейджер является заменяемой составной частью.
Драйверы устройств также могут располагаться как внутри ядра, так и вне его. При размещении драйверов устройств вне микроядра для обеспечения возможности разрешения и запрещения прерываний часть программы драйвера должна исполняться в пространстве ядра. Отделение драйверов устройств от ядра делает возможной динамическую конфигурацию ОС. Кроме динамической конфигурации, есть и другие причины рассматривать драйверы устройств в качестве процессов пользовательского режима. Например, какая-либо система управления базами данных может иметь свой драйвер, оптимизированный под конкретный вид доступа к диску, но его нельзя будет подключить, если драйверы будут расположены в ядре. Этот подход также способствует переносимости системы, так как функции драйверов устройств могут быть во многих случаях абстрагированы от аппаратной части.
Технология микроядер обеспечивает совместимость программ, написанных для разных ОС, за счет абстрагирования интерфейсов прикладных программ от расположенных ниже операционных систем. Высокая степень переносимости обусловлена тем, что весь машиннозависимый код изолирован в микроядре, поэтому для переноса системы на новый процессор требуется меньше изменений и все они логически сгруппированы вместе.
Одним из важнейших требований, предъявляемых к ОС, является возможность расширяемости операционной системы. Практически для любой ОС неизбежно наступает время, когда возникает острая необходимость привнесения в систему новых функций и свойств, которые не были заложены в нее первоначально. Для монолитных операционных систем внесение изменений затруднительно и чаще всего вообще невозможно. В микроядерных структурах благодаря наличию ограниченного набора четко определенных интерфейсов возможна постепенная эволюция и расширение ОС.
Обычно операционная система выполняется только в режиме ядра, а прикладные программы – только в режиме пользователя (за исключением тех случаев, когда они обращаются к ядру за выполнением системных функций). В отличие от обычных систем, операционная система, построенная на микроядре, выполняет свои серверные подсистемы в режиме пользователя как обычные прикладные программы. Такая структура позволяет изменять и добавлять серверы, не влияя на целостность микроядра.
Иногда имеется потребность и в сокращении возможностей ОС. Микроядро не обязательно подразумевает небольшую систему. Надстроенные службы, типа файловой системы и системы управления окнами,добавляют к ней немало. Если некоторые свойства являются важными, но предназначены лишь для определенных потребителей, можно исключать их из состава системы. Тогда базовый продукт подойдет более широкому кругу пользователей.
Использование модели клиент-сервер повышает надежность системы. Каждый сервер выполняется в виде отдельного процесса в своей собственной области памяти и таким образом защищен от других процессов. Более того, поскольку серверы выполняются в пространстве пользователя, они не имеют непосредственного доступа к аппаратуре и не могут модифицировать память, в которой хранится управляющая программа. И если отдельный сервер может потерпеть «крах», то он может быть перезапущен без останова или повреждения остальной части ОС. Эта модель хорошо подходит для распределенных вычислений, так как отдельные серверы могут работать на разных процессорах многопроцессорной ВМ или даже на разных ВМ. При получении от процесса сообщения микроядро может обработать его самостоятельно или переслать другому процессу. Так как микроядру безразлично, пришло ли сообщение от локального или удаленного процесса, подобная схема передачи сообщений является эффективным базисом для механизма RPC. Однако такая гибкость имеет свои недостатки: пересылка сообщений не так быстра, как обычные вызовы функций, и ее оптимизация является критическим фактором успеха ОС на основе микроядра.
Хотя технология микроядер и заложила основы модульных систем, способных развиваться регулярным образом, она не смогла в полной мере обеспечить возможности расширения систем. В настоящее время этой цели в наибольшей степени соответствует объектно-ориентированный подход, при котором каждый программный компонент является функционально изолированным от других. Основным понятием этого подхода является «объект». Объект – это единица программ и данных, взаимодействующая с другими объектам посредством приема и передачи сообщений. Объект может быть представлением как некоторых конкретных вещей – прикладной программы или документа, так и некоторых абстракций – процесса, события. Функции объекта определяют перечень действий, которые могут быть выполнены над данными этого объекта. Объект-клиент может обратиться к другому объекту, послав сообщение с запросом на выполнение какой-либо функции объекта-сервера. Объекты могут описывать сущности, которые они представляют, с разной степенью детализации. Для обеспечения преемственности при переходе к более детальному описанию предлагается механизм наследования свойств уже существующих объектов, то есть механизм, позволяющий порождать конкретные объекты из более общих. Например, при наличии объекта «текстовый документ» разработчик может легко создать объект «текстовый документ в формате Word», добавив соответствующее свойство к базовому объекту. Механизм наследования позволяет создать иерархию объектов, в которой каждый объект более низкого уровня приобретает все свойства своего предка.
Внутренняя структура данных объекта скрыта от наблюдения. Нельзя произвольно изменять данные объекта. Для того, чтобы получить данные из объекта или поместить данные в объект, необходимо вызывать соответствующие объектные функции. Это изолирует объект от того кода, который использует его. Разработчик может обращаться к функциям других объектов, или строить новые объекты путем наследования свойств других объектов, ничего не зная о том, как они сконструированы. Это свойство называется инкапсуляцией.
Таким образом, объект предстает для внешнего мира в виде «черного ящика» с хорошо определенным интерфейсом. С точки зрения разработчика, использующего объект, пока внешняя реакция объекта остается без изменений, не имеют значения никакие изменения во внутренней реализации. Это дает возможность легко заменять одну реализацию объекта на другую, например, в случае смены аппаратных средств. При этом сложное программное окружение, в котором находятся заменяемые объекты, не потребует никаких изменений. С другой стороны, способность объектов представать в виде «черного ящика» позволяет упаковывать в них и представлять в виде объектов уже существующие приложения, ничего в них не изменяя.
Использование объектно-ориентированного подхода особенно эффективно при создании активно развивающегося программного обеспечения, например, при разработке приложений, предназначенных для выполнения на разных аппаратных платформах.
Полностью объектно-ориентированные операционные системы очень привлекательны для программистов, так как позволяют им работать в глубине ОС, приспосабливая их к своим нуждам, но при этом не нарушая целостность системы.

Концепция объектно-ориентированного подхода непосредственно касается только разработчиков и лишь косвенно влияет на конечного пользователя. Другая концепция – концепция множественных прикладных сред – приносит пользователю возможность выполнять при помощи своей ОС программы, написанные для других операционных систем и других процессоров. Хотя в некоторых ОС дополнительное программное обеспечение позволяет пользователям запускать «чужие» программы, но в новом поколении операционных систем средства для выполнения «чужих» программ становятся стандартной частью системы. Благодаря этому операционная система практически не ограничивает выбор прикладных программ. Множественные прикладные среды обеспечивают совместимость конкретной ОС с приложениями, написанными для других ОС и процессоров, на двоичном уровне, а не на уровне исходных текстов.
При реализации множественных прикладных сред разработчики сталкиваются с противоречивыми требованиями. С одной стороны, задачей каждой прикладной среды является выполнение программы по возможности так, как если бы она выполнялась на «родной» ОС. Но потребности этих программ могут входить в конфликт с конструкцией операционной системы. Специализированные драйверы устройств могут противоречить требованиям безопасности. Могут конфликтовать схемы управления памятью и оконные системы. Но самой большой потенциальной проблемой является производительность – прикладная среда должна выполнять программы с приемлемой скоростью. Этому требованию не могут удовлетворить широко используемые ранее эмулирующие системы. Для сокращения времени на выполнение «чужих» программ прикладные среды используют имитацию программ на уровне библиотек. Эффективность этого подхода связана с тем, что большинство сегодняшних программ работают под управлением графических интерфейсов пользователя (Graphic User Interface – GUI), при этом приложения тратят большую часть времени, производя некоторые хорошо предсказуемые вещи. Они непрерывно выполняют вызовы библиотек GUI для манипулирования окнами и для других связанных с GUI действий. Тщательно продуманная прикладная среда имеет в своем составе библиотеки, имитирующие внутренние библиотеки GUI, но написанные на «родном» коде, то есть она совместима с программным интерфейсом другой ОС. Иногда такой подход называют трансляцией для того, чтобы отличать его от более медленного процесса эмулирования кода по одной команде за один раз.
С позиции использования прикладных сред более предпочтительным является способ написания программ, при котором программист для выполнения некоторой функции обращается с вызовом к ОС, а не пытается более эффективно реализовать эквивалентную функцию самостоятельно, работая напрямую с аппаратурой.
Модульность операционных систем нового поколения позволяет намного легче реализовать поддержку множественных прикладных сред. В отличие от старых операционных систем, состоящих из одного большого блока, предназначенного для всех практических применений и разбитого произвольным образом на части, новые системы являются модульными, с четко определенными интерфейсами между составляющими. Это делает создание дополнительных модулей, объединяющих эмуляцию процессора и трансляцию библиотек, значительно более простым действием.

5.3. Принципы построения системных и прикладных программных интерфейсов

Операционная система всегда выступает как интерфейс между аппаратурой машины и пользователем с его задачами. Под интерфейсами операционных систем понимают специальные системные и приклад­ные программные интерфейсы, предназначенные для выполнения ниже перечисленных задач.
1. Управление процессами, которое включает в себя следующий набор основ­ных функций:
– запуск, приостановка и снятие процесса с выполнения;
– задание или изменение приоритета процесса;
– взаимодействие процессов между собой (механизмы семафоров, мьютексов и т.п.);
– удаленный вызов процедур (подпрограмм).
2. Управление памятью, которое включает в себя следующий набор основ­ных функций:
– запрос на выделение блока памяти;
– освобождение блока памяти;
– изменение параметров блока памяти (например, память может быть забло­кирована процессом либо предоставлена в общий доступ);
– отображение файлов на память (имеется не во всех системах).
3. Управление вводом-выводом, которое включает в себя запросы на управление виртуальными устройствами ввода-вывода, файловые операции (запросы к системе управления файлами на создание, изменение и удаление данных, организованных в файлы).
Выше отмечены основные наборы функций, которые выполняются ОС по соответствующим запросам от процессов. Что касается пользовательского интерфейса операционной системы, то он реализуется с помощью специальных программ­ных модулей, которые принимают его команды на соответствующем языке (воз­можно, с использованием графического интерфейса) и транслируют их в обычные вызовы в соответствии с основным интерфейсом системы. Обычно эти модули называют интерпретатором команд. Получив от пользователя команду, такой модуль после лексического и синтаксического анализа либо сам выполняет действие, либо (что случается чаще) обращается к другим модулям ОС, используя механизм прикладного программного интерфейса API.
Обращения к операционной системе в соответствии с имеющимся API может осуществляться как посредством вызова подпрограммы с передачей ей необ­ходимых параметров, так и через механизм программных прерываний. Выбор метода реализации вызовов функций API должен определяться архитектурой платформы.
В большинстве ОС используется метод вызова подпрограмм. В этом случае вызов сначала передается в модуль API (например, это может быть библиотека времени выполнения – RTL, Run Time Library), который и перенаправля­ет вызов соответствующим обработчикам программных прерываний, входящим в состав ОС. Использование механизма прерываний вызва­но, главным образом, тем, что при этом процессор переводится в режим суперви­зора.
Прикладной программный интерфейс API, как это и следует из его названия, пред­назначен для использования прикладными программами системных ресурсов ОС и реализуемых ею функций. API описывает совокупность функций и процедур, принадлежащих ядру или надстройкам ОС, и предоставляет разработчику прикладной программы набор функций, ориентированных на ор­ганизацию взаимодействия результирующей прикладной программы с так называемой «целевой вычислительной системой», которая представляет собой совокупность программных и аппаратных средств, в окружении которых выпол­няется результирующая программа. Сама результирующая программа порожда­ется системой программирования на основании кода исходной программы, соз­данного разработчиком, а также объектных модулей и библиотек, входящих в состав системы программирования. Функции API позволяют разработчику строить результирующую прикладную программу так, чтобы использовать средства целевой вычислительной системы для выполнения типовых операций. При этом разработчик программы избавлен от необходимости создания исходного кода для выполнения этих операций.
Программный интерфейс API включает в себя не только сами функции, но и со­глашения об их использовании, которые регламентируются операционной систе­мой, архитектурой целевой вычислительной системы и системой програм­мирования.
Существует несколько вариантов реализации API:
- реализация на уровне ОС;
- реализация на уровне системы программирования;
- реализация на уровне внешней библиотеки процедур и функций.
Система программирования в каждом из этих вариантов предоставляет разра­ботчику средства для подключения функций API к исходному коду программы и организации их вызовов. Объектный код функций API подключается к резуль­тирующей программе компоновщиком при необходимости.
При реализации функций API на уровне ОС за их выполнение ответственность несет ОС. Объектный код, выполняющий функции, либо непосредственно входит в состав ОС (или даже ядра ОС), либо поставляется в составе динамически загружаемых библиотек, разработанных для данной ОС. Система программиро­вания ответственна только за то, чтобы организовать интерфейс для вызова это­го кода. В таком варианте результирующая программа обращается непосредственно к ОС. Поэтому достигается наибольшая эффективность выполнения функций API по сравнению со всеми другими вариантами реализации API. Недостатком организации API по такой схеме является практически полное от­сутствие переносимости не только кода результирующей программы, но и кода исходной программы. Программа, созданная для одной архитектуры ВМ, не сможет исполняться на ВМ другой ар­хитектуры даже после того, как ее объектный код будет полностью перестроен. Чаще всего система программирования не сможет выполнить перестроение ис­ходного кода для новой архитектуры ВМ, поскольку мно­гие функции API, ориентированные на определенную ОС, будут в новой архи­тектуре просто отсутствовать. Таким образом, в данной схеме для переноса прикладной программы с одной це­левой вычислительной системы на другую будет требоваться изменение исход­ного кода программы.
Если функции API реализуются на уровне системы программирования, они пре­доставляются пользователю в виде библиотеки функций соответствующего язы­ка программирования (обычно речь идет о библиотеке RTL, которая включает в себя стандартные программы, поставляемые системой программирования на этапе компиляции). Эффективность функций API в таком варианте будет несколько ниже, чем при непосредственном обращении к функциям ОС, так как для выполнения многих функций API библиотека RTL языка про­граммирования должна все равно выполнять обращения к функциям ОС. Однако переносимость исходного кода программы в таком варианте будет самой высокой, поскольку синтаксис и семантика всех функций будут строго регла­ментированы в стандарте соответствующего языка программирования. Они за­висят от языка и не зависят от архитектуры целевой вычислительной системы. Поэтому для выполнения прикладной программы на новой архитектуре достаточно заново построить код результирующей програм­мы с помощью соответствующей системы программирования.
При реализации функций API с помощью внешних библиотек они предоставля­ются пользователю в виде библиотеки процедур и функций, созданной сторон­ним разработчиком. Причем разработчиком такой библиотеки может выступать тот же самый производитель. Система программирования ответственна только за то, чтобы подключить объ­ектный код библиотеки к результирующей программе. Причем внешняя библио­тека может быть и динамически загружаемой (то есть загружаемой во время выполне­ния программы). С точки зрения эффективности выполнения этот метод реализации API имеет самые низкие результаты, поскольку внешняя библиотека обращается как к функ­циям ОС, так и к функциям RTL языка программирования. Только при очень высоком качестве внешней библиотеки ее эффективность становится сравнимой с библиотекой RTL. Если говорить о переносимости исходного кода, то здесь существует только одно требование – используемая внешняя библиотека должна быть доступна в любой из архитек­тур, на которые ориентирована прикладная программа. Тогда удается достигнуть переносимости. Это возможно, если используемая биб­лиотека удовлетворяет какому-то принятому стандарту, а система программиро­вания поддерживает этот стандарт.



Резюме

Основными принципами построения современных эффективных операционных систем являются принципы модульности, генерируемости, функциональной избыточности, виртуализации, независимости выполняемых программ от внешних устройств, совместимости с другими ОС, открытости, легкой наращиваемости, мобильности (переносимости на другие аппаратные платформы), обеспечения надежной безопасности.
При модульном построении в ОС выделяется некоторая часть важных программных модулей, которые для более эффективной организации вычис­лительного процесса должны постоянно находиться в оперативной памяти. Эту часть ОС называют ядром операционной системы. Помимо программных модулей, входящих в состав ядра и постоянно располагающихся в оперативной памяти, может быть множество других системных программных модулей, которые загружаются в оперативную память только при необходимости, а в случае отсутствия свободного пространства могут быть замещены другими подобными модулями. Такие модули получили название транзитных или диск-резидентных.
Операционные системы прошли длительный путь развития и совершенствования своей архитектуры от монолитных систем до хорошо структурированных модульных систем, способных к развитию, расширению и легкому переносу на новые платформы. Монолитная ОС представляет собой набор процедур, каждая из которых имеет определенный интерфейс и может вызывать любую другую процедуру для выполнения некоторой нужной для нее полезной работы. При построении монолитной системы все отдельные процедуры связываются вместе в единый объектный файл.
Обобщением предыдущего подхода является организация ОС как иерархии уровней. Уровни образуются группами функций операционной системы, таких как файловая система, управление процессами и устройствами и т.п. Каждый уровень может взаимодействовать только со своим непосредственным соседом – выше- или нижележащим уровнем. Процессы передают запросы вверх и вниз по этим уровням. Такой структурный подход также воспринимается как монолитный. На смену монолитному подходу пришла модель клиент-сервер и тесно связанная с ней концепция микроядра.
Модель клиент-сервер предполагает наличие, во-первых, программного компонента – потребителя какого-либо сервиса, то есть клиента, а во-вторых, программного компонента – поставщика этого сервиса, то есть сервера. Взаимодействие между клиентом и сервером стандартизуется, так что сервер способен обслуживать клиентов, реализованных различными способами и, может быть, разными производителями. Ядро ОС (называемое здесь микроядром), работая в привилегированном режиме, доставляет сообщение нужному серверу, сервер выполняет операцию, после чего ядро возвращает результаты клиенту с помощью другого сообщения. Подход с использованием микроядра заменил вертикальное распределение функций (в монолитных ОС) на горизонтальное.
Технология микроядер заложила основы модульных систем, способных развиваться регулярным образом. Благодаря наличию четко определенных интерфейсов возможна постепенная эволюция и расширение ОС.
Достижению целей легкой расширяемости ОС в наибольшей степени соответствует объектно-ориентированный подход, при котором каждый программный компонент является функционально изолированным от других.
Модульность операционных систем нового поколения позволяет намного легче реализовать поддержку множественных прикладных сред, которые обеспечивают совместимость (на двоичном уровне) конкретной ОС с приложениями, написанными для других ОС и процессоров.
Под интерфейсами операционных систем понимают специальные системные и приклад­ные программные интерфейсы, предназначенные для управления процессами, памятью и вводом-выводом. Для использования прикладными программами системных ресурсов ОС и реализуемых ею функций пред­назначен прикладной программный интерфейс API, который может быть реализован на уровне ОС, на уровне системы программирования или на уровне внешней библиотеки процедур и функций.





Контрольные вопросы и задания

1. Перечислите основные принципы построения операционных систем.
2. Опишите принцип модульности построения ОС.
3. Охарактеризуйте принцип генерируемости ОС.
4. Какие преимущества дает реализация принципа функциональной избыточности?
5. Изложите основные положения принципа виртуализации.
6. В чем заключается принцип независимости программ от внешних устройств?
7. Что дает на практике построение ОС с учетом принципа совместимости?
8. Покажите практическое значение принципа переносимости ОС.
9. Опишите принципы обеспечения безопасности.
10. Перечислите и охарактеризуйте основные структурные модели, применяемые при проектировании ОС.
11. Для выполнения каких задач предназначены системные и приклад­ные программные интерфейсы?
12. Какие возможности предоставляют разработчику программного обеспечения функции прикладного программного интерфейса?
13. Опишите основные варианты реализации функций прикладного программного интерфейса, укажите их достоинства и недостатки.
6. История развития операционных систем
и эволюция их функциональных характеристик

6.1. Операционные системы разных этапов разработки вычислительных машин

Зарождение прообразов операционных систем в современном их толковании относят к периоду разработки в середине 1950-х годов вычислительных машин на полупроводниковой элементной базе (так называемого второго поколения ВМ) и появления первых систем пакетной обработки информации. Системы пакетной обработки (которые просто автоматизировали запуск одной программ за другой и тем самым увеличивали коэффициент загрузки процессора) явились прообразом современных операционных систем, они стали первыми системными программами, предназначенными для управления вычислительным процессом. В ходе реализации систем пакетной обработки был разработан формализованный язык управления заданиями, с помощью которого программист сообщал системе и оператору, какую работу он хочет выполнить на вычислительной машине. Совокупность нескольких заданий, как правило, в виде набора перфокарт, получила название пакета заданий.
Следующим важным этапом развития ВМ стал переход в 1960-х годах от отдельных полупроводниковых элементов типа транзисторов к интегральным микросхемам, что привело к разработке более мощных вычислительных машин третьего поколения. Этот этап характеризуется созданием семейств программно-совместимых машин. Первым таким семейством была серия машин IBM/360 корпорации IBM (International Business Machines), построенных на интегральных микросхемах. Это семейство значительно превосходило ВМ второго поколения по удельному критерию производительность/стоимость. Вскоре идея программно-совместимых машин стала общепризнанной. Программная совместимость требовала и совместимости операционных систем. Такие ОС системы должны были работать как на больших, так и на малых ВМ, с различным количеством разнообразной периферии, в коммерческой области и в области научных исследований. Разработанные в то время операционные системы, в которых делалась попытка удовлетворения указанных противоречивых требований, оказывались чрезвычайно сложными. Они состояли из многих миллионов ассемблерных строк, написанных тысячами программистов, и, как правило, содержали множество ошибок. Однако, несмотря на огромные размеры и множество проблем, операционная система OS/360 и другие ей подобные ОС машин третьего поколения действительно удовлетворяли большинству требований потребителей. Важнейшим достижением ОС этого поколения явилась реализация мультипрограммного режима, когда на одном процессоре попеременно выполняется несколько программ. Мультипрограммный режим работы процессора значительно сократил время его простоев, которые были присущи однопрограммному режиму (при котором, например, процессор мог быть не загружен работой в ряде операций одной выполняемой программы). При мультипрограммном режиме каждая из выполняемых программ загружалась в свой участок (раздел) оперативной памяти. Другим нововведением стал спулинг (описанный выше в разделе 2), определявшийся в то время как способ организации вычислительного процесса, в соответствии с которым задания считывались с перфокарт на диск в том темпе, в котором они появлялись в очереди к устройству ввода, а затем, когда очередное задание завершалось, новое задание с диска загружалось в освободившийся раздел.
Наряду с организацией мультипрограммного режима для систем пакетной обработки появился новый тип ОС – системы разделения времени. Вариант мультипрограммного режима, применяемый в системах разделения времени, был нацелен на создание для каждого отдельного пользователя иллюзии единоличного использования вычислительной машины.
Следующий период в эволюции операционных систем связан с появлением больших интегральных схем (БИС). В 1980-х годах произошло резкое возрастание степени интеграции и удешевление микросхем. ВМ стала доступна отдельному человеку, и наступила эра персональных ВМ (чаще называемых более устоявшимся термином –«персональные компьютеры»). С точки зрения архитектуры персональные ВМ практически ничем не отличались от класса миниВМ, например, самых распространенных в мире на тот период времени миниВМ типа PDP-11, но их цены были существенно ниже. Если миниВМ давали возможность иметь собственную ВМ отдельному подразделению предприятия или образовательному учреждению, то персональный компьютер сделал это возможным для отдельного человека. ВМ стали широко использоваться неспециалистами, что потребовало разработки так называемого «дружественного» программного обеспечения.
На рынке операционных систем стали доминировать системы двух классов: многопользовательские многозадачные (мультипрограммные) ОС клона UNIX и однопользовательские однозадачные (однопрограммные) ОС клона MS-DOS.
История развития операционных систем клона UNIX, эволюция их функциональных характеристик, а также описание современных версий UNIX представлены далее в подразделе 6.2.
Операционные системы корпорации Microsoft под названием MS-DOS (MicroSoft Disk Operating System – «дисковая операционная система от Microsoft») были разработана для персональных компьютеров, построенных на базе микропроцессоров Intel 8088, а затем 80286, 80386 и 80486. История операционной системы MS-DOS, получившей ши­рочайшее распространение во всем мире, начинается со скромной системы 86-DOS, написанной в 1980 г. При разра­ботке 86-DOS были учтены требования совместимости с весьма по­пулярной в то время системой СР/М-80 (Control Program for Microcomputers – программа управления для микрокомпьютеров), предназначенной для вось­миразрядных микрокомпьютеров на базе процессоров Intel 8080 и Zylog Z-80. В июле 1981 г. корпорация Microsoft приобрела права на систему 86-DOS, существенно переработала ее и выпустила на рынок под торговой маркой MS-DOS. Когда в 1981 г., когда появились первые персональные компьютеры корпорации IBM, система MS-DOS 1.0 и ее аналог PC-DOS 1.0 (разработка IBM для персональных компьютеров – Personal Computer, PC) быстро стали основными системами для этих машин. В то же время непре­рывное развитие аппаратных средств компьютеров и накопление опыта работы с ними привели к необходимости столь же непрерыв­ного совершенствования исходных систем MS-DOS и PC-DOS. В дальнейшем они развивались параллельно и их новые версии практически во всем со­ответствовали друг другу. Первое серьезное усовершенствование MS-DOS (версия 2.0) было выполнено в 1983 г. Фактически была выпущена новая опера­ционная система, хотя разработчикам удалось обеспечить полную совместимость с MS-DOS 1.0. В систему MS-DOS 2.0 были включе­ны следующие новшества:
– поддержка дискет с повышенной плотностью записи и, глав­ное, появившихся к этому времени жестких дисков;

– иерархическая структура каталогов (пришедшая из системы UNIX) вместе с группой команд ее поддержки;
– утилита PRINT, обеспечивающая вывод на печать в фоновом режиме с возможностью одновременного выполнения любой программы;
– атрибуты файлов и их системная поддержка;
– устанавливаемые драйверы внешних устройств;
– динамическое выделение и освобождение памяти;
– расширение возможностей командных файлов;
– большая группа новых команд, утилит и драйверов устройств, а также ряди других новшеств.
Система MS-DOS 3.0 появилась в 1984 г., одновременно с выпуском компьютеров IBM PC/AT на базе процессоров 80286. Начиная с этой версии в MS-DOS входит поддержка расширенной памяти, жестких дисков увеличенного объема, разделяемых файлов. Начиная с версии 3.1 в MS-DOS включается поддержка сетевых структур. В 1988 г. разработана версия MS-DOS 4.0, в которую была включена поддержка разделов на жестких дисках, превышающих 32 Мбайт, средства эмуляции дополнительной памя­ти, а также ряд новых команд. Наиболее привлека­тельной чертой MS-DOS 5.0 явилась возможность организации на компьютерах с расширенной памятью специальных областей, куда можно загружать устанавливаемые драйверы, резидентные программы и большую часть самой DOS. Это позволило сущест­венно увеличить объем памяти, отводимой прикладным программам (до 600 – 610 Кбайт). Операционная система MS-DOS 6.0, выпущенная в 1993 г., вобрала в себя все лучшие качества предыдущих версий. В систему был включен целый ряд полноэкранных инструментальных утилит, охватыва­ющих широкий диапазон потребностей пользователей персональных компьютеров. Утилиты имели развитый интерфейс пользователя, могли управляться как от клавиатуры, так и мышью, включали контекстные справочники и элементы обучающих систем. Важнейшим усовершенствованием, введенным в версию MS-DOS 6.0, явилась возможность задания в процессе начальной за­грузки альтернативных конфигураций системы (методика использо­вания расширенной и дополнительной памяти, состав загружаемых драйверов устройств, наличие и характеристики электронных дис­ков и пр.).
Управление компьютером при помощи команд DOS требует определенных знаний, большой аккуратности и внимания. Для того, чтобы сделать общение с компьютером более простым, были разработаны специальные программы-оболочки. Операционная оболочка – это такая программа, которая позволяет пользователю осуществлять действия по управлению ресурсами компьютера в рамках более развитого (более удобного и интуитивно понятного) интерфейса, чем командная строка. Начиная с версии 4.0 в MS-DOS входила собственная псевдографическая оболочка SHELL, однако наибольшую популярность среди оболочек DOS завоевал пакет программ Norton Commander фирмы Symantec. Использование операционной оболочки Norton Commander значительно yпростило управление компьютером, позволило в наглядном виде получать информацию о его основных ресурсах (и их загруженности), осуществлять все основные процедуры управления компьютером (выбор диска и каталога; создание каталога; создание, просмотр и редактирование текстовых файлов; копирование, перемещение, удаление файлов и каталогов; поиск файлов и каталогов; работа с архивными файлами и т. п.).
Также необходимо отметить, что Norton Commander явился не единственным шагом фирмы Symantec по расширению функциональных возможностей служебного и системного программного обеспечения, работающего в среде DOS. Другой ее извест­ный продукт – Norton Utilities – объединил в себе большое количество утилит, реализующих многие важные и полезные функции, которые затруднительно или даже невозможно осуществить с помощью штатных средств операционной системы.
Следующим шагом в развитии оболочек операционных систем стало появление в 1986 г. графической многооконной операционной оболочки Windows от корпорации Microsoft. В последующие годы она претерпела ряд модификаций и в 1991 г. вышла вepcия Windows 3.1, а несколько позже – сетевой вариант Windows 3.11 (Windows 3.11 For WorkGroups), очень быстро завоевавшие широкое признание пользователей. Windows 3.1 запускалась на выполнение как обычная программа MS-DOS и работала на базе MS-DOS, используя на нижнем уровне внутренние функции и процедуры этой операционной системы. Приципиальным условием для программных приложений, предназначенных для работы в среде Windows, являлось то, что они должны работать с внешними устройствами (монитором, принтером, плоттером и т. п.) не напрямую, а через универсальную систему команд. Управляющая система транслировала вызовы (обращения к тому или иному физическому устройству) и передавала их соответствующему Windows-драйверу данного устройства, который непосредственно отвечал за работу с ним с учетом конкретных особенностей его функционирования. Почти все драйверы устройств Windows 3.1 фактически выполняли функции базовой системы ввода-вывода и работали с устройствами напрямую.
Основой пользовательского интерфейса Windows послужил так называемый графический интерфейс пользователя GUI (Graphical User Interface), разработанном еще в 1960-е годы Дагом Энгельбартом (Doug Engelbart) в научно-исследовательском институте Стэнфорда (Stanford Research Institute) и состоящим из окон, значков, различных меню и мыши. Идеи GUI впервые были использованы в разработках вычислительных машин Xerox PARC, а затем успешно внедрены в качестве пользовательского интерфейса в персональных компьютерах Apple компании Macintosh.
Итак, пользовательский интерфейс Windows 3.1 являлся графическим (использовался графический режим работы видеомонитора) и представлял собой иерархически организованную систему окон и других графических объектов. Интерфейс Windows в отличие от интерфейса командной строки в DOS и псевдографического интерфейса оболочки Norton Commander реализовывал оперативное управление на основе выбора того или иного графически визуализированного элемента (кнопки, пиктограммы, списка и т. п.) с помощью манипулятора мышь (команды клавиатуры, как правило, имели вспомогательное или резервное значение). Основным отличием версии Windows 3.11 была интеграция в программный пакет сетевых драйверов, что позволяло использовать эту версию для работы компьютеров в сети.
Дальнейшим развитием семейства Microsoft Windows стала разработка полноценных операционных систем Windows 95 (Windows 4.0) и Windows NT, положившим начало двух ветвей ОС от Microsoft: Windows 95/98/ME и Windows NT/2000/XP/2003. Более подробная информация об этих системах представлена далее в подразделе 6.3.
Значительную роль в развитии операционных систем играет фирма Novell. Наибольшую известность Novell приобрела благодаря своим сетевым операционным системам семейства NetWare. Эти системы реализованы как системы с выделенными серверами. Основные усилия Novell были затрачены на создание высокоэффективной серверной части сетевой ОС, которая за счет специализации на выполнении функций файл-сервера обеспечивала бы максимально возможную для данного класса ВМ скорость удаленного доступа к файлам и повышенную безопасность данных. Для серверной части своих ОС Novell разработала специализированную операционную систему, оптимизированную на файловые операции и использующую все возможности, предоставляемые процессорами Intel x386 и выше. Для рабочих станций Novell выпустила собственные ОС со встроенными сетевыми функциями: Novell DOS с входящей в нее сетевой одноранговой компонентой Personal Ware, а также ОС UnixWare, являющейся реализацией UNIX System V со встроенными возможности работы в сетях NetWare.
Первая рабочая версия NetWare была выпущена фирмой Novell в 1983 г. В 1993 году фирма Novell выпустила ОС NetWare v.4.0, явившуюся во многих отношениях революционно новым продуктом. Эта система была разработана специально для построения вычислительных сетей «масштаба предприятия» с несколькими файл-серверами, большим количеством сетевых ресурсов и пользователей. Одним из основных нововведений явилась служба каталогов NetWare Directory Services (NDS), хранящая в распределенной по нескольким серверам базе данных информацию обо всех разделяемых сетевых ресурсах и пользователях, что обеспечило возможность при одном логическом входе в систему получать прозрачный доступ ко всем ресурсам многосерверной сети.
NetWare – это специализированная ОС, которая с самого начала проектировалась для оптимизации сетевого сервиса и, в первую очередь, доступа к удаленным файлам. Кроме основной цели разработки семейства ОС NetWare – повышения производительности – ставились также задачи создания открытой, расширяемой и надежной операционной системы, обеспечивающей высокий уровень защиты информации. В 1983 году фирма Novell ввела в систему концепций локальной сети понятия имени пользователя, пароля и характеристики пользователя («профиля пользователя»). Характеристика пользователя содержит перечень ресурсов, к которым пользователь имеет доступ, и права, которыми он обладает при работе с этими ресурсами. Администратор сети может ограничить права пользователя по входу в сеть датой, временем и конкретными рабочими станциями. Средства обнаружения нарушений защиты и блокировки действий нарушителя извещают администратора сети о попытках несанкционированного доступа. Помимо поддержки многопроцессорного режима, в число приоритетных направлений развития NetWare входит обеспечение процессорной независимости.
Операционная система OS/2 v.2.0 корпорации IBM была первой работающей 32-х разрядной ОС для персональных компьютеров. Версия OS/2 Warp, предназначенная для клиентских машин сетей типа клиент-сервер и одноранговых сетей, появилась на рынке раньше Windows 95, позиционированной аналогичным образом. OS/2 поддерживала вытесняющую многозадачность, виртуальную память и виртуальную машину для выполнения DOS-приложений, а также средства объектной ориентации. OS/2 Warp имела хорошо продуманный объектно-ориентированный графический пользовательский интерфейс, а также впервые включала набор средств поддержки сети Internet.
С интенсивным развитием компьютерных сетей в 1990-е годы появились специализированные ОС, которые предназначены исключительно для выполнения коммуникационных задач. Примером такой системы является ОС IOS компании Cisco Systems, которая работает в маршрутизаторах и организует выполнение в мультипрограммном режиме набора программ, реализующих коммуникационные протоколы.
Следует отметить также некоторые ОС, ориентированные на конкретную аппаратную платформу компьютеров, например, MacOS для компьютеров семейства Macintosh, PalmOS и Windows CE (Consumer Electronics – бытовая электроника) для серии сверхминиатюрных так называемых «карманных» компьютеров или PDA (Personal Digital Assistant – персональный цифровой помощник).
Среди операционных систем реального времени заслуженной популярностью пользуются ОС VxWorks и QNX.
Высоким уровнем отказоустойчивости при работе с сетью Internet и интрасетями характеризуются операционные системы SOLARIS фирмы Sun Microsystems. Однако в полной мере высокий уровень отказоустойчивости, обеспечиваемый благодаря сильным сторонам архитектуры ядра системы, может быть реализован только для платформы процессоров SPARC, а аппаратная поддержка платформы Intel ограничена.

6.2. История развития и характеристики
операционных систем UNIX

История операционной системы UNIX началась в 1969 году с совместного проекта Массачусетского технологического института, исследовательской лаборатории Bell Labs и корпорации General Electric – системы MULTICS (Multiplexed Infor­mation and Computing Service – информационно-вычислительная служба с муль­типлексированием каналов передачи данных). Ее разработчики поставили своей задачей создание большой и сложной системы общего назначения с разделением времени. Лаборатория Bell Labs вскоре вышла из числа участников проекта, по­сле чего один из ее сотрудников, Кен Томпсон (Ken Thompson), к которому затем присоединился Денис Ритчи (Dennis Ritchie), создал усеченный вариант систе­мы MULTICS для миникомпьютера PDP-7. Эта система в шутку была названа UNICS (UNiplexed Information and Computing Service – примитивная информационная и вычислительная служба). В дальнейшем, сохранив то же произношение, система UNICS получила несколько «сокращенное» написание в виде UNIX. UNIX привлекла внимание других специа­листов, группа разработчиков увеличилась, и система была перенесена на более современные вычислительные машины PDP-11 (доминирующие в нише миниВМ в 1970-е годы), причем ее переписали на языке программиро­вания высокого уровня С, специально созданном для этой цели Денисом Ритчи. Это событие произошло в 1973 году, когда еще не было принято писать операционные системы на высокоуровневых языках, но время для этого уже пришло, поскольку проблема переносимости системного программного обеспечения между различными аппаратными плат­формами стояла довольно остро. UNIX обладала огромным преимуществом перед другими ОС – она была переносимой и могла быть установлена, по край­ней мере потенциально, на ВМ любой архитектуры.
Поначалу UNIX была сравнительно маленькой, очень простой в использовании и по­нятной системой. Первая ее версия, однопользовательская, вскоре была расширена и стала поддерживать многопользовательский доступ. Разработчики стремились максимально упростить структуру операционной системы, пусть даже за счет сни­жения эффективности работы и удаления некоторых функций, которыми на то время обладало большинство подобных систем. UNIX быстро приобрела популярность не только в Bell Labs, но и за ее пределами. Корпо­рация AT&T (учредитель лаборатории Bell Labs) стала вкладывать средства в развитие UNIX, в результате чего было выпущено несколько новых версий ОС, архитектура которых все более усложнялась. Конечным результатом усилий корпорации AT&T стал продукт под названием System V, развитие которого затем было продолжено уже другими компаниями.
Параллельно с указанной ветвью операционной системы UNIX специалисты Калифорний­ского университета в Беркли разрабатывали еще одну, названную BSD (Berkeley Software Distribution – программное изделие Калифорнийского университета). Она тоже получила широкое распространение, а впоследствии на основе обеих ветвей был создан ряд новых версий ОС.
Клас­сической UNIX принято считать так называемую седьмую версию ОС, которая является исходной точкой двух основных ветвей развития данной архитектуры: System V и BSD.
Третья самостоятельная ветвь развития UNIX началась с попытки вернуться к ис­ходному свойству этой операционной системы – предельной простоте. Данная ветвь начинается с микроядерной системы MINIX, за которой последовалазна­чительно более мощная система Linux, разработанная Линусом Торвальдсом (Linus Torvalds) и представленная им в 1991 г. в качестве первой официальной версии 0.02. В настоящее время Linux – это полноценная многоза­дачная многопользовательская операционная система семейства UNIX. Одним из наиболее удачных вариантов реализации Linux является дистрибутивный комплект Red Hat Linux. Главными особенностями, выделяющими его на фоне других версий Linux, являются, во-первых, наличие средств управления пакетами (Red Hat Package Manager, RPM), служащих для установки программ, проверки их целостности, обновления и удаления программного обеспечения, а во-вторых, наличие графической панели управления (Control panel), с помощью которой можно осуществлять контроль практически за всеми ресурсами ВМ.
Для совместимости различных версий операционной системы UNIX и производных от нее систем Международный институт инженеров по электротехнике и электронике IEEE разработал стандарт системы UNIX, называемый POSIX, который теперь поддерживают большинство версий UNIX. Стандарт POSIX определяет минимальный интерфейс системного вызова, необходимый для совместимости UNIX-систем. Некоторые другие операционные системы в настоящее время тоже поддерживают интерфейс POSIX.
На сегодняшний день существуют версии UNIX для многих ВМ – от персональных компьютеров до су­перВМ. Независимо от версии, общими для UNIX чертами являются:
– многопользовательский режим со средствами защиты данных от несанкционированного доступа,
– реализация мультипрограммного режима разделения времени, основанного на использовании алгоритмов вытесняющей многозадачности,
– использование механизмов виртуальной памяти и свопинга,
– унификация операций ввода-вывода на основе расширенного использования понятия «файл»,
– иерархическая файловая система, образующая единое дерево каталогов независимо от количества физических устройств, используемых для размещения файлов,
– переносимость системы за счет написания ее основной части на языке С,
– разнообразные средства взаимодействия процессов, в том числе и через сеть,
– кэширование диска для уменьшения среднего времени доступа к файлам.
Хотя многие пользователи UNIX, особенно опытные программисты, предпо­читают командный интерфейс графическому, почти все UNIX-системы поддержи­вают оконную систему, созданную в Массачусетсском технологическом институте. Она называется X Windows. Эта система оперирует основными функциями окна, позволяя пользователю создавать, удалять, перемещать окна и изменять их разме­ры с помощью мыши. Часто поверх системы X Windows может быть установлен полный графический интерфейс, например Motif, придающий системе UNIX вне­шний вид системы типа Microsoft Windows или системы компьютеров Macintosh.

6.3. История развития и характеристики
операционных систем семейства Windows

Особое значение в истории и сегодняшнем дне операционных систем имеет семейство продуктов Windows корпорации Microsoft как наиболее популярных ОС для персональных компьютеров и сетей на их основе.
Как уже отмечалось выше, вплоть до версий Windows 3.1/3.11 это была не операционная система в полном смысле данного понятия, а лишь графическая оболочка, надстройка над ОС MS-DOS. Полноценной ОС Windows стала начиная с четвертой версии, получившей наименование Windows 95. Ключевыми позициями, отличающими Windows 95 от Windows 3.х, явились следующие:
– новое 32-разрядное ядро;
– усовершенствованный механизм многозадачности;
– улучшенная поддержка аппаратного обеспечения;
– новые и существенно обновленные приложения.
После Windows 95 на рынке появилась ее новая редакция Windows 95 OSR2 (OEM Service Release – «сервисный выпуск для производителей компьютеров»). В эту редакцию были включены следующие чрезвычайно полезные дополнения:
– новая файловая система FAT32;
– встроенный браузер-обозреватель Internet Explorer 3.0;
– поддержка трехмерной графики;
– расширенный состав драйверов;
– повышенная стабильность работы системы.
Следующей версией стала Windows 98. Главным достоинством новой системы явилось включение более мощного браузера Internet Explorer 4.0 и обеспечение поддержки новых аппаратных устройств. Дальнейшим совершенствованием стала разработка Windows 98 SE (Second Edition), в которой была расширена поддержка аппаратных устройств с новыми интерфейсами USB и FireWire, включена новая версия браузера Internet Explorer 5.0, а также исправлены некоторые ошибки, добавлены новые функции и в целом существенно повышена стабильность работы.
Последней ОС в ряду Windows 9.х стала Windows ME (Millennium Edition). В нее включены новая система восстановления ОС, встроенный редактор видео, новый Internet Explorer 5.5. В Windows ME значительно обновлен графический интерфейс и упрощена работа с драйверами.
Параллельно с развитием линии ОС Windows 9.х корпорация Microsoft в 1988 году начала разработку и непрерывно продолжает развивать линию принципиально отличающихся от Windows 9.х операционных систем «новой технологии» – Windows NT (NT – New Technology). Если при создании Windows 9.х разработчики стремились сочетать новые возможности с предельной простотой установки и конфигурирования, максимальной совместимо­стью с имеющимся программным и аппаратным обеспечением, то серия NT прежде всего предназначалась для больших сетей и должна была обеспечить максималь­ную надежность и безопасность.
Команду разработчиков новой операционной системы возглавил перешедший в Microsoft из Digital Equipment Corporation Дэйв Катлер (Dave Cutler), ранее уже участвовавший в разработке нескольких ОС. К тому времени уже был накоплен немалый опыт академических исследований в облас­ти операционных систем, в том числе на основе Mach и других микроядерных ар­хитектур. Результатом трудов команды из почти 40 разработчиков стало появление в 1993 году операционной системы, получившей название Windows NT и поддерж­ивающей процессоры Intel x86, MIPS и Digital Alpha. Годом позже вышла версия Windows NT 3.51 с повышенной производительностью и поддержкой процессора PowerPC. За ней в 1996 последовала Windows NT 4.0.
Windows NT является 32-х разрядной ОС с приоритетной многозадачностью. В качестве фундаментальных компонентов в состав этой ОС входят развитый сетевой сервис и средства обеспечения безопасности. Windows NT обеспечивает совместимость со многими другими операционными и файловыми системами, а также с разнородными сетями, поддерживает высокопроизводительные многопроцессорные вычислительные комплексы. Windows NT не является дальнейшим развитием ранее существовавших продуктов. Ее архитектура создавалась заново с учетом современных жестких требований совместимости, переносимости, масштабируемости, распределенной обработки данных, расширяемости, надежности и отказоустойчивости операционных систем.
Несмотря на сходный интерфейс, Windows NT 4.0. имеет массу отличий от современной ей Windows 95:
– принципиально другое ядро, которое загружается самостоятельно и не имеет в своей основе MS-DOS;
– собственная файловая система NTFS, обеспечива­ющая разграничение доступа на уровне файлов, протоколирование файловых операций, хране­ние сверхбольших объемов данных, сжатие дис­ков и прочее;
– иной механизм многозадачности, обеспечиваю­щий лучшую изоляцию приложений друг от друга и от ядра системы (в частности, драйверы уст­ройств здесь не допускаются к ядру ОС, поэтому принципы написания драйверов для семейств 9.х и NT сильно отличаются);
– существенно расширенные многопользователь­ские возможности, чему способствует наличие серьезных средств авторизации, защиты трафи­ка и т.п;
– высокая степень масштабируемости.

На смену версии Windows NT 4.0. пришла ОС Windows 2000 Professional (в бета-версиях она называлась Windows NT 5.0.), с которой началась новая схема именования версий системы.
Windows 2000 Professional позволяет:
1. Облегчить использование ОС корпоративными клиентами, что достигается благодаря привычному, но более простому и «интеллектуальному» интерфейсу, упрощению настройки системы путем использованию новых «про­грамм-мастеров», ориентацией на работу с мобильными компьютерами, наличию эффективных встроенных инструментов для работы с Интернет.
2. Сохранить традиционные достоинства систем Windows NT: защищенность информации, устойчивость работы, надежность и высокую производи­тельность.
3. Перенести в систему лучшие качества Windows 98, такие как поддержка существующих приложений и драйверов, поддержка аппаратных устройств нового поколения, встроенная сетевая поддержка для подключения к системам Win­dows NT Server, Novell NetWare и UNIX.
4. Создать легко конфигурируемую настольную систему с автоматизированным процессом инстал­ляции, развитыми средствами удаленного администри­рования, установки и удаления программ, встроенной диагностики про­цесса загрузки.
Серверной версией Windows 2000 Professional является система Windows 2000 Server. Эта серверная платформа способна функционировать как сервер файлов, печати, приложений или Web-сервер и по сравнению с Windows 2000 Professional включает множество дополнительных специальных функций.
В конце 2001 г. корпорация Microsoft выпустила новую версию ОС – Windows XP, продолжившую линию Win­dows NT (внутренняя «фирменная» нумерация новой версии – Win­dows NT 5.1) и по сути являющейся модифицированной ОС Windows 2000 Professional. В новой ОС радикально изменен графический интерфейс, существенно повышена стабильность системы, значительно усовершенствована процедура инсталляции, усилена аппаратная и программная совместимость.
Новая серверная версия системы Windows 2000 Server – ОС Windows Server 2003 (внутренняя «фирменная» нумерация этой версии – Win­dows NT 5.2). Windows Server 2003 обладает рядом преимуществ по сравнению с Windows 2000 Server. Это самая быстрая, самая надежная и наиболее защищенная из всех серверных ОС Windows, когда-либо выпущенных Microsoft.
Windows Server 2003 предоставляет интегрированную инфраструктуру, по­могающую гарантировать безопасность информации. Надежность, готовность и масштабируемость этой ОС по­зволяют реализовать сетевую инфраструктуру, соответ­ствующую потребностям наиболее взыскательных пользователей. Windows Server 2003 предоставляет инст­рументы для развертывания, управления и использования сетевой инфраструктуры предприятия, средства реализации административной политики, автоматизации задач и упро­щения процесса модернизации. Новые средства ОС помогают снизить расходы на сопровождение, позволяя пользователям выполнять большее число задач самостоятельно. Windows Server 2003 помогает организовать инфраструктуру прикладных задач и обеспечивает лучшее вза­имодействие между системами и клиентами. Для этого ОС предоставляет интегрированные Web-сервер и сервер мультимедийных потоков, которые позволя­ют легко, быстро и безопасно создавать динамичные Web-сайты для интрасети и Интернета. ОС также включает ин­тегрированный сервер приложений, обеспечивающий лег­кость разработки, развертывания и управления Web-серви­сами.
Высокая надежность Windows Server 2003 позволяет управлять расходами, снижая время ремонтных работ и регламентных про­стоев. Windows Server 2003 может гибко масштабироваться вверх и вширь в зависимости от текущих потребностей. Инструменты администрирования и настройки Windows Ser­ver 2003 упрощают развертывание и управление. Обеспечивается совместимость с существующими приложениями и продуктами независимых производителей.

Резюме

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

Следующим этапом эволюции ОС стала разработка в 1960-х годах универсальных ОС, которые были способны работать на разных типах ВМ, имеющих различный набор периферийных устройств и используемых в разных областях человеческой деятельности. Попытки удовлетворения сложных и часто противоречивых требований приводили к тому, что такие ОС были чрезвычайно громоздкими и имели низкую надежность при эксплуатации. Однако, несмотря на указанные проблемы, операционная система OS/360 и другие ей подобные ОС получили широкое признание потребителей. Важнейшим достижением ОС этого периода явилась реализация мультипрограммного режима и спулинга.
Следующий период в эволюции операционных систем связан с появлением в 1980-х годах ВМ на больших интегральных схем. ВМ стали доступны отдельным небольшим организациям и учреждениям, а впервые предложенные в то время «персональные компьютеры» были доступны уже отдельному человеку. ВМ все чаще объдинялись в распределенные вычислительные сети. Такие ВМ и сети на их основе стали широко использоваться неспециалистами, что потребовало разработки «дружественного» программного поддержки сетевого взаимодействия. На рынке операционных систем в то время начали доминировать системы двух классов: многопользовательские многозадачные (мультипрограммные) ОС клона UNIX и однопользовательские однозадачные (однопрограммные) ОС клона MS-DOS.
ОС UNIX получила несколько ветвей развития исходной архитектуры. Это ОС System V (корпорации AT&T) и BSD (Калифорний­ского университета в Беркли). Впоследствии на основе обеих ветвей был создан ряд новых версий ОС UNIX. Третья самостоятельная ветвь развития UNIX начиналась с микроядерной системы MINIX, за которой в 1991 году последовала зна­чительно более мощная многоза­дачная многопользовательская ОС LINUX.
В настоящее время существуют версии ОС UNIX для многих типов ВМ. Независимо от версии, общими для UNIX чертами являются многопользовательский режим со средствами защиты данных от несанкционированного доступа, реализация мультипрограммного режима разделения времени с вытесняющей многозадачностью, использование механизмов виртуальной памяти и свопинга, унификация операций ввода-вывода на основе расширенного использования понятия «файл», иерархическая файловая система, хорошая переносимость системы и множество других свойств.
Операционные системы корпорации Microsoft под названием MS-DOS и их аналоги других корпораций были разработаны для персональных компьютеров клона IBM PC. Управление компьютером при помощи команд DOS, вводимых в режиме командной строки, требует определенных знаний, большой аккуратности и внимания. Для того, чтобы сделать общение с компьютером более простым, были предложены так называемые программы-оболочки, представляющие собой программные надстройки операционной системы, позволяющие пользователю осуществлять действия по управлению ресурсами компьютера в рамках более развитого и удобного, чем командная строка, псевдографического интерфейса,. Следующим историческим шагом в развитии оболочек операционных систем стало появление в 1986 г. графической многооконной операционной оболочки Windows от корпорации Microsoft, которая работала на базе MS-DOS, а основой пользовательского интерфейса Windows послужил графический интерфейс пользователя GUI, представляющий собой в данном случае иерархически организованную систему окон и других графических объектов. Дальнейшим развитием семейства Microsoft Windows стала разработка полноценных операционных систем Windows 95 (Windows 4.0) и Windows NT, положившим начало двух ветвей ОС от Microsoft: Windows 95/98/ME и Windows NT/2000/XP/2003.
Полноценной ОС Windows стала начиная с четвертой версии, получившей наименование Windows 95. Ключевыми позициями, отличающими Windows 95 от Windows 3.х, явились новое 32-разрядное ядро, усовершенствованный механизм многозадачности, улучшенная поддержка аппаратного обеспечения, новые и существенно обновленные приложения. Следующими версиями стали Windows 98, Windows 98 SE и Windows ME. Главными достоинствами новых систем явилось включение более мощных версий браузера Internet Explorer, расширение поддержки новых аппаратных устройств с новыми интерфейсами.
Параллельно с развитием линии ОС Windows 9.х корпорация Microsoft в 1988 году начала разработку и непрерывно продолжает развивать линию принципиально отличающихся от Windows 9.х операционных систем «новой технологии» – Windows NT (NT – New Technology). Windows NT является 32-х разрядной ОС с приоритетной многозадачностью. В качестве фундаментальных компонентов в состав этой ОС входят развитый сетевой сервис и средства обеспечения безопасности. Windows NT совместима со многими другими операционными и файловыми системами, а также с разнородными сетями, поддерживает работу высокопроизводительных многопроцессорных вычислительных комплексов.
На смену версии Windows NT 4.0. пришла существенно усовершенствованная и усиленная ОС Windows 2000 Professional. Серверной версией Windows 2000 Professional является система Windows 2000 Server, включающаяет множество дополнительных специальных функций.
В конце 2001 г. корпорация Microsoft выпустила новую версию ОС – Windows XP, продолжившую линию Win­dows NT и по сути являющейся модифицированной ОС Windows 2000 Professional. Новая серверная версия системы Windows Server 2003 обладает рядом преимуществ по сравнению с Windows 2000 Server. Это самая быстрая, самая надежная и наиболее защищенная из всех серверных ОС Windows, выпущенных Microsoft до настоящего времени.
Значительную роль в развитии ОС играет фирма Novell со своим сетевым операционными системами семейства NetWare.
Историческое значение имеет ОС OS/2 корпорации IBM, которая появилась на рынке раньше Windows 95 и была первой работающей 32-х разрядной ОС для персональных компьютеров, а кроме того поддерживала вытесняющую многозадачность, виртуальную память и имела средства работы в сети Internet.
Следует отметить также некоторые специализированные ОС. Например, IOS компании Cisco Systems предназначена исключительно для выполнения коммуникационных задач, MacOS ориентирована на конкретную аппаратную платформу компьютеров семейства Macintosh, PalmOS и Windows CE работают в сверхминиатюрных так называемых «карманных» компьютерах.
Отметим также ОС Solaris фирмы Sun Microsystems, которая благодаря высокому уровню отказоустойчивости предпочтительна для использования в интер- и интрасетях.





Контрольные вопросы и задания

1. Охарактеризуйте начальные этапы разработки и развития ОС.
2. Опишите основные характерные особенности ОС семейства MS-DOS и историю их эволюции.
3. Какие функции выполняют операционные оболочки, какова их роль в совершенствовании пользовательского интерфейса ОС?
4. Дайте характеристику ОС семейства UNIX, представьте области практического применения наиболее популярных версий UNIX.
5. В чем особенности реализации и функционирования сетевых операционных систем компании Novell?
6. Охарактеризуйте ОС семейства Windows 95/98/ME.
7. Назовите главные достоинства ОС линии Windows NT.
8. Какие качества характерны для современных серверных версий ОС Windows?
9. Перечислите известные специализированные ОС.

7. Пример практической реализации
операционной системы: UNIX
7.1. Обзор системы UNIX
7.1.1. Общие представления
Операционная система UNIX представляет собой интерактивную систему, раз­работанную для одновременной поддержки нескольких процессов и нескольких пользователей. Она была разработана программистами и для программистов, что­бы использовать ее в окружении, в котором большинство пользователей являют­ся относительно опытными и занимаются сложными проектами разработки программного обеспечения. Во многих случаях большое количество программистов активно сотрудничают в деле создания единой системы, поэтому в операционной системе UNIX есть достаточное количество средств, позволяющих программистам работать вместе и управлять совместным использованием общей информации. К настоящему времени существует множество версий системы UNIX (см. раздел 6) и между ними имеются определенные различия. В дальнейшем изложении основное внимание уделяется общим чертам всех версий, а не особенностям какой-либо конкретной версии. Поэтому рассматриваемые воп­росы реализации ОС UNIX не всегда в равной степени соответствуют разным версиям.
7.1.2. Интерфейсы системы UNIX
Операционную систему UNIX можно рассматривать в виде некоторой пирамиды. У основания пирамиды располагается аппаратное обеспечение, состоящее из цен­трального процессора, памяти, дисков, терминалов и других устройств. Выше над аппаратным обеспечением работает операционная система UNIX. Ее функции заключаются в управ­лении аппаратным обеспечением и предоставлении всем программам интерфейса системных вызовов. Эти системные вызовы позволяют программам создавать про­цессы, файлы и прочие ресурсы, а также управлять ими. Программы обращаются к системным вызовам, помещая аргументы в регист­ры центрального процессора (или иногда в стек) и выполняя команду эмулиро­ванного прерывания для переключения из пользовательского режима в режим ядра и передачи управления операционной системе UNIX. Системные вызовы реализуются библиотечные функциями – процедурами. Каждая такая процедура помещает аргументы в нужное место и выполняет команду эму­лированного прерывания. Интерфейс библиотечных функций определен в стандарте POSIX. Стандарт POSIX определяет библиотечные процедуры, соответствующие системным вызовам, их параметры, что они должны делать и какой результат возвращать.
Помимо операционной системы и библиотеки системных вызовов, все вер­сии UNIX содержат большое количество стандартных программ, некоторые из них описываются стандартом POSIX 1003.2, тогда как другие могут различаться в разных версиях системы UNIX. К этим программам относятся командный про­цессор (оболочка), компиляторы, редакторы, программы обработки текста и утили­ты для работы с файлами. Именно эти программы и запускаются пользователем с терминала.
Таким образом, можно говорить о трех интерфейсах в операционной систе­ме UNIX: интерфейсе системных вызовов, интерфейсе библиотечных функций и интерфейсе, образованным набором стандартных обслуживающих программ.
7.1.3. Оболочка и утилиты системы UNIX
У многих версий системы UNIX имеется графический интерфейс пользователя, схожий с популярными интерфейсами, примененными на компьютере Macintosh и впоследствии в системе Windows. Однако истинные программисты до сих пор предпочитают интерфейс командной строки, называемый оболочкой (shell). По­добный интерфейс значительно быстрее в использовании, существенно мощнее и проще расширяется. Ниже будет кратко описана так называемая оболочка Бурна (sh).
Когда оболочка запускается, она инициализируется, а затем печатает на экране символ приглашения к вводу (обычно это знак доллара или процента) и ждет, ког­да пользователь введет командную строку. После того как пользователь введет командную строку, оболочка извлекает из нее первое слово и ищет файл с таким именем. Если такой файл удается найти, оболочка запускает его. При этом работа оболочки приостанавливается на время работы запущенной программы. По завершении работы программы оболочка снова печатает приглашение и ждет ввода следующей строки. Здесь важно под­черкнуть, что оболочка представляет собой обычную пользовательскую програм­му. Все, что ей нужно, – это способность ввода с терминала и вывода на терминал, а также возможность запускать другие программы.
У команд оболочки могут быть аргументы, которые передаются запускаемой программе в виде текстовых строк. Не все аргументы обязательно должны быть именами файлов. Аргументы, управляющие работой команды или указывающие дополнительные значения, называются флагами или клю­чами и по соглашению обозначаются знаком тире.
Программа вроде оболочки не должна открывать терминал, чтобы прочитать с него или вывести на него строку. Вместо этого запускаемые программы автома­тически получают доступ к файлу, называемому стандартным устройством вво­да (standard input), и к файлу, называемому стандартным устройством вывода (standard output), а также к файлу, называемому стандартным уст­ройство для вывода сообщений об ошибках (standard error). По умолчанию всем трем устрой­ствам соответствует терминал, то есть клавиатура для ввода и экран для вывода. Многие программы в системе UNIX читают данные со стандартного устройства ввода и пишут на стандартное устройство вывода. Стандартные ввод и вывод также можно перенаправить, что является очень полезным свойством. Для этого используются символы «<» и «>» соответственно. Разрешается их одновременное использование в одной командной строке. Программа, считывающая данные со стандартного устройства ввода, выполняющая определенную обработку этих данных и записывающая результат в поток стандартного вывода, называется фильтром.
В системе UNIX часто используются командные строки, в которых первая про­грамма в командной строке формирует вывод, используемый второй программой в качестве входа. Система UNIX предоставляет более простой способ реализации этого механизма, который заключается в использовании вертикальной черты, называемой символом канала. Набор команд, соединенных симво­лом канала, называется конвейером и может содержать произвольное количество команд.
UNIX является универсальной многозадачной системой. Один пользователь может одновременно запустить несколько программ, каждую в виде отдельного процесса. Конвейеры также могут выполняться в фоновом режиме. Можно одновременно запустить несколько фоновых конвейеров.

Список команд оболочки может быть помещен в файл, а затем этот файл с ко­мандами может быть выполнен, для чего нужно запустить оболочку с этим фай­лом в качестве входного аргумента. Вторая программа оболочки просто выполнит перечисленные в этом файле команды одну за другой, точно так же, как если бы эти команды вводились с клавиатуры. Файлы, содержащие команды оболочки, называются сценариями оболочки. Сценарии оболочки могут присваивать значе­ния переменным оболочки и затем считывать их. Они также могут запускаться с параметрами. Таким образом, сценарии оболочки представляют собой настоящие программы, написан­ные на языке оболочки. Существует альтернативная оболочка Berkley С, разра­ботанная таким образом, чтобы сценарии оболочки (и команды языка вообще) выглядели во многих аспектах подобно программам на языке С. Поскольку оболочка представляет собой всего лишь еще одну пользовательскую программу, к настоящему времени на­писано множество различных ее версий.
Пользовательский интерфейс UNIX состоит не только из оболочки, но также из большого числа стандартных обслуживающих программ, называемых также утили­тами. К ним относятся команды управления файлами и каталогами, фильтры, средства разработки программ (такие как текстовые редакторы и компи­ляторы), текстовые процессоры, программы системного администрирования и другие программы. Стандарт POSIX 1003.2 определяет синтаксис и семантику менее 100 из этих программ, в основном относящихся к первым трем категориям. Идея стандарти­зации данных программ заключается в том, чтобы можно было писать сценарии оболочки, которые работали бы на всех системах UNIX. Помимо этих стандарт­ных утилит, существует множество других прикладных программ, таких как web-браузеры, программы просмотра изображений и т. д.
7.1.4. Структура ядра системы UNIX

Нижний уровень ядра состоит из драйверов устройств и процедуры диспетче­ризации процессов. Все драйверы системы UNIX делятся на два класса: драйверы символьных устройств и драйверы блочных устройств. Основное различие между этими двумя классами устройств заключается в том, что на блочных устройствах разрешается операция поиска, а на символьных нет. Технически сетевые устрой­ства представляют собой символьные устройства, но они обрабатываются по-ино­му, поэтому их правильнее выделить в отдельный класс. Диспетчеризация процессов производится при возникновении прерывания. При этом низкоуровневая программа останавливает выполнение ра­ботающего процесса, сохраняет его состояние в таблице процессов ядра и запус­кает соответствующий драйвер. Кроме того, диспетчеризация процессов произво­дится также, когда ядро завершает свою работу и наступает время для того, чтобы снова запустить процесс пользователя. Программа диспетчеризации процессов написана на ассемблере и представляет собой отдельную от процедуры планирования программу.
В более высоких уровнях программы, относящиеся к тому или иному уровню могут существенно отличаться друг от друга. Так ко второму снизу уровню относятся программы символьных устройств, драйверы сетевых устройств, драйверы дисковых устройств, программы диспетчиризации процессов.
Сетевое программное обеспечение часто бывает модульным, с поддержкой мно­жества различных устройств и протоколов. Уровень выше сетевых драйверов вы­полняет своего рода функции маршрутизации, гарантируя, что правильный пакет направляется правильному устройству или блоку управления протоколами. Боль­шинство систем UNIX содержат в своем ядре полноценный маршрутизатор Ин­тернета, и хотя его производительность ниже, чем у аппаратного маршрутизатора, эта программа появилась раньше современных аппаратных маршрутизаторов. Над уровнем маршрутизации располагается стек протоколов, обязательно включая протоколы IP и TCP, но также иногда и некоторые дополнительные протоколы. Над сетевыми протоколами располагается интерфейс сокетов, позволяющий про­граммам создавать сокеты для отдельных сетей и протоколов. Для использования сокетов пользовательские программы получают дескрипторы файлов.
Над дисковыми драйверами располагаются буферный кэш и страничный кэш файловой системы. В ранних системах UNIX буферный кэш представлял собой фиксированную область памяти, а остальная память использовалась для страниц пользователя. Во многих современных системах UNIX этой фиксированной гра­ницы уже не существует, и любая страница памяти может быть схвачена для вы­полнения любой задачи, в зависимости от того, что требуется в данный момент.

Над буферным кэшем располагаются файловые системы. Большинством сис­тем UNIX поддерживаются несколько файловых систем. Все файловые системы совместно используют общий буферный кэш. Выше файловых систем помещается именование файлов, управление каталогами, управление жесткими и символьными связями, а также другие свойства файловой системы, одинаковые для всех файловых систем.
Над страничным кэшем располагается уровень системы виртуальной памяти. В нем реализуется вся логика работы со страницами, например алгоритм замещения страниц. Поверх него находится программа отображения файлов на виртуальную память и высо­коуровневая программа управления страничными прерываниями. Эта программа решает, что нужно делать при возникновении страничного прерывания. Сначала она проверяет допустимость обращения к памяти и, если все в порядке, определя­ет местонахождение требуемой страницы и то, как она может быть получена.
Над диспет­чером располагается планировщик процессов, выбирающий процесс, который дол­жен быть запущен следующим. Если потоками управляет ядро, то управление потоками также помещается здесь, хотя в некоторых системах UNIX управление потоками вынесено в пространство пользователя. Над планировщиком располо­жена программа для обработки сигналов и отправки их в требуемом направлении, а также программа, занимающаяся созданием и завершением процессов.
Верхний уровень представляет собой интерфейс системы. Во-первых, он включает интерфейс системных вызовов. Все системные вызовы поступают сюда и направ­ляются одному из модулей низших уровней в зависимости от природы системно­го вызова. Во-вторых, интерфейс системы представляет собой вход для аппаратных и эмулированных прерываний, включая сигналы, страничные прерывания, разно­образные исключительные ситуации процессора и прерывания ввода-вывода.
7.2. Процессы в UNIX
7.2.1. Основные понятия
Единственными активными сущностями в системе UNIX являются процессы. Процессы UNIX очень похожи на классические последовательные процессы, кото­рые рассматривались в разделе 1. Каждый процесс запускает одну программу и изначально получает один поток управления. Другими словами, у процесса есть один счетчик команд, указывающий на следующую исполняемую команду процессора. Боль­шинство версий UNIX позволяют процессу после того, как он запущен, создавать дополнительные потоки. UNIX представляет собой многозадачную систему, так что несколько незави­симых процессов могут работать одновременно. У каждого пользователя может быть одновременно несколько активных процессов, так что в большой системе могут одновременно работать сотни и даже тысячи процессов. Действительно, на большинстве однопользовательских рабочих станций (даже без участия пользователя) работают десятки фоновых процессов, называемых демо­нами (daemon). Они запускаются автоматически при загрузке системы. Демоны позволяют планировать в системе UNIX активность на минуты, часы, дни и месяцы вперед, управлять входящей и исходящей элек­тронной почтой, очередями на принтер, проверять наличие свободных страниц памяти и т. д. Демоны реализуются в системе UNIX относительно просто, так как каждый из них представляет собой отдельный процесс, независи­мый ото всех остальных процессов.
В операционной системе UNIX посредством системного вызова fork создается точная копия исходного процесса (так называемого родительского процесса). Новый процесс называется дочерним процессом. У ро­дительского и у дочернего процессов есть свои собственные образы памяти. Если родительский процесс впоследствии изменяет какие-либо свои переменные, из­менения остаются невидимыми для дочернего процесса, и наоборот.
Открытые файлы совместно используются родительским и дочерним процес­сами. Это значит, что если какой-либо файл был открыт до выполнения системно­го вызова fork, он останется открытым в обоих процессах и в дальнейшем. Изме­нения, произведенные с этим файлом, будут видимы каждому процессу. Такое поведение является единственно разумным, так как эти изменения будут также видны любому другому процессу, который тоже откроет этот файл.
У родительского процесса может быть много дочерних процессов. Поскольку у дочерних процессов также могут быть до­черние процессы, исходный процесс может создать целое дерево детей, внуков, правнуков и т. д.
В системе UNIX процессы могут общаться друг с другом с помощью разно­видности обмена сообщениями. Можно создать канал между двумя процессами, в который один процесс может писать поток байтов, а другой процесс может его читать. Эти каналы иногда называют трубами. Синхронизация процессов дости­гается путем блокирования процесса при попытке прочитать данные из пустого канала. Когда данные появляются в канале, процесс разблокируется.
Процессы также могут общаться другим способом: при помощи программных прерываний. Один процесс может послать другому так называемый сигнал. Про­цессы могут сообщить системе, какие действия следует предпринимать, когда при­дет сигнал. У процесса есть выбор: проигнорировать сигнал, перехватить его или позволить сигналу убить процесс (действие по умолчанию для большинства сиг­налов). Если процесс выбрал перехват посылаемых ему сигналов, он должен ука­зать процедуры обработки сигналов. Когда сигнал прибывает, управление сразу же передается обработчику. Когда процедура обработки сигнала завершает свою работу, управление снова возвращается в то место процесса, в котором оно нахо­дилось, когда пришел сигнал. Обработка сигналов аналогична обработке аппарат­ных прерываний ввода-вывода. Процесс может посылать сигналы только членам его группы процессов, состоящей из его прямого родителя, всех прародителей, братьев и сестер, а также детей (внуков и правнуков). Процесс может также по­слать сигнал сразу всей своей группе за один системный вызов.
Сигналы в UNIX используются и для других целей. Например, если процесс выполняет вычисления с плавающей точкой и непреднамеренно делит число на 0, он получает сигнал исключения при выпол­нении операции с плавающей точкой. Сигналы UNIX описываются стандартом POSIX. В большинстве систем UNIX также имеются дополни­тельные сигналы, но программы, использующие их, могут оказаться непереноси­мыми на другие версии UNIX.
В первых версиях системы UNIX понятия потоков не существовало. После введения этого понятия и возможности управления потоками были стандартизированы соответствующие системные вызовы в виде части стандарта POSIX – P1003.1c.
В исходном варианте стандарта POSIX не указывается, должны ли потоки реализовываться в про­странстве ядра или в пространстве пользователя. Преимущество потоков в пользо­вательском пространстве состоит в том, что они легко реализуются без необходи­мости изменения ядра, а переключение потоков осуществляется очень эффективно. Недостаток потоков в пространстве пользователя заключается в том, что если один из потоков заблокируется (например, на операции ввода-вывода, семафоре или страничном прерывании), все потоки процесса блокируются. Ядро полагает, что существует только один поток, и не передает управление процессу потока, пока блокировка не снимется. Таким образом, системные вызовы, определенные в части стан­дарта P1003.1c были подобраны так, чтобы потоки могли быть реализова­ны любым способом. До тех пор, пока пользовательские программы четко придер­живаются семантики стандарта POSIX – P1003.1c, оба способа реализации работают корректно. Когда используется системная реализация потоков, они явля­ются настоящими системными вызовами. При использовании потоков на уровне пользователя они полностью реализуются в динамической библиотеке в простран­стве пользователя.
Синхронизация потоков может осуществляться при помощи мьютексов. Как правило, мьютекс охраняет какой-либо ресурс, например буфер, совместно исполь­зуемый двумя потоками. Чтобы гарантировать, что только один поток в каж­дый момент времени имеет доступ к общему ресурсу, предполагается, что потокиблокируют (захватывают) мьютекс перед обращением к ресурсу и разблокируют (отпускают) его, когда ресурс им более не нужен. До тех пор пока потоки со­блюдают данный протокол, состояния состязания можно избежать. Напомним, что название мьютекс (mutex) образовано от английских слов mutual exclusion – взаимное исключение, так как мьютексы по­добны двоичным семафорам, то есть семафорам, способным принимать только зна­чения 0 и 1. Таким образом мьютекс может находиться в одном из двух состояний: блокированный и разблокированный. Поток может заблокировать мьютекс с помощью системного вызова. Если мьютекс уже заблокирован, то поток, обратившийся к этому вызову, блокируется. Когда поток, захвативший мьютекс, выполнил свою работу в критической области, он должен освободить мьютекс, обратившись к соответствующему системному вызову.
Мьютексы предназначены для кратковременной блокировки, например для защиты совместно используемой переменной. Они не предназначаются для дол­говременной синхронизации, например для ожидания, когда освободится накопи­тель на магнитной ленте. Для долговременной синхронизации применяются так называемые переменные состояния. Переменные состояния используются следующим образом: один поток ждет, когда переменная примет определенное значение, а другой поток сигнализирует ему изменением этой переменной. Например, обнаружив, что нужный ему нако­питель на магнитной ленте занят, поток может обратиться к системному вызову, задав в качестве параметра адрес переменной, которую все потоки со­гласились связать с накопителем на магнитной ленте. Когда поток, использующий накопитель на магнитной ленте, наконец, освободит это устройство, он обращается к соответствующему системному вызову, чтобы изме­нить переменную состояния и тем самым сообщить ожидающим потокам, что накопитель свободен. Если ни один поток в этот момент не ждет, когда освобо­дится накопитель на магнитной ленте, этот сигнал просто теряется. Другими сло­вами, переменные состояния не считаются семафорами.

7.2.2. Реализация процессов в UNIX

У каждого процесса в системе UNIX есть пользовательская часть, в которой работает программа пользователя. Однако когда один из потоков обращается к системному вызову, происходит эмулированное прерывание с переключением в режим ядра. После этого поток начинает работу в контексте ядра, с отличной картой памяти и полным доступом к ресурсам машины. Это все еще тот же самый поток, но теперь обладающий большей мощью, а также со своим стеком ядра и счетчиком команд в режиме ядра. Это важно, так как системный вызов может блокироваться на полпути, например, ожидая завершения дисковой операции. При этом счетчик команд и регистры будут сохранены таким образом, чтобы позднее поток можно было восстановить в режиме ядра.
Ядро поддерживает две ключевые структуры данных, относящиеся к процес­сам: таблицу процессов и структуру пользователя. Таблица процессов является резидентной. В ней содержится информация, необходимая для всех процессов, даже для тех процессов, которых в данный момент нет в памяти. Структура пользо­вателя выгружается на диск, освобождая место в памяти, когда относящийся к ней процесс отсутствует в памяти, чтобы не тратить память на ненужную в данный момент информацию.
Информация в таблице процессов подразделяется на следующие категории:
1. Параметры планирования. Приоритеты процессов, процес-сорное время, потребленное за последний учитываемый период, количество времени, про­веденное процессом в режиме ожидания. Вся эта информация использует­ся для выбора процесса, которому будет передано управление следующим.
2. Образ памяти. Указатели на сегменты программы, данных и стека, или, если используется страничная организация памяти, указатели на соответствую­щие им таблицы страниц. Если программный сегмент используется совмест­но, то программный указатель указывает на общую таблицу программы. Когда процесса нет в памяти, то здесь также содержится информация о том, как найти части процесса на диске.
3. Сигналы. Маски, указывающие, какие сигналы игнорируются, какие пере­хватываются, какие временно заблокированы, а какие находятся в процессе доставки.
4. Прочая информация. Текущее состояние процесса, ожидаемые процессом события (если таковые есть), идентификатор текущего процесса, идентификатор родительского процесса, идентификаторы пользователя и группы, а также некоторая другая информация.
В структуре пользователя содержится информация, которая не требуется, ког­да процесса физически нет в памяти и он не выполняется. Например, хотя процес­су, выгруженному на диск, можно послать сигнал,выгруженный процесс не мо­жет прочитать файл. По этой причине информация о сигналах должна храниться в таблице процессов, постоянно находящейся в памяти, даже когда процесс не при­сутствует в памяти. С другой стороны, сведения об описателях файлов могут хра­ниться в структуре пользователя и загружаться в память вместе с процессом.
Данные, хранящиеся в структуре пользователя, включают в себя следующие пункты:
1. Машинные регистры. Когда происходит прерывание с пере-ключением в ре­жим ядра, машинные регистры (включая регистры с плавающей точкой) сохраняются здесь.
2. Состояние системного вызова. Информация о текущем системном вызове, включая параметры и результаты.
3. Таблица дескрипторов файлов. Когда происходит обращение к системному вызову, работающему с файлом, дескриптор файла используется в качестве индекса в данной таблице, что позволяет найти структуру данных (i-узел), соответствующую данному файлу.
4. Учетная информация. Указатель на таблицу, учитывающую процессорное время, использованное процессом в пользовательском и системном режи­мах. В некоторых системах здесь также ограничивается процессорное вре­мя, которое может использовать процесс, максимальный размер стека, ко­личество страниц памяти и так далее.
5. Стек ядра. Фиксированный стек для использования процессом в режиме ядра.
Рассмотрим подробне, как в системе UNIX создаются процессы. Когда выполняется системный вызов fork, вызываю­щий процесс обращается в ядро и ищет свободную ячейку в таблице процессов, в которую можно записать данные о дочернем процессе. Если свободная ячейка находится, системный вызов копирует туда информацию из ячейки родительского процесса. Затем он выделяет память для сегментов данных и для стека дочернего процесса, куда копируются соответствующие сегменты родительского процесса. Структура пользователя (которая часто хранится вместе с сегментом стека) копи­руется вместе со стеком. Программный сегмент может либо копироваться, либо использоваться совместно, если он доступен только для чтения. Начиная с этого момента дочерний процесс может быть запущен.
Для дочернего процесса создается новая ячейка в таблице процессов, которая заполняется по большей мере из соответствующей ячейки родительского процесса. Дочерний процесс получает идентификатор, затем настраивается его карта памяти. Кроме того, дочернему процессу пре­доставляется совместный доступ к файлам родительского процесса. Затем настра­иваются регистры дочернего процесса, после чего он готов к запуску.
Так как никакая область памяти не может ис­пользоваться совместно родительским и дочерним процессами, дочернему процессу выделяются новые таблицы страниц, но эти таблицы указывают на страницы родительского процесса, помеченные как доступ­ные только для чтения. Когда дочерний процесс пытается писать в такую страни­цу, происходит прерывание. При этом ядро выделяет дочернему процессу новую копию этой страницы, к которой этот процесс получает также и доступ записи. Таким образом, копируются только те страницы, в которые дочерний процесс пишет новые данные. Такой механизм называется копированием при записи. При этом сохраняется память, так как страницы с программой не копируются.
После того, как дочерний процесс начинает работу, его программа выполняет системный вызов ехес, задавая имя команды в качестве пара­метра. При этом ядро находит и проверяет исполняемый файл, копирует в ядро аргументы и строки окружения, а также освобождает старое адресное простран­ство и его таблицы страниц.
Затем следует создать и заполнить новое адресное пространство. Если си­стемой поддерживается отображение файлов на адресное пространство памяти, как, например, в System V, BSD и в большинстве других версий UNIX, то таблицы страниц настраиваются следующим образом: в них указывается, что страниц в па­мяти нет, кроме, возможно, одной страницы со стеком, а содержимое адресного пространства может подгружаться из исполняемого файла на диске. Когда новый процесс начинает работу, он немедленно вызывает страничное прерывание, в ре­зультате которого первая страница программы подгружается с диска. Таким обра­зом, ничего не нужно загружать заранее, что позволяет быстро запускать про­граммы, а в память загружать только те страницы, которые действительно нужны программам. Наконец, в стек копируются аргументы и строки окружения, сигна­лы сбрасываются, а все регистры устанавливаются на ноль. С этого момента новая команда начинает исполнение.
Реализация потоков зависит от того, поддерживаются они ядром или нет. Если потоки ядром не поддерживаются, как, например, в 4BSD, реализация потоков целиком осуществляется в библиотеке, загружающейся в пространстве пользователя.

7.2.3. Планирование в системе UNIX

Поскольку UNIX всегда была многозадачной системой, ее алгоритм планирования с самого начала развития системы разрабатывался так, чтобы обеспечить хорошую реакцию в ин­терактивных процессах. У этого алгоритма два уровня. Низкоуровневый алгоритм выбирает следующий процесс из набора процессов в памяти и готовых к работе. Высокоуровневый алгоритм перемещает процессы из памяти на диск и обратно, что предоставляет всем процессам возможность попасть в память и быть запущенными.
У каждой версии UNIX свой слегка отличающийся низкоуровневый алгоритм планирования, но у большинства этих алгоритмов есть много общих черт, кото­рые мы здесь и опишем. В низкоуровневом алгоритме используется несколько очередей. С каждой очередью связан диапазон непересекающихся значений прио­ритетов. Процессы, выполняющиеся в режиме пользователя (верхняя часть айс­берга), имеют положительные значения приоритетов. У процессов, выполняющих­ся в режиме ядра (обращающихся к системным вызовам), значения приоритетов отрицательные. Отрицательные значения приоритетов считаются наивысшими, а положительные – наоборот, минимальными. В очере­дях располагаются только процессы, находящиеся в памяти и готовые к работе.
Когда запускается низкоуровневый планировщик, он ищет очередь, начиная с самого высокого приоритета (то есть с наименьшего отрицательного значения), пока не находит очередь, в которой есть хотя бы один процесс. После этого в этой очереди выбирается и запускается первый процесс. Ему разрешается работать в те­чение некоего максимального кванта времени, как правило, 100 мс, или пока он не заблокируется. Если процесс использует весь свой квант времени, он помещается обратно, в конец очереди, а алгоритм планирования запускается снова. Таким об­разом, процессы, входящие в одну группу приоритетов, совместно используют цен­тральный процессор в порядке циклической очереди.
Раз в секунду приоритет каждого процесса пересчитывается по формуле, со­стоящей из суммы трех компонентов. Первой составляющей этой формулы является параметр использования центрального процессора, представляющий собой среднее значение тиков (прерываний) таймера в секунду, которые процесс работал в тече­ние последних нескольких секунд. При каждом тике таймера счет­чик использования центрального процессора в таблице процессов увеличивается на единицу. Этот счетчик в конце концов добавляется к приоритету процесса, увеличивая тем самым числовое значение его приоритета (что соответствует бо­лее низкому приоритету), в результате чего процесс попадает в менее приоритет­ную очередь. Однако в UNIX процесс не находится в конце очереди бесконечно долго, и величина параметра использования центрального процессора со временем уменьша­ется. В различных версиях UNIX это уменьшение выполняется по-разному. Один из способов состоит в том, что к этому параметру прибавляется полученное число тиков, после чего сумма делится на два. Такой алгоритм учитывает самое последнее значение числа тиков с весовым коэффициентом 1/2, предшествующее ему – с весовым коэффициентом 1/4 и т. д. Алгоритм взвешивания очень быстр, так как состоит из всего одной операции сложения и одного сдвига, но также применяются и другие схемы взвешивания.
Второй составляющей формулы является так называемый параметр nice. Его значение по умолчанию рав­но 0, но допустимый диапазон значений, как правило, составляет от -20 до +20. Процесс может установить значение nice в диапазоне от 0 до 20 с помощью систем­ного вызова. Только системный администратор может запросить обслуживание с более высоким приоритетом (то есть значения nice от -20 до -1).
Третьей составляющей формулы является параметр base (база). Когда процесс эмулирует прерывание для выполнения системного вызова в ядре, процесс, вероятно, должен быть блокирован, пока системный вызов не будет вы­полнен и не вернется в режим пользователя. Например, процесс может обратить­ся к системному вызову, ожидая, пока один из его дочерних процессов не закончит работу. Он может также ожидать ввода с терминала или завершения дис­ковой операции ввода-вывода и т. д. Когда процесс блокируется, он удаляется из структуры очереди, пока этот процесс снова не будет готов работать. Однако когда происходит событие, которого ждал процесс, он снова помещает­ся в очередь с отрицательным значением. Выбор очереди определяется событием, которого ждал процесс. Например дисковый ввод-вывод может быть событием с наивысшим приоритетом, так что процесс, только что прочитавший или запи­савший блок диска, вероятно, получит центральный процессор в течение 100 мс. Отрицательные значения приоритета для дискового ввода-вывода, терминаль­ного ввода-вывода и некоторых других операций жестко прошиты в операционной системе и могут быть изменены только путем перекомпиляции самой системы. Эти значения (отрицательные) и представлены параметром base. Их величина достаточно отличается от нуля, чтобы перезапущенный процесс навер­няка попадал в другую очередь.
В основе этой схемы лежит идея как можно более быстрого удаления процес­сов из ядра. Например, если процесс пытается читать дисковый файл, необходимость ждать секунду между обращениями к системным вызовам read замедлит его работу во много раз. Значительно лучше позволить ему немедленно продолжить работу сразу после выполнения запроса, так чтобы он мог быстро обратиться к следую­щему системному вызову. Если процесс был заблокирован ожиданием ввода с тер­минала, то, очевидно, это интерактивный процесс, и ему должен быть предостав­лен наивысший приоритет, как только он перейдет в состояние готовности, чтобы гарантировать хорошее качество обслуживания интерактивных процессов. Таким образом, процессы, ограниченные производительностью процессора (то есть на­ходящиеся в положительных очередях), в основном обслуживаются после того, как будут обслужены все процессы, ограниченные вводом-выводом (когда все эти про­цессы окажутся заблокированы в ожидании ввода-вывода).
Планирование в опера­ционной системе Linux представляет собой одну из немногих областей, в которых использует алгоритм, отличный от применяющегося в UNIX. Так как потоки в системе Linux реализованы в ядре, то планирование в ней основано на потоках, а не на процессах. В операционной системе Linux алгоритмом планирования раз­личаются три класса потоков:
1. Потоки реального времени, обслуживаемые по алгоритму FIFO.
2. Потоки реального времени, обслуживаемые в порядке цикли-ческой очереди.
3. Потоки разделения времени.
Потоки реального времени, обслуживаемые по алгоритму FIFO, имеют наивыс­шие приоритеты и не могут прерываться другими потоками, за исключением та­кого же потока реального времени FIFO, перешедшего в состояние готовности. Потоки реального времени, обслуживаемые в порядке циклической очереди, пред­ставляют собой то же самое, что и потоки реального времени FIFO, но с тем отли­чием, что они могут прерываться таймером. Находящиеся в состоянии готовности потоки реального времени, обслуживаемые в порядке циклической очереди, вы­полняются в течение определенного кванта времени, после чего поток помещает­ся в конец своей очереди. Ни один из этих классов на самом деле не является клас­сом реального времени. Здесь нельзя задать предельный срок выполнения задания и предоставить гарантий его выполнения. Эти классы просто имеют более высо­кий приоритет, чем у потоков стандартного класса разделения времени.
У каждого потока есть приоритет планирования. Значение по умолчанию рав­но 20, но оно может быть изменено при помощи специального системного вызова, вычитающего некоторое значение в диапа­зоне от -20 до +19 из 20. Поэтому возможные значения приоритетов попадают в промежуток от 1 до 40. Цель алгоритма планирования состоит в том, чтобы обеспечить грубое пропорциональ­ное соответствие качества обслуживания приоритету (то есть чем выше приори­тет, тем меньше должно быть время отклика и тем большая доля процессорного времени достанется процессу).
Помимо приоритета с каждым процессом связан квант времени, то есть коли­чество тиков таймера, в течение которых процесс может выполняться. По умолча­нию каждый тик равен 10 мс. Планировщик использует сочетание значений приоритета и кванта для плани-рования по определенному алгоритму.

7.3. Управление памятью в UNIX

7.3.1. Основные понятия

У каждого процесса в системе UNIX есть адресное пространство, состоящее из трех сегментов: текста (программы), данных и стека. Текстовый (программный) сегмент содержит машинные команды, образующие исполняемый код программы. Он создается компилятором и ассемблером при трансляции программы, написанной на языке высокого уровня, в машинный код. Как правило, тексто­вый сегмент разрешен только для чтения. Текстовый сегмент не изменяется ни в размерах, ни по своему содержанию.
Сегмент данных содержит переменные, строки, массивы и другие данные про­граммы. Он состоит из двух частей: инициализированных данных и неинициали­зированных данных. По историческим причинам вторая часть называется BSS (Bulk Storage System – запоминающее устройство большой емкости или массовое запоминающее устройств). Инициализированная часть сегмента данных содержит переменные и константы компилятора, значения которых должны быть заданы при запуске программы. Например, на языке С можно объявить символьную строку и в то же время за­дать ее значение, то есть проинициализировать ее. Когда программа запускается, она предполагает, что в этой строке уже содержится некий осмысленный текст. Чтобы реализовать это, компилятор назначает строке определенное место в адрес­ном пространстве и гарантирует, что в момент запуска программы по этому адресу будет располагаться соответствующая строка. С точки зрения операционной сис­темы, инициализированные данные не отличаются от текста программы – тот и другой сегменты содержат сформированные компилятором последовательности битов, загружаемые в память при запуске программы.
Неинициализированные данные необходимы лишь с точки зрения оптимиза­ции. Когда начальное значение глобальной переменной явно не указано, то, соглас­но семантике языка С, ее значение устанавливается равным 0. На практике боль­шинство глобальных переменных не инициализируются, и, таким образом, их начальное значение равно 0. Это можно реализовать следующим образом: создать целый сегмент исполняемого двоичного файла, точно равного по размеру числу байтов данных, и проинициализировать весь этот сегмент нулями. Однако с целью экономии места на диске этого не делается. Файл содержит только те переменные, начальные значения которых явно заданы. Вместо неинициализи­рованных переменных компилятор помещает в исполняемый файл просто одно слово, содержащее размер области неинициализированных данных в байтах. При запуске программы операционная система считывает это слово, выделяет нужное число байтов и обнуляет их.
В отличие от текстового сегмента, который не может изменяться, сегмент дан­ных может модифицироваться. Программы изменяют свои переменные постоян­но. Более того, многим программам требуется выделение дополнительной памяти динамически, во время выполнения. Чтобы реализовать это, операционная систе­ма UNIX разрешает сегменту данных расти при динамическом выделении памяти программам и уменьшаться при освобождении памяти программами. Програм­ма может установить размер своего сегмента данных с помощью системного вызо­ва brk. Таким образом, чтобы получить больше памяти, программа может увеличить размер своего сегмента данных. Этим системным вызовом пользуется библиотеч­ная процедура, используемая для выделения памяти.
Третий сегмент – это сегмент стека. На большинстве вычислительных машин он начи­нается около старших адресов виртуального адресного пространства и растет вниз к 0. Если указатель стека оказывается ниже нижней границы сегмента стека, как правило, происходит аппаратное прерывание, при котором операционная сис­тема понижает границу сегмента стека на одну страницу памяти. Программы не управляют явно размером сегмента стека. Когда программа запускается, ее стек не пуст. Напротив, он содержит все пере­менные окружения (оболочки), а также командную строку, введенную в оболочке при вызове этой программы. Таким образом, программа может узнать параметры, с которыми она была запущена.
Когда два пользователя запускают одну и ту же программу, например тексто­вый редактор, в памяти можно хранить две копии программы редактора. Однако такой подход является неэффективным. Вместо этого большинством систем UNIX поддерживаются текстовые сегменты совместного использования. Отображение выполняется аппаратным обеспечением виртуальной памяти.
Сегменты данных и стека никогда не бывают общими, кроме как после выпол­нения системного вызова fork, и то только те страницы, которые не модифициру­ются любым из процессов. Если размер любого из сегментов должен быть увели­чен, то отсутствие свободного места в соседних страницах памяти не является проблемой, так как соседние виртуальные страницы памяти не обязаны отобра­жаться на соседние физические страницы.
На некоторых вычислительных машинах аппаратное обеспечение поддерживает раздельные адресные пространства для команд и для данных. Если такая возможность есть, система UNIX может ею воспользоваться. Например, на компьютере с 32-разряд­ными адресами при возможности использования раздельных адресных пространств можно получить 4 Гбайт адресного пространства для команд и еще 4 Гбайт адрес­ного пространства для данных. Передача управления по адресу 0 будет восприни­маться как передача управления по адресу 0 в текстовом пространстве, тогда как при обращении к данным по адресу 0 будет использоваться адрес 0 в пространстве данных. Таким образом, это свойство удваивает доступное адресное пространство.
Многими версиями UNIX поддерживается отображение файлов на адресное пространство памяти. Это свойство позволяет отображать файл на часть адресно­го пространства процесса, так чтобы можно было читать из файла и писать в файл, как если бы это был массив, хранящийся в памяти. Отображение файла на адрес­ное пространство памяти делает произвольный доступ к нему существенно более легким, нежели при использовании системных вызовов, таких как read и write. Совместный доступ к библиотекам предоставляется именно при помощи этого механизма.
Дополнительное преимущество отображения файла на память заключается в том, что два или более процессов могут одновременно отобразить на свое адрес­ное пространство один и тот же файл. Запись в этот файл одним из процессов мгновенно становится видимой всем остальным. Таким образом, отображение на адресное пространство памяти временного файла (который будет удален после завершения работы процессов) представляет собой механизм реализации общей памяти для нескольких процессов, причем у такого механизма будет высокая про­пускная способность. В предельном случае два или более процессов могут отобра­зить на память файл, покрывающий все адресное пространство, получая, таким образом, форму совместного использования памяти – нечто среднее между процес­сами и потоками. В этом случае, как и у потоков, все адресное пространство ис­пользуется совместно, но каждый процесс может управлять собственными файла­ми и сигналами, что отличает этот вариант от потоков.

7.3.2. Реализация управления памятью в UNIX

До версии 3BSD большинство систем UNIX основывались на свопинге (подкач­ке), работавшем следующим образом. Когда загружалось больше процессов, чем могло поместиться в памяти, некоторые из них выгружались на диск. Выгружае­мый процесс всегда выгружался на диск целиком (исключение представляли толь­ко совместно используемые текстовые сегменты). Таким образом, процесс мог быть либо в памяти, либо на диске.
Перемещением данных между памятью и диском управлял верхний уровень двух­уровневого планировщика, называвшийся свопером (swapper). Выгрузка данных из памяти на диск инициировалась, когда у ядра кончалась свободная память из-за одного из следующих событий:
1. Системному вызову fork требовалась память для дочернего процесса.
2. Системный вызов brk собирался расширить сегмент данных.
3. Разросшемуся стеку требовалась дополнительная память.
Кроме того, когда наступало время запустить процесс, уже достаточно долго находящийся на диске, часто бывало необходимо удалить из памяти другой про­цесс, чтобы освободить место для запускаемого процесса. Выбирая про­цесс, который необходимо было удалить из памяти, свопер сначала рассматривал блокированные (например, ожи­данием ввода с терминала) процессы. Лучше удалить из памяти процесс, который не может работать, чем работоспособный процесс. Если такие процессы находи­лись, из них выбирался процесс с наивысшим значением суммы приоритета и вре­мени пребывания в памяти. Таким образом, подвергались выгрузки процессы, потребившие большое количество процессорного времени или находящиеся в памяти уже достаточно долгое время. Если блокированных процессов не было, тогда на основе тех же критериев выбирался готовый процесс.
Каждые несколько секунд свопер исследовал список выгруженных процессов, проверяя, не готов ли какой-либо из этих процессов к работе. Если процессы в со­стоянии готовности обнаруживались, из них выбирался процесс, дольше всех нахо­дящийся на диске. Затем свопер проверял, будет ли это легкий свопинг или тяже­лый. Легким свопингом считался тот, для которого не требовалось дополнительное высвобождение памяти. При этом нужно было всего лишь загрузить выгружен­ный на диск процесс. Тяжелым свопингом назывался свопинг, при котором для загрузки в память выгруженного на диск процесса из нее требовалось удалить один или несколько других процессов. Затем весь этот алгоритм повторялся до тех пор, пока не выполнялось одно из следующих двух условий: на диске не оставалось процессов, готовых к работе, или в памяти не оставалось места для новых процессов. Чтобы не терять боль­шую часть производительности системы на свопинг, ни один процесс не выгру­жался на диск, если он пробыл в памяти менее 2 с. Свободное место в памяти и на устройстве перекачки учитывалось при помо­щи связного списка свободных пространств. Когда требовалось свободное про­странство в памяти или на диске, из списка выбиралось первое подходящее сво­бодное пространство. После этого в список возвращался остаток от свободного пространства.
Начиная с версии 3BSD к системе была добавлена страничная подкачка, чтобы предоставить возможность работать с программами больших размеров. Практически во всех версиях системы UNIX теперь есть страничная подкачка по требованию, появившаяся впервые в версии 3BSD. Ниже описывается реализация этого механизма в версии 4BSD. Такая реализация во многом соответствует и версии System V, которая основана на 4BSD.
Идея, лежащая в основе страничной подкачки в системе 4BSD, состоит в том, что процессу для работы не нужно целиком находиться в памяти. Все, что в действи­тельности требуется, – это структура пользователя и таблицы страниц. Если они загружены, то процесс считается находящимся в памяти и может быть запущен планировщиком. Страницы с сегментами текста, данных и стека загружаются в па­мять динамически, по мере обращения к ним. Если пользовательской структуры и таблицы страниц нет в памяти, то процесс не может быть запущен, пока свопер не загрузит их.
Страничная подкачка реализуется частично ядром и частично новым про­цессом, называемым страничным демоном. Как и все демоны, страничный демон периодически запускается и смотрит, есть ли для него работа. Если он обнаруживает, что количество страниц в списке свободных стра­ниц слишком мало, страничный демон инициирует действия по освобождению дополнительных страниц.
Память в 4BSD делится на три части. Первые две части, ядро операционной системы и карта памяти, фиксированы в фи­зической памяти (то есть никогда не выгружаются). Остальная память машины делится на страничные блоки, каждый из которых может содержать либо страницу текста, данных или стека, либо находиться в списке свободных страниц.
Карта памяти содержит информацию о содержимом страничных блоков. Для каждого страничного блока в карте памяти есть запись фиксированной длины. При килобайтных страничных блоках и 16-байтовых записях карты памяти на нее расходуется менее 2 % от общего объема памяти. Первые два поля записи карты памяти используются только тогда, когда соответствующий страничный блок находится в списке свободных страниц. В этом случае они сшивают свобод­ные страницы в двусвязный список. Следующие три записи используются, когда страничный блок содержит информацию. У каждой страницы в памяти есть фик­сированное место хранения на диске, в которое она помещается, когда выгружает­ся из памяти. Еще три поля содержат ссылку на запись в таблице процессов, тип хранящегося в странице сегмента и смещение в сегменте процесса. Последнее поле содержит некоторые флаги, нужные для алгоритма страничной подкачки. При запуске процесс может вызвать страничное прерывание, если одной или нескольких его страниц не окажется в памяти. При страничном прерывании опе­рационная система берет первый страничный блок из списка свободных страниц, удаляет его из списка и считывает в него требуемую страницу. Если список сво­бодных страниц пуст, выполнение процесса приостанавливается до тех пор, пока страничный демон не освободит страничный блок.
Алгоритм замещения страниц выполняется страничным демоном. Раз в 250 мс он сравнивает количество свободных страничных блоков с систем­ным параметром lotsfree (равным, как правило, 1/4 объема памяти). Если число свободных страничных блоков меньше, чем значение этого параметра, страничный демон начинает переносить страницы из памяти на диск, пока количество свобод­ных страничных блоков не станет равно lotsfree. Если же количество свободных страничных блоков больше или равно lotsfree, тогда страничный демон ничего не предпринимает. Если в машине много памяти и мало активных процессов, страничный демон практически все время бездействует.
Страничный демон использует модифицированную версию алгоритма часов. Это глобальный алгоритм, то есть при удалении страницы он не учитывает, чья это страница. Таким образом, количество страниц, выделяемых каждому процес­су, меняется со временем. Основной алгоритм часов работает, сканируя в цикле страничные блоки (как если бы они лежали на окружности циферблата часов). На первом проходе, когда стрелка часов указывает на страничный блок, сбрасывается его бит использова­ния. На втором проходе у каждого страничного блока, к которому не было доступа с момента первого прохода, бит использования останется сброшенным, и этот стра­ничный блок будет помещен в список свободных страниц. Страничный блок в списке свободных страниц сохра­няет свое содержание, что позволяет восстановить страницу, если она потребует­ся прежде, чем будет перезаписана.
Изначально в версии UNIX 4BSD использовался основной алгоритм часов, но затем он был заменен более эффективным алгоритмом часов с двумя стрелками. В этом алгоритме страничный демон под­держивает два указателя на карту памяти. При работе он сначала очищает бит использования передней стрелкой, а затем проверяет этот бит задней стрелкой. После чего перемещает обе стрелки. Если две стрелки находятся близко друг от друга, то только у очень активно используемых страниц появляется шанс, что к ним будет обращение между проходами двух стрелок. Если же стрелки разнесены на 359 градусов (то есть задняя стрелка находится слегка впереди передней), то по сути снова получается исходный алгоритм часов. При каждом запуске страничного демона стрелки проходят не полный оборот, а столько, сколько необходимо, что­бы количество страниц в списке свободных страниц было не менее lotsfree.
Если операционная система обнаруживает, что частота подкачки страниц слишком высока, а количество свободных страниц все время ниже lotsfree, свопер начинает удалять из памяти один или несколько процессов, чтобы остановить состязание за свободные страничные блоки. Свопинг в системе 4BSD осуществляется по следующему алгоритму. Сначала свопер проверяет, есть ли процесс, который бездействовал в течение 20 и более секунд. Если такие процессы есть, из них выбирается бездей­ствовавший в течение максимального срока и выгружается на диск. Если таких процессов нет, изучаются четыре самых больших процесса, из которых выбирает­ся тот, который находился в памяти дольше всех, и выгружается на диск. При не­обходимости этот алгоритм повторяется до тех пор, пока не будет высвобождено достаточное количество памяти.
Каждые несколько секунд свопер проверяет, есть ли на диске готовые процес­сы, которые следует загрузить в память. Каждому процессу на диске присваива­ется значение, зависящее от времени его пребывания в выгруженном состоянии, размера, значения, использовавшегося при обращении к системному вызову nice (если такое обращение было), и от того, как долго этот процесс бездействовал, прежде чем был выгружен на диск. Эта функция обычно взвешивается так, чтобы загружать в память процесс, дольше всех находящийся в выгруженном состоянии, если толь­ко он не крайне большой. Теория утверждает, что загружать большие процессы дорого, поэтому их не следует перемещать с диска в память и обратно слишком часто. Загрузка про­цесса производится только при условии наличия достаточного количества свобод­ных страниц, чтобы, когда случится неизбежное страничное прерывание, для него нашлись свободные страничные блоки. Свопер загружает в память только струк­туру пользователя и таблицы страниц. Страницы с текстом, данными и стеком подгружаются при помощи обычной страничной подкачки.
У каждого сегмента каждого активного процесса есть место на диске, где он располагается, когда его страницы удаляются из памяти. Сегменты данных и сте­ка сохраняются на временном устройстве, но текст программы подгружается из самого исполняемого двоичного файла. Для текста программы временная копия не используется.
Страничная подкачка в версии System V во многом схожа с применяемой в системе 4BSD, но тем не менее между этими версиями операционной системы есть интересные различия. Во-первых, в System V вместо алгоритма часов с двумя стрелками использует­ся оригинальный алгоритм часов с одной стрелкой. Более того, вместо того чтобы помещать страницу в список свободных страниц на втором проходе, страница по­мещается туда только в случае, если она не использовалась в течение нескольких последовательных проходов. Хотя при таком решении страницы не освобождаются так быстро, как это делается алгоритмом в 4BSD, оно значительно увели­чивает вероятность того, что освобожденная страница не потребуется тут же снова. Во-вторых, вместо единственной переменной lotsfree в System V используются две переменные: min и max. Когда количество свободных страниц опускается ниже min, страничный демон начинает освобождать страницы. Демон продолжает рабо­тать до тех пор, пока число свободных страниц не сравняется со значением max. Такой подход позволяет избежать неустойчивости, возможной в системе 4DSD, например, в ситуации, когда количество свободных страниц на единицу мень­ше, чем lotsfree. При этом страничный демон освобождает одну страницу, чтобы привести количество свободных страниц в соответствие со значением lotsfree. Затем происходит еще одно страничное прерывание, и количество свободных страниц опять становится на единицу меньше lotsfree, в результате страничный демон сно­ва начинает работать. Если установить значение max существенно большим, чем min, страничный демон, завершив освобождение страниц, создает достаточный за­пас для своего бездействмя в течение относительно продолжительного времени.


7.4. Ввод-вывод в системе UNIX

7.4.1. Основные понятия

Подход, применяемый в операционной системе UNIX для доступа программ к устройствам ввода-вывода (таким как диски, принтеры, сетевые устройства и т.п.), заключается в интегрировании всех устройств в файловую систему в виде так называемых специальных файлов. Каждому устройству ввода-вывода назначается имя пути, обычно в ката­логе /dev. Например, диск может иметь путь /dev/hd1, у принтера может быть путь /dev/lр, а у сети – /dev/net. Доступ к этим специальным файлам осуществляется так же, как и к обычным файлам. Для этого не требуется никаких специальных команд или системных вызо­вов, а используются обычные системные вызовы read и write. Программы мо­гут открывать, читать специальные файлы, а также писать в них тем же способом, что и в обычные файлы. Таким образом, для выполнения ввода-вывода не требуется специального механизма.
Специальные файлы подразделяются на две категории: блочные и символьные. Блочный специальный файл – это специальный файл, состоящий из последова­тельности нумерованных блоков. Основное свойство блочного специального фай­ла заключается в том, что к каждому его блоку можно адресоваться и получить доступ отдельно. Другими словами, программа может открыть блочный специаль­ный файл и прочитать, скажем, 124-й блок, не читая сначала блоки с 0 по 123. Блоч­ные специальные файлы обычно используются для дисков. Символьные специальные файлы, как правило, используются для устройств ввода или вывода символьного потока. Символьные специальные файлы исполь­зуются такими устройствами, как клавиатуры, принтеры, сети, мыши, плоттеры и т. д.
С каждым специальным файлом связан драйвер устройства, осуществляющий управление соответствующим устройством. У каждого драйвера есть так называе­мый номер старшего устройства, служащий для его идентификации. Если драй­вер одновременно поддерживает несколько устройств, например два диска одного типа, то каждому диску присваивается номер младшего устройства, идентифици­рующий это устройство. Вместе номера старшего устройства и младшего устройства однозначно обозначают каждое устройство ввода-вывода.
Другим примером ввода-вывода является работа с сетью, впервые появившаяся в Berkeley UNIX. Ключевым понятием в схеме Berkeley UNIX является сокет. Сокеты образуют пользовательский интерфейс с сетью. Сокеты могут динамически создаваться и разрушаться. При создании сокета вызывающему процессу возвращается дескриптор файла, требующийся для уста­новки соединения, чтения и записи данных, а также разрыва соединения.
Каждый сокет поддерживает определенный тип работы в сети, указываемый при создании сокета. Наиболее распространенными типами сокетов являются: 1) надежный байтовый поток (ориентиро-ванный на соединение), 2) надежный поток пакетов (ориенти-рованный на соединение), 3) ненадежная передача пакетов.
Первый тип сокетов позволяет двум процессам на различных машинах устано­вить между собой эквивалент «трубы» (канала между процессами на одной машине). Байты подаются в канал с одного конца и в том же порядке выходят с другого. Такая система гарантирует, что все посланные байты прибудут на другой конец канала и прибудут именно в том порядке, в котором были отправлены.
Второй тип сокетов отличается от первого тем, что он сохраняет границы меж­ду пакетами. Если отправитель пять раз отдельно обращается к системному вызо­ву write, каждый раз отправляя по 512 байт, а получатель запрашивает 2560 байт по сокету типа 1, он получит все 2560 байт сразу. При использовании сокета типа 2 ему будут выданы только первые 512 байт. Чтобы получить остальные байты, получателю придется выполнить системный вызов read еще четыре раза.
Третий тип сокета особенно полезен для приложений реального времени и ситуаций, в которых пользователь хочет реализовать специальную схему обработки ошибок. Сеть может терять пакеты или доставлять их в неверном порядке. В отличие от сокетов первых двух типов, сокет типа 3 не предоставляет никаких гарантий доставки. Преиму­щество этого режима заключается в более высокой производительности, которая в некоторых ситуациях оказывается важнее надежности (например, для доставки мультимедиа, при которой скорость ценится существенно выше, нежели сохран­ность данных).
При создании сокета один из параметров указывает протокол, используемый для него. Для надежных байтовых потоков, как правило, используется протокол TCP (Transmission Control Protocol – протокол управления передачей). Для ненадежной передачи пакетов обычно применяется протокол UDP (User Data Protocol – пользовательский протокол данных). Все эти протоколы составляют основу Интернета. Для надежного потока пакетов специального протокола нет.
Прежде чем сокет может быть использован для работы в сети, с ним должен быть связан адрес. Этот адрес может принадлежать к одному из нескольких про­странств адресов. Наиболее распространенным пространством является простран­ство адресов Интернета, использующее 32-разрядные числа для идентификации конечных адресатов в протоколе IPv4 и 128-разрядные числа в протоколе IPv6.
Как только сокеты созданы на машине-источнике и машине-приемни­ке, между ними может быть установлено соединение (для ориентированной на соединение связи). Одна сторона обращается к системному вызову listen, указы­вая в качестве параметра локальный сокет. При этом системный вызов создает буфер и блокируется до тех пор, пока не прибудут данные. Другая сторона обра­щается к системному вызову connect, задавая в параметрах дескриптор файла для локального сокета и адрес удаленного сокета. Если удаленная машина прини­мает вызов, тогда система устанавливает соединение между двумя сокетами.
Функции установленного соединения аналогичны функциям канала. Процесс может читать из канала и писать в него, используя дескриптор файла для локаль­ного сокета. Когда соединение далее не требуется, оно может быть закрыто обычным способом, при помощи системного вызова close.

7.4.2. Реализация ввода-вывода в системе UNIX

Ввод-вывод в операционной системе UNIX реализуется набором драйверов уст­ройств, по одному для каждого типа устройств. Функция драйвера заключается в изолировании остальной части системы от индивидуальных отличительных осо­бенностей аппаратного обеспечения. При помощи стандартных интерфейсов меж­ду драйверами и остальной операционной системой большая часть системы вво­да-вывода может быть помещена в машинно-независимую часть ядра.
Когда пользователь получает доступ к специальному файлу, файловая система определяет номера старшего и младшего устройств, а также выясняет, является ли файл блочным специальным файлом или символьным специальным файлом. Но­мер старшего устройства используется в качестве индекса для одного из двух внут­ренних массивов структур – bdevsw для блочных специальных файлов или cdevsw для символьных специальных файлов. Найденная таким образом структура содер­жит указатели на процедуры открытия устройства, чтения из устройства, записи на устройство и т. д. Номер младшего устройства передается в виде параметра. Добавление нового типа устройства к системе UNIX означает добавление нового элемента к одной из этих таблиц, а также предоставление соответствующих про­цедур выполнения различных операций с устройством.
Каждый драйвер разделен на несколько частей. Верхняя часть драйвера работа­ет в режиме вызывающего процесса и служит интерфейсом с остальной системой UNIX. Нижняя часть работает в контексте ядра и взаимодействует с устройством. Драйверам разрешается обращаться к процедурам ядра для выделения памяти, управления таймером, управления DMA и т. д.
Система ввода-вывода разделена на два основных компонента: обработку блоч­ных специальных файлов и обработку символьных специальных файлов. Цель той части системы, которая занимается операциями ввода-вывода с блоч­ными специальными файлами (например, дисковым вводом-выводом), заключа­ется в минимизации количества операций переноса данных. Для достижения дан­ной цели в системах UNIX между дисковыми драйверами и файловой системой помещается буферный кэш. Буферный кэш представляет собой таб­лицу в ядре, в которой хранятся тысячи недавно использованных блоков. Когда файловой системе требуется блок диска (например, блок i-узла, каталога или дан­ных), сначала проверяется буферный кэш. Если нужный блок есть в кэше, он по­лучается оттуда, при этом обращения к диску удается избежать. Буферный кэш значительно улучшает производительность системы. Если же блока нет в буферном кэше, он считывается с диска в кэш, а оттуда копируется туда, куда нужно. Поскольку в буферном кэше есть место только для фиксированного количества блоков, требуется некий алгоритм управления кэшем. Обычно блоки в кэше организуются в связный список. При каждом обращении к блоку он перемещается в начало списка. Если в кэше не хватает места для нового блока, то из него удаляется самый старый блок, находящийся в конце списка. Буферный кэш поддерживает не только операцию чтения с диска, но также и запись на диск. Когда программа пишет блок, этот блок не попадает напрямую на диск, а отправляется в кэш. Только когда кэш наполняется, модифицированные блоки кэша сохраняются на диске. Чтобы модифицированные блоки не хранились в кэше слишком дол-го, их принудительная выгрузка на диск про­изводится каждые 30 с.
В течение десятилетий драйверы устройств системы UNIX статически компоно­вались вместе с ядром, так что все они постоянно находились в памяти при каждой загрузке системы. Такая схема хорошо работала в условиях мало меняющихся кон­фигураций вычислительных машин. Все изменилось с появлением системы Linux, ориентированной в первую очередь на поддержку персональных компьютеров.Количество всевозможных устройств ввода-вывода на персональных компьютерах значительно больше, чем у классических вычислительных машин. Кроме того, хотя у пользователей системы Linux есть возможность иметь полный набор исходных текстов операционной систе­мы, подавляющее большинство пользователей будет испытывать существенные трудности с добавлением нового драйвера, обновлением файлов cdevsw или bdevsw, компоновкой ядра и установкой его как загружаемой системы. В операционной системе Linux подобные проблемы были решены при помощи концепции подгружаемых модулей. Это куски кода, которые могут быть загру­жены в ядро во время работы операционной системы. Как правило, это драйверы символьных или блочных устройств, но подгружаемым модулем также могут быть целая файловая система, сетевые протоколы, программы для отслеживания про­изводительности системы и т. д.
При загрузке модуля должно выполняться несколько определенных действий. Во-первых, модуль должен быть «на лету» перенастроен на новые адреса. Во-вторых, система должна проверить, доступны ли ресурсы, необходимые драйверу (напри­мер, определенные уровни запроса прерывания), и если они доступны, то поме­тить их как используемые. В-третьих, должны быть настроены все необходимые векторы прерываний. В-четвертых, для поддержки нового типа старшего устрой­ства следует обновить таблицу переключения драйверов. Наконец, драйверу по­зволяется выполнить любую специфическую для данного устройства процедуру инициализации. Когда все эти этапы выполнены, драйвер является полностью ус­тановленным, как и драйвер, установленный статически. Некоторые современные системы UNIX также поддерживают подгружаемые модули.

7.4.3. Потоки данных в UNIX

Так как символьные специальные файлы имеют дело с символьными потоками, а не перемещают блоки данных между памятью и диском, они не пользуются бу­ферным кэшем. Вместо этого в первых версиях системы UNIX каждый драйвер символьного устройства выполнял всю работу, требуемую для данного устройства. Однако с течением времени стало ясно, что многие драйверы, например програм­мы буферизации, управления потоком и сетевые протоколы, дублировали проце­дуры друг друга. Поэтому для структурирования драйверов символьных устройств и придания им модульности было разработано два решения.
Первое решение, реализованное в системе BSD, основано на структурах данных, при­сутствующих в классических системах UNIX и называемых С-списками. Каждый С-список представляет собой блок размером до 64 символов плюс счетчик и указатель на следующий блок. Символы, поступающие с терминала или любого другого символьного устройства, буферизируются в цепочках таких блоков. Когда пользовательский процесс считывает данные из /dev/tty (то есть из стандартного входного потока), символы не передаются процессу напрямую из С-списков. Вместо этого они пропускаются через процедуру, расположенную в ядре и называемую дисциплиной линии связи. Дисциплина линии связи ра­ботает как фильтр, принимая необработанный поток символов от драйвера терми­нала, обрабатывая его и формируя то, что называется обработанным символьным потоком. В обработанном потоке выполняются операции локального строково­го редактирования (например, удаляются отмененные пользователем символы и строки), а также выполняются другие специальные операции обработки. Обработанный поток передается процессу. Однако если процесс желает воспринимать каждый символ, введенный пользователем, он может принимать необработанный поток, минуя дисциплину линии связи. Вывод работает аналогично, заменяя табуляторы пробелами, добавляя символы-заполнители и т. д. Как и входной поток, выходной символьный поток может быть пропущен через дисциплину линии связи (обработанный режим) или мино­вать ее (необработанный режим). Необработанный режим особенно полезен при отправке двоичных данных на другие машины по линии последовательной передачи или для графических интерфейсов пользователя. Здесь не требуется ни­какого преобразования.
Второе решение реализовано в системе System V под названием потоков данных. Потоки данных основаны на возможности динамически соединять процесс пользователя с драйвером, а также динамически, во время исполнения, вставлять модули обра­ботки в поток данных. В некотором смысле поток представляет собой работающий в ядре аналог каналов в пространстве пользователя.
У потока данных всегда есть голова потока у вершины и соединение с драйве­ром у основания. В поток может быть вставлено столько модулей, сколько необ­ходимо. Обработка может происходить в обоих направлениях, так что каждому модулю может понадобиться одна секция для чтения (из драйвера) и одна секция для записи (в драйвер). Когда процесс пользователя пишет данные в поток, про­грамма в голове потока интерпретирует системный вызов и запаковывает данные в буферы потока, передаваемые от модуля к модулю вниз, при этом каждый мо­дуль выполняет соответствующие преобразования. У каждого модуля есть очередь чтения и очередь записи, так что буферы обрабатываются в правильном порядке. У модулей есть строго определенные интерфейсы, определяемые инфраструкту­рой потока, что позволяет объединять вместе несвязанные модули.
Важное свойство потоков данных – мультиплексирование. Мультиплексный модуль может взять один по­ток и расщепить его на несколько потоков или, наоборот, объединить несколько потоков в единый поток.


7.5. Файловые системы UNIX

7.5.1. Основные понятия

Файл в системе UNIX – это последовательность байтов произвольной длины (от 0 до некоторого максимума), содержащая произвольную информацию. Не делается принципиального различия между текстовыми (ASCII) файлами, двоичными фай­лами и любыми другими файлами. Значение битов в файле целиком определяется владельцем файла. Системе это безразлично. Изначально размер имен файлов был ограничен 14 символами, но в системе Berkeley UNIX этот предел был расширен до 255 символов, что впоследствии было принято в System V, а также в большин­стве других версий. В именах файлов разрешается использовать все ASCII-симво­лы, кроме символа NUL.
По соглашению многие программы ожидают, что имена файлов должны состоять из основного имени и расширения, отделяемого от основного имени файла точкой (которая в системе UNIX также считается символом). Эти со­глашения никак не регулируются операционной системой, но некоторые компи­ляторы и другие программы ожидают файлов именно с такими расширениями. Расширения могут иметь произвольную длину, кроме того, файлы могут иметь по нескольку расширений.
Для удобства использования файлы могут группироваться в каталоги. Катало­ги хранятся на диске в виде файлов, и до определенного предела с ними можно работать как с файлами. Каталоги могут содержать подкаталоги, что приводит к иерархической файловой системе. Корневой каталог называется / и, как правило, содержит несколько подкаталогов. Символ / также используется для разделения имен каталогов.
Существует два способа задания имени файла в системе UNIX, как в оболочке, так и при открытии файла из программы. Первый способ заключается в использо­вании абсолютного пути, указывающего, как найти файл от корневого каталога. Пример абсолютного пути: /man/labs/operat/numb4. Он сообщает системе, что в корневом каталоге следует найти каталог man, затем в нем найти каталог labs, ко­торый содержит каталог operat, а в нем расположен файл numb4.
Абсолютные имена путей часто бывают длинными и неудобными. По этой при­чине операционная система UNIX позволяет пользователям и процессам обозначить каталог, в котором они работают в данный момент, как рабочий каталог (также называемый текущим каталогом). Имена путей также могут указываться относи­тельно рабочего каталога. Путь файла, заданный относительно рабочего каталога, называется относительным путем.
Если пользователю необходимо обратиться к файлам, принадлежа­щим другим пользователям, или к своим файлам, расположенным в другом месте дерева файлов, а абсолют­ное имя пути представляется доста­точно длинным, то в системе UNIX эта проблема решается при помощи так называемых связей, представляющих собой записи каталога, которые указывают на другие файлы.
Кроме обычных файлов, системой UNIX также поддерживаются символьные специальные файлы и блочные специальные файлы. Символьные специальные файлы используются для моделирования последовательных устройств ввода-вы­вода, таких как клавиатуры и принтеры. Если процесс откроет файл /dev/tty и про­читает из него, он получит символы, введенные с клавиатуры. Если открыть файл /dev/lp и записать в него данные, то эти данные будут распечатаны на принтере.
Блочные специальные файлы, часто с такими именами, как /dev/hd1, могут исполь­зоваться для чтения и записи необработанных дисковых разделов, минуя файло­вую систему. Таким образом, поиск байта номер k, за которым последует чтение, приведет к чтению k-то байта на соответствующем дисковом разделе, игнорируя i-узел и файловую структуру. Необработанные блочные устройства используют­ся для страничной подкачки и свопинга программами установки файловой систе­мы (например, mkfs) и программами, исправляющими ломаные файловые систе­мы (например, fsck).
При наличии у вычислительной машины нескольких дисков возникает вопрос управления ими. Одно из решений заключается в том, чтобы установить самостоятельную файло­вую систему на каждый отдельный диск и управлять ими как отдельными файло­выми системами. При таком решении пользователь должен помимо каталогов указывать также и устройство, если оно отличается от используемого по умолчанию. Такой подход применяется в операционных системах MS-DOS, Windows 98 и VMS. Решение, применяемое в операционной системе UNIX, заключается в том, чтобы позволить монтировать один диск в дерево файлов другого диска. Например, можно смонтировать дискету в каталог жесткого диска. При этом пользователь будет видеть еди­ное дерево файлов и уже не должен думать о том, какой файл на каком устройстве хранится.
Другим интересным свойством файловой системы UNIX является бло­кировка. В некоторых приложениях два и более процессов могут одновременно использовать один и тот же файл, что может привести к конфликту. Одно из ре­шений данной проблемы заключается в том, чтобы создать в приложении крити­ческие области. Однако если эти процессы принадлежат независимым пользова­телям, такой способ координации действий, как правило, очень неудобен. Рассмотрим, например, базу данных, состоящую из многих файлов в одном или нескольких каталогах, доступ к которым могут получить никак не связанные меж­ду собой пользователи. С каждым каталогом или файлом можно связать семафор и достичь взаимного исключения, заставляя процессы выполнять операцию down на соответствующем семафоре, прежде чем читать или писать определенные дан­ные. Недостаток этого решения заключается в том, что недоступным становится весь каталог или файл, даже если процессам нужна всего одна запись. По этой причине стандартом POSIX предоставляется гибкий и детальный ме­ханизм, позволяющий процессам за одну неделимую операцию блокировать даже единственный байт файла или целый файл по желанию. Механизм блокировки требует от вызывающего его процесса указать блокируемый файл, начальный байт и количество байтов. Если операция завершается успешно, система создает запись в таблице, в которой указывается, что определенные байты файла заблокированы.
Стандартом определены два типа блокировки: блокировка с монополизацией и блокировка без монополизации. Если часть файла уже содержит блокировку без монополизации, то повторная установка блокировки без монополизации на это место файла разрешается, но попытка установки блокировку с монополизацией будет отвергнута. Если же какая-либо область файла содержит блокировку с моно­полизацией, то любые попытки заблокировать любую часть этой области файла бу­дут отвергаться, пока не будет снята монопольная блокировка. Для успешной уста­новки блокировки необходимо, чтобы каждый байт в данной области был доступен. При установке блокировки процесс должен указать, хочет ли он сразу получить управление или будет ждать, пока не будет установлена блокировка. Если процесс выбрал вызов с ожиданием, то он блокируется до тех пор, пока с запрашиваемой области файла не будет снята блокировка, установленная другим процессом, пос­ле чего процесс активизируется, и ему сообщается, что блокировка установлена. Если процесс решил воспользоваться системным вызовом без ожидания, он не­медленно получает ответ об успехе или неудаче операции.

7.5.2. Реализация классической файловой системы UNIX

Все системы UNIX могут под­держивать несколько дисковых разделов, каждый со своей файловой системой.
В классической системе UNIX раздел диска содержит файловую систему со следующей структурой. Блок 0 не используется системой и час­то содержит программу загрузки компьютера. Блок 1 представляет собой супер­блок. В нем хранится критическая информация о размещении файловой системы, включая количество i-узлов, количество дисковых блоков, а также начало списка свободных блоков диска (обычно несколько сот записей). При уничтожении супер­блока файловая система окажется нечитаемой. Следом за суперблоком располагаются i-узлы (i-nodes, сокращение от index-nodes – индекс-узлы). Они нумеруются от 1 до некоторого максимального числа. Каждый i-узел имеет 64 байт в длину и описывает ровно один файл, i-узел содер­жит учетную информацию (включая всю информацию, возвращаемую системным вызовом stat, который ее просто берет в i-узле), а также достаточное количество информации, чтобы найти все блоки файла на диске. Следом за i-узлами располагаются блоки с данными. Здесь хранятся все фай­лы и каталоги. Если файл или каталог состоит более чем из одного блока, блоки файла не обязаны располагаться на диске подряд. В действительности блоки боль­шого файла, как правило, оказываются разбросанными по всему диску.
Каталог в классической файловой системе представляет собой несортированный набор 16-байтовых записей. Каждая запись состоит из 14-байтного имени файла и номера i-узла. Чтобы открыть файл в рабочем каталоге, система просто считывает каталог, сравнивая имя искомого файла с каж­дой записью, пока не найдет нужную запись или пока не закончится каталог.
Если искомый файл присутствует в каталоге, система извлекает его i-узел и использует его в качестве индекса в таблице i-узлов (на диске), чтобы найти соответствующий i-узел и считать его в память. Этот i-узел помещается в таблицу i-узлов – структуру данных в ядре, содержащую все i-узлы открытых в данный мо­мент файлов и каталогов. Формат i-узлов варьируется от одной версии UNIX к другой. Как минимум i-узел должен содержать все поля, возвращаемые системным вызовом stat.
Поиск файла по абсолютному пути, например /man/labs/operat немного сложнее. Сначала система находит корневой каталог, как правило, использующий i-узел с но­мером 2 (i-узел 1 обычно резервируется для хранения дефектных блоков). Затем он ищет в корневом каталоге строку «man», чтобы получить номер i-узла каталога /man. Затем считывается этот i-узел, и из него извлекаются номера блоков, в которых располагается каталог /man. После этого считывается каталог /man, в котором ищет­ся строка «labs». Когда нужная запись найдена, из нее извлекается номер i-узла для каталога /man/labs и т. д. Таким образом, использование относительного имени фай­ла не только удобнее для пользователя, но также представляет существенно мень­шее количество работы для файловой системы.
Рассмотрим далее, как система считывает файл. По дескриптору файла файловая система должна найти i-узел соответствую­щего файла. С каждым дескриптором файла должен быть связан указатель в файле, определяющий байт в файле, кото­рый будет считан или записан при следующем обращении к файлу. Указатель помещается в таблицу деск­рипторов файла. При этом каждый процесс, открывающий файл, получает соб­ственную позицию в файле. Для передачи указателя от одного процесса другому вводится в обращение новая таблица – таблица открытых файлов, которая располагается между таблицей дескрипторов файлов и таблицей i-узлов. Указатели хранятся в файле, а бит чтения/записи в этой таблице. Задача таблицы открытых файлов заключа­ется в том, чтобы позволить родительскому и дочернему процессам совместно использовать один указатель в файле, но для посторонних процессов выделять отдельные указатели.
Каждый i-узел содержит дисковые адреса первых 10 блоков файла. Если позиция в файле попадает в его первые 10 блоков, то считывается нужный блок файла, а данные копируются пользователю. Для поддержки файлов, длина которых превышает 10 блоков, в i-узле содержится дисковый адрес одинарного косвенного блока. Этот блок содержит дисковые адреса дополни­тельных блоков файла. Например, если размер блока составляет 1 Кбайт, а диско­вый адрес занимает 4 байта, то одинарный косвенный блок может хранить до 256 дис­ковых адресов. Такая схема позволяет поддержать файлы размером до 266 Кбайт. Для файлов, размер которых превосходит 266 Кбайт, используется двойной косвенный блок. Он содержит адреса 256 одинарных косвенных блоков, каждый из которых содержит адреса 256 блоков данных. Такая схема позволяет поддер­жать файлы размером до 10 + 2 в степени 16 блоков (67 119 104 байт). Если и этого оказыва­ется недостаточно, в i-узле есть место для тройного косвенного блока. Его указа­тели показывают на 256 двойных косвенных блоков.
7.5.3. Реализация файловой системы Berkeley Fast
Приведенное выше описание объясняет принципы работы классической файло­вой системы UNIX. Теперь познакомимся с усовершенствованиями этой системы, реализованными в версии Berkeley. Во-первых, были реорганизованы каталоги. Длина имен файлов была увеличена до 255 символов. Для обеспечения совместимости двух систем в системе Berkeley были разработа­ны специальные системные вызовы, чтобы программы мог­ли читать каталоги, не зная их внутренней структуры. Позднее длинные имена файлов и эти системные вызовы были добавлены ко всем другим версиям UNIX и к стандарту POSIX.
Каждый каталог BSD, поддерживающей имена файлов длиной до 255 сим­волов, состоит из некоторого целого коли­чества дисковых блоков, так что каталоги могут записываться на диск как единое целое. Внутри каталога записи файлов и каталогов никак не отсортированы, при этом каждая запись сразу следует за предыдущей записью. В конце каждого блока может оказаться несколько неиспользованных байтов, так как записи могут быть различного размера. Каждая каталоговая запись состоит из четырех полей фиксиро­ванной длины и одного поля переменной длины. Первое поле представляет собой номер i-узла. Следом за номером i-узла идет поле, сообщающее размер всей каталоговой записи в байтах, возможно, вместе с дополнительными байтами-заполнителями в конце записи. Это поле необходимо, чтобы найти следующую запись. Затем располагается поле типа файла, определяющее, является ли этот файл каталогом и т. д. Последнее поле содержит длину имени файла в байтах. Наконец, идет само имя файла, заканчивающееся нулевым байтом и дополненное до 32-битовой границы. За ним могут следовать дополнительные байты-заполнители.
Поскольку поиск в каталогах производится линейно, он может занять много времени, пока не будет найдена запись у конца большого каталога. Для увеличе­ния производительности в BSD было добавлено кэширование имен. Прежде чем искать имя в каталоге, система проверяет кэш. Если имя файла есть в кэше, то в каталоге его уже можно не искать.
Вторым существенным изменением, введенным в Berkeley, было разбиение диска на группы цилиндров, у каждой из которых был собственный суперблок, i-узлы и блоки данных. Идея такой организации диска заключается в том, чтобы хранить i-узел и блоки данных файла ближе друг к другу. Тогда при обращении к файлам снижается время, затрачиваемое жестким диском на перемещение блоков головок. По мере возможности блоки для файла выделяются в группе цилиндров, в которой содержится i-узел.
Третье изменение заключалось в использовании блоков не одного, а двух раз­меров. Для хранения больших файлов значительно эффективнее использовать небольшое количество крупных блоков, чем много маленьких блоков. С другой стороны, размер многих файлов в системе UNIX невелик, поэтому при использо­вании только блоков большого размера расходовалось бы слишком много диско-вого пространства. Наличие блоков двух размеров обеспечивает эффективное чте­ние/запись для больших файлов и эффективное использование дискового про­странства для небольших файлов. Платой за эффективность является значитель­ная дополнительная сложность программы.
7.5.4. Реализация файловой системы Linux
Изначально в операционной системе Linux использовалась файловая система опе­рационной системы MINIX. Однако в системе MINIX длина имен файлов ограни­чивалась 14 символами (для совместимости с UNIX Version 7), а максимальный размер файла был равен 64 Мбайт. Поэтому у разработчиков операционной систе­мы Linux практически сразу появился интерес к усовершенствованию файловой системы. Первым шагом вперед стала файловая система Ext, в которой длина имен файлов была увеличена до 255 символов, а размер файлов – до 2 Гбайт. Однако эта система была медленнее файловой системы MINIX, поэтому была разработана файловая система Ext2 с длинными именами файлов, длинными файлами и высокой производительностью. Эта файловая система и стала основной файловой системой Linux. Однако опера­ционная система Linux также поддерживает еще более десятка файловых систем, используя для этого файловую систему NFS (описанную в следующем разделе). При компоновке операционной системы Linux предлагается сделать выбор файло­вой системы, которая будет встроена в ядро. Другие файловые системы при необхо­димости могут динамически подгружаться во время исполнения в виде модулей.
Файловая система Ext2 очень похожа на файловую систему Berkeley Fast с небольшими изменениями. Вместо того, чтобы использовать группы цилин­дров, что практически ничего не значит при современных дисках с виртуальной геометрией, она делит диск на группы блоков, независимо от того, где располага­ются границы между цилиндрами. Каждая группа блоков начинается с суперблока, в котором хранится информация о том, сколько блоков и i-узлов находятся в дан­ной группе, о размере группы блоков и т. д. Затем следует описатель группы, содер­жащий информацию о расположении битовых массивов, количестве свободных блоков и i-узлов в группе, а также количестве каталогов в группе. Эта информация важна, так как файловая система Ext2 пытается распространить каталоги равномер­но по всему диску. В двух битовых массивах ведется учет свободных блоков и свободных i-узлов. Размер каждого битового массива равен одному блоку. При размере блоков в 1 Кбайт такая схема ограничивает размер группы блоков 8192 блоками и 8192 i-узлами. На практике ограничение числа i-узлов никогда не встречается, так как блоки заканчиваются раньше. Затем располагаются сами i-узлы. Размер каждого i-узла – 128 байт, что в два раза больше размера стандартных i-узлов в UNIX. Дополнительные байты в i-узле используются следующим образом. Вместо 10 пря­мых и 3 косвенных дисковых адресов файловая система Linux позволяет 12 пря­мых и 3 косвенных дисковых адреса. Кроме того, длина адресов увеличена с 3 до 4 байт, и это позволяет поддерживать дисковые разделы размером более 224 бло­ков (16 Гбайт), что уже стало проблемой для UNIX.
Работа файловой системы похожа на функционирование быстрой файловой системы Berkeley. Однако в отличие от BSD, в системе Linux используются дис­ковые блоки только одного размера – 1 Кбайт. Быстрая файловая система Berkeley использует 8-килобайтные блоки, которые затем разбиваются при необходимости на килобайтные фрагменты. Файловая система Ext2 делает примерно то же самое, но более простым способом. Как и система Berkeley, когда файл увеличивается в размерах, файловая система Ext2 пытается поместить новый блок файла в ту же группу блоков, что и остальные блоки, желательно сразу после предыдущих бло­ков. Кроме того, при создании нового файла в каталоге файловая система Ext2 ста­рается выделить ему блоки в той же группе блоков, в которой располагается ката­лог. Новые каталоги, наоборот, равномерно распределяются по всему диску.
Другой файловой системой Linux является файловая система /рrос (process – процесс). Идея этой файловой системы изначально была реализована в 8-й редак­ции операционной системы UNIX, созданной лабораторией Bell Labs, а позднее скопированной в 4.4BSD и System V. Однако в операционной системе Linux дан­ная идея получила дальнейшее развитие. Основная концепция этой файловой системы заключается в том, что для каждого процесса системы создается подката­лог в каталоге /рrос. В этом каталоге располагаются файлы, которые хранят информацию о процессе – его командную строку, строки окружения и маски сигналов. В действительности этих файлов на диске нет. Когда они считываются, система получает информацию от фактического процесса и возвращает ее в стандартном формате. Многие расширения, реализованные в операционной системе Linux, относятся к файлам и каталогам, расположенным в каталоге /рrос. Они содержат информацию о центральном процессоре, дисковых разделах, векторах прерывания, счетчиках ядра, файловых системах, подгружаемых модулях и о многом другом. Непривилегирован­ные программы пользователя могут читать большую часть этой информации, что позволяет им узнать о поведении системы безопасным способом. Некоторые из этих файлов могут записываться в каталог /рrос, чтобы изменить параметры системы.

7.5.5. Реализация файловой системы NFS
Файловая система NFS (Network File System – сетевая файловая система) корпо­рации Sun Microsystems, использующуюся на всех современных системах UNIX (а также на некоторых не-UNIX системах) для объединения на логическом уров­не файловых систем отдельных сетевых машин в единое целое.
В основе файловой системы NFS лежит представление о том, что пользоваться общей файловой системой может произвольный набор клиентов и серверов. Во мно­гих случаях все клиенты и серверы располагаются на одной и той же локальной сети, хотя этого не требуется. Файловая система NFS может также работать в гло­бальной сети, если сервер находится далеко от клиента. Для простоты мы будем говорить о клиентах и серверах, как если бы они работали на различных машинах, хотя файловая система NFS позволяет каждой машине одновременно быть клиентом и сервером. Каждый сервер файловой системы NFS экспортирует один или несколько ее каталогов, предоставляя доступ к ним удаленным клиентам. Как правило, доступ к каталогу предоставляется вместе со всеми его подкаталогами, то есть все дерево каталогов экспортируется как единое целое. Список экспортируемых сервером каталогов хранится в файле /etc/fexports, таким образом, эти каталоги экспортиру­ются автоматически при загрузке сервера. Клиенты получают доступ к экспорти­руемым каталогам, монтируя эти каталоги. Когда клиент монтирует удаленный каталог, этот каталог становится частью иерархии каталогов клиента. У одного и того же файла могут быть различные имена на различных клиентах, так как их каталоги могут монтироваться в различных узлах каталоговых деревьев. Выбор узла, в котором монтируется уда­ленный каталог, целиком зависит от клиента. Сервер не знает, где клиент монти­рует его каталог.
Так как одна из целей файловой системы NFS заключается в поддержке разнород­ных систем, в которых клиенты и серверы могут работать под управлением раз­личных операционных систем и на различном оборудовании, существенно, чтобы интерфейс между клиентами и серверами был тщательно определен. Только в этом случае можно ожидать, что новый написанный клиент будет корректно работать с существующими серверами, и наоборот. В файловой системе NFS эта задача выполняется при помощи двух протоко­лов клиент-сервер. Протокол – это набор запросов, посылаемых клиентами сер­верам, и ответов серверов, посылаемых клиентам.
Первый протокол NFS управляет монтированием каталогов. Клиент может послать серверу путь к каталогу и запросить разрешение смонтировать этот каталог где-либо в своей иерархии каталогов. Данные о месте, в котором клиент намерева­ется смонтировать удаленный каталог, серверу не посылаются, так как серверу это безразлично. Если путь указан верно и указанный каталог был экспортирован, тогда сервер возвращает клиенту дескриптор файла, содержащий поля, однознач­но идентифицирующие тип файловой системы, диск, i-узел каталога и информа­цию о правах доступа. Этот дескриптор файла используется последующими обра­щениями чтения и записи к файлам в монтированном каталоге или в любом из его подкаталогов.
Во время загрузки операционная система UNIX, прежде чем перейти в много­пользовательский режим, запускает сценарий оболочки /etc/rc. В этом сценарии можно разместить команды монтировки файловых систем. Таким образом, все необходимые удаленные файловые системы будут автоматически смонтированы прежде, чем будет разрешена регистрация в системе. В качестве альтернативы в большинстве версий системы UNIX также поддерживается автомонтировка. Это свойство позволяет ассоциировать с локальным каталогом несколько удаленных каталогов. Ни один из этих удаленных каталогов не монтируется во время загруз­ки операционной системы (не происходит даже контакта с сервером). Вместо это­го при первом обращении к удаленному файлу (когда файл открывается) опера­ционная система посылает каждому серверу сообщение. Побеждает ответивший первым сервер, чей каталог и монтируется.
У автомонтировки есть два принципиальных преимущества перед статической монтировкой с использованием файла /etc/rc. Во-первых, если один из серверов, перечисленных в файле /etc/rc, окажется выключенным, запустить клиента будет невозможно, по крайней мере без определенных трудностей, задержки и большого количества сообщений об ошибках. Если пользователю в данный момент этот сервер не нужен, вся работа просто окажется напрасной. Во-вторых, предоставление кли­енту возможности связаться с несколькими серверами параллельно позволяет зна­чительно повысить устойчивость системы к сбоям (так как для работы достаточно всего одного работающего сервера) и улучшить показатели производи-тельности (так как первый ответивший сервер скорее всего окажется наименее загруженным). С другой стороны, при таком подходе неявно подразумевается, что все указан­ные как альтернативные файловые системы идентичны для автомонтировки. Так как файловая система NFS не предоставляет поддержки репликации файлов или каталогов, то следить за идентичностью всех файловых систем должен сам пользо­ватель. Поэтому автомонтировка, как правило, используется для файловых сис­тем, в которых клиенту разрешено только чтение. Такие файловые системы обыч­но содержат системные двоичные файлы, а также другие редко изменяемые файлы.
Второй протокол NFS предназначен для доступа к каталогам и файлам. Клиен­ты могут посылать серверам сообщения, содержащие команды управления ката­логами и файлами, что позволяет им создавать, удалять, читать и писать файлы. Кроме того, у клиентов есть доступ к атрибутам файла, таким как режим, размер и время последнего изменения файла. Файловой системой NFS поддерживается большинство системных вызовов операционной системы UNIX, за исключением системных вызовов open и close. Пропуск системных вызовов open и close не случаен. Это сделано намеренно. Нет необходимости открывать файл, прежде чем прочитать его. Также не нужно закрывать файл после того, как данные из него прочитаны. Вместо этого, чтобы прочитать файл, клиент посылает на сервер сообщение lookup, содержащее имя файла, с запросом найти этот файл и вернуть дескриптор файла, представляющий собой структуру, идентифицирующую файл (то есть содержащую идентификатор файловой системы и номер i-узла вместе с прочей информацией). В отличие от системного вызова open, операция lookup не копирует никакой информации во внутренние системные таблицы. Системному вызову read подается на входе деск­риптор файла, который предстоит прочитать, смещение в файле, а также количе­ство байтов, которые нужно прочитать. Таким образом, каждое сообщение явля­ется самодостаточным. Преимущество такой схемы заключается в том, что серверу не нужно помнить что-либо об открытых соединениях между обращениями к нему. Поэтому если на сервере произойдет сбой с последующей перезагрузкой, не будет потеряно никакой информации об открытых файлах, так как терять просто нече­го. Такие серверы называются серверами без состояния.
К сожалению, метод файловой системы NFS усложняет достижение точной файловой семантики системы UNIX. Например, в операционной системе UNIX файл может быть открыт и заблокирован, так что никакой другой процесс не сможет получить к этому файлу доступ. Когда файл закрывается, все его блокировки сни­маются. В сервере без состояния, как в файловой системе NFS, с открытыми фай­лами нельзя связать блокировку, так как сервер не знает, какие файлы открыты. Следовательно, в файловой системе NFS требуется отдельный специальный ме­ханизм осуществления блокировки.
Файловая система NFS использует стандартный механизм защиты UNIX с би­тами rwx для владельца, группы и всех прочих пользователей. Изначально каждое со­общение с запросом просто содержало идентификаторы пользователя и группы вызывающего процесса, которые сервер NFS использовал для проверки прав до­ступа. В настоящее время для установки надежного ключа для аутентификации клиента и сервера при каждом запросе и каждом отве­те можно использовать шифрование с открытым ключом. При этом злоумышлен­ник не сможет выдать себя за другого клиента (или другой сервер), так как ему неизвестен секретный ключ этого клиента (или сервера).
Хотя реализация программ клиента и сервера не зависит от протоколов NFS, в боль­шинстве систем UNIX используется их трехуровневая реализация. Верхний уровень представляет собой уровень системных вызовов. Он управляет такими системными вызовами, как open, read и close. После анализа системного вызова и проверки его параметров он вызывает второй уровень – уровень VFS (Virtual File System – виртуальная файловая система).
Задача уровня VFS заключается в управлении таблицей, содержащей по одной записи для каждого открытого файла, аналогичной таблице i-узлов для открытых файлов в системе UNIX. В обычной системе UNIX i-узел однозначно указывается парой: устройство – номер i-узла. Вместо этого уровень VFS содержит для каждо­го открытого файла записи, называемые v-узлами (virtual i-node – виртуальный i-узел). V-узлы используются, чтобы отличать локальные файлы от удаленных. Для удаленных файлов предоставляется информация, достаточная для доступа к ним. Для локальных файлов записываются сведения о файловой системе и i-узле, так как современные системы UNIX могут поддерживать несколько файловых систем (например, V7, Berkeley Fast, ext2, /proc, FAT и т. д.). Хотя уровень VFS был создан для поддержки файловой системы NFS, сегодня он поддерживается большинством современных систем UNIX как составная часть операционной сис­темы, даже если NFS не используется.
Чтобы понять, как используются v-узлы, рассмотрим выполнение последова­тельности системных вызовов mount, open и read. Чтобы смонтировать файловую систему, системный администратор (или сценарий /etc/rc) вызывает программу mount, указывая ей удаленный каталог, локальный каталог, в котором следует смонтировать удаленный каталог, и прочую информацию. Программа mount ана­лизирует имя удаленного каталога и обнаруживает имя сервера NFS, на котором располагается удаленный каталог. Затем она соединяется с этой машиной, запра­шивая у нее дескриптор удаленного каталога. Если этот каталог существует и его удаленное монтирование разрешено, сервер возвращает его дескриптор. Наконец, программа mount обращается к системному вызову mount, передавая ядру получен­ный от сервера дескриптор каталога. Затем ядро формирует для удаленного каталога v-узел и просит программу клиента NFS создать в своих внутренних таблицах r-узел (удален­ный i-узел) для хранения дескриптора файла. V-узел указывает на r-узел. Каждый v-узел на уровне VFS будет в конечном итоге содержать либо указатель на r-узел в программе клиента NFS, либо указатель на i-узел в одной из локальных файловых систем. По содержимому v-узла можно понять, является ли файл или каталог локальным или удаленным. Если он локальный, то может быть найдена соответствующая файловая система и i-узел. Если файл удаленный, может быть найден удаленный хост и дескриптор файла.
Когда на клиенте открывается удаленный файл, при анализе пути файла ядро обнаруживает каталог, в котором смонтирована удаленная файловая система. Оно видит, что этот каталог удаленный, а в v-узле каталога находит указатель на r-узел. Затем она просит программу клиента NFS открыть файл. Программа клиента NFS просматривает оставшуюся часть пути на удаленном сервере, ассоциированном с монтированным каталогом, и получает обратно дескриптор файла для него. Он создает в своих таблицах r-узел для удаленного файла и докладывает об этом уровню VFS, который помещает в свои таблицы v-узел для файла, указывающий на r-узел. Таким образом и в этом случае у каждого открытого файла или каталога есть v-узел, указывающий на r-узел или i-узел.
Вызывающему процессу выдается дескриптор удаленного файла. Этот дескрип­тор файла отображается на v-узел при помощи таблиц уровня VFS. Необходимо обратить вни­мание, что на сервере не создается никаких записей в таблицах. Хотя сервер готов предоставить дескрипторы файлов по запросу, он не следит за состоянием дескрип­торов файлов. Когда дескриптор файла присылается серверу для доступа к файлу, сервер проверяет дескриптор и использует его, если дескриптор действителен. При проверке может проверяться ключ аутентификации, содержащийся в заголовках вызова удаленной процедуры RPC.
Когда дескриптор файла используется в последующем системном вызове, на­пример read, уровень VFS находит соответствующий v-узел и по нему определяет, является ли он локальным или удаленным, а также какой i-узел или r-узел его опи­сывает. Затем он посылает серверу сообщение, содержащее дескриптор, смещение в файле (хранящееся на стороне клиента, а не сервера) и количество байтов. Для повышения эффективности обмен информацией между клиентом и сервером вы­полняется большими порциями, как правило, по 8192 байт, даже если запрашива­ется меньшее количество байтов.
Когда сообщение с запросом прибывает на сервер, оно передается там уровню VFS, который определяет файловую систему, содержащую файл. Затем уровень VFS обращается к этой файловой системе, чтобы прочитать и вернуть байты. Эти данные передаются клиенту. После того, как уровень VFS клиента получает 8-ки-лобайтную порцию данных, которую запрашивал, он автоматически посылает за­прос на следующую порцию, чтобы она была под рукой, когда понадобится. Такая функция, называемая опережающим чтением, позволяет значительно увеличить производительность.
При записи в удаленный файл проходится аналогичный путь от клиента к серверу. Данные также передаются 8-килобайтными порциями. Если системному вызову write подается менее 8 Кбайт данных, данные просто накапливаются ло­кально. Только когда порция в 8 Кбайт готова, она посылается серверу. Если файл закрывается, то весь остаток немедленно посылается серверу.
Кроме того, для увеличения производительности применяется кэширование, как в обычной системе UNIX. Серверы кэшируют данные, чтобы снизить количе­ство обращений к дискам, но это происходит незаметно для клиентов. Клиенты управляют двумя кэшами, одним для атрибутов файлов (i-узлов) и одним для дан­ных. Когда требуется либо i-узел, либо блок файла, проверяется, нельзя ли полу­чить эту информацию из кэша. Если да, то обращения к сети можно избежать.
Хотя кэширование на стороне клиента во много раз повышает производитель­ность, оно также приводит к появлению непростых проблем. Например, если два клиента сохранили в своих кэшах один и тот же блок файла, а затем один из клиентов его модифицировал, тогда другой клиент, считывая этот блок, получает из кэша старое значение блока. Учитывая серьезность данной проблемы, реализация NFS пытается смягчить ее остроту несколькими способами. Во-первых, с каждым блоком кэша ассоцииро­ван таймер. Когда время истекает, запись считается недействительной. Как прави­ло, для блоков с данными таймер устанавливается на 3 секунды, а для блоков каталога – на 30 секунд. Таким образом риск несколько снижается. Кроме того, при каждом открытии кэшированного файла серверу посылается сообщение, чтобы определить, когда в последний раз был модифицирован этот файл. Если последнее изменение про­изошло после того, как была сохранена в кэше локальная копия файла, эта копия из кэша удаляется, а с сервера получается новая копия. Наконец, каждые 30 секунд ис­текает время таймера, и все модифицированные («грязные») блоки кэша посылаются на сервер. Хотя такая схема и далека от совершенства, но она успешно используется системой в большинстве практических случаев.

7.6. Безопасность в UNIX

7.6.1. Основные понятия

Каждый пользователь операционной системы UNIX регистрируется в системе, получая свой уникальный UID (User ID – идентификатор пользователя). UID представляет собой целое число в пределах от 0 до 65 535. Идентификатором владельца помеча­ются файлы, процессы и другие ресурсы. По умолчанию владельцем файла явля­ется пользователь, создавший этот файл, хотя владельца можно сменить.
Пользователи могут организовываться в группы, которые также нумеруются 16-разрядными целыми числами, называемыми GID (Group ID – идентификатор группы). Назначение пользователя к группе выполняется вручную системным администратором и заключается в создании нескольких записей в системной базе данных, в которой содержится информация о том, какой пользователь к какой группе принадлежит. Вначале пользователь мог принадлежать только к одной группе, но теперь в некоторых версиях системы UNIX пользователь может одно­временно принадлежать к нескольким группам.
Основной механизм безопасности в операционной системе UNIX заключается в следующем. Каж­дый процесс несет на себе UID и GID своего владельца. Когда создается файл, он получает UID и GID создающего его процесса. Файл также получает набор разре­шений доступа, определяемых создающим процессом. Эти разрешения определя­ют доступ к этому файлу для владельца файла, для других членов группы владель­ца файла и для всех прочихпользователей. Для каждой из этих трех категорий определяется три вида доступа: чтение, запись и исполнение файла, что обознача­ется соответственно буквами r, w и х (read, write, execute). Возможность исполнять файл, конечно, имеет смысл только в том случае, если этот файл является испол­няемой двоичной программой. Попытка запустить файл, у которого есть разре­шение на исполнение, но который не является исполняемым (то есть не начинает­ся с соответствующего заголовка), закончится ошибкой. Поскольку существует три категории пользователей и три вида доступа для каждой категории, все режимы доступа к файлу можно закодировать 9 битами.
Пользователь, UID которого равен 0, является особым пользователем и назы­вается суперпользователем (superuser или root). Суперпользователь может читать и писать все файлы в системе, независимо от того, кто ими владеет и как они защищены. Процессы с UID=0 также обладают возможностью обращаться к не­большой группе системных вызовов, доступ к которым запрещен для обычных пользователей. Как правило, пароль суперпользователя известен только систем­ному администратору.
Каталоги представляют собой файлы и обладают теми же самыми режимами защиты, что и обычные файлы. Отличие состоит в том, что бит х интерпретиру­ется в отношении каталогов как разрешение не исполнения, а поиска в каталоге. У специальных файлов, соответствующих устройствам ввода-вывода, есть те же самые биты защиты. Благодаря этому может использоваться тот же самый механизм для ограничения доступа к устройствам ввода-вывода. Существует общая проблема регулируемого доступа ко всем устройствам ввода-вывода и другим системным ресурсам. Эта проблема решается с помощью добавления к указанным выше 9 бит нового бита защиты – бита SETUID. Когда выполняется программа с уста­новленным битом SETUID, то запускаемому процессу присваивается не UID вы­звавшего его пользователя или процесса, a UID владельца файла. Когда процесс пытается открыть файл, то проверяется его рабочий UID, а не UID запустившего его пользователя. Таким образом, если программой, обращающейся к принтеру, будет владеть демон с установленным битом SETUID, тогда любой пользователь сможет запустить ее и запущенный процесс будет обладать полномочиями демона, но только для запуска этой программы (которая может устанавливать задания в очередь на принтер).
Помимо бита SETUID, есть также еще и бит SETGID, работающий аналогич­но и временно предоставляющий пользователю рабочий GID программы. Однако на практике этот бит почти не используется.
7.6.2. Реализация безопасности в UNIX

Когда пользователь входит в систему, программа регистрации login (которая явля­ется SETUID root) запрашивает у пользователя его имя и пароль. Затем она хэширует пароль и ищет его в файле паролей /etc/passwd, чтобы определить, соответ­ствует ли хэш-код содержащимся в нем значениям. Хэширование применяется, чтобы избежать хранения паро­ля в незашифрованном виде где-либо в системе. Если пароль введен верно, про­грамма регистрации считывает из файла /etc/passwd имя программы оболочки, которую предпочитает пользователь. Ей может быть программа sh, но это также может быть и другая оболочка, например csh или ksh. Затем программа регистрации ис­пользует системные вызовы setuld и setgid, чтобы установить для себя UID и GID. После этого программа регистрации открывает клавиатуру для стандартного ввода (файл с дескрипто­ром 0) и экран для стандартного вывода (файл с дескриптором 1), а также экран для вывода стандартного потока сообщений об ошибках (файл с дескриптором 2). Наконец, она выполняет оболочку, которую указал пользователь, таким образом, завершая свою работу.
С этого момента начинает работу оболочка с установленными UID и GID, а так­же стандартными потоками ввода, вывода и ошибок, настроенными на устройства ввода-вывода по умолчанию. Все процессы, которые она запускает при помощи системного вызова fork (то есть команды, вводимые пользователем с клавиатуры), автоматически наследуют UID и GID оболочки, поэтому у них будет верное зна­чение владельца и группы. Все файлы, создаваемые этими процессами, также будут иметь эти значения.
Когда любой процесс пытается открыть файл, система сначала проверяет биты защиты в i-узле файла для заданных значений рабочих UID и GID, чтобы опреде­лить, разрешен ли доступ для данного процесса. Если доступ разрешен, файл от­крывается и процессу возвращается дескриптор файла. В противном случае файл не открывается, а процессу возвращается значение –1. При последующих обраще­ниях к системным вызовам read и write проверка не выполняется. В результате, если режим защиты файла изменяется уже после того, как файл открыт, новый режим не повлияет на процессы, которые уже успели открыть этот файл.
В операционной системе Linux защита файлов и ресурсов осуществляется так же, как и в UNIX.

Резюме

Операционная система UNIX широко используется на вычислительных маши­нах различных классов от ноутбуков до суперкомпьютеров. В операционной системе UNIX есть три интерфейса: оболочка, библиотека языка С и сами системные вызовы. Оболочка позво­ляет пользователям вводить команды и исполнять их. Это могут быть простые команды, конвейеры или более сложные структуры. Ввод и вывод могут пере­направляться. В библиотеке С содержатся системные вызовы, а также множество расширенных вызовов. Каждый из системных вызовов выполняет определенные необходимые функции.
К ключевым понятиям операционной системы UNIX относятся процесс, модель памяти, ввод-вывод и файловая система. Процессы могут создавать дочерние про­цессы, в результате чего формируются деревья процессов. Для управления про­цессами в UNIX используются две ключевые структуры данных: таблица процес­сов и структура пользователя. Таблица процессов постоянно находится в памяти, а структура пользователя может выгружаться на диск. При создании процесса дублируется запись в таблице процессов, а также образ памяти процесса. Для пла­нирования применяется алгоритм, основанный на приоритетах, отдающий пред­почтение интерактивным процессам.
Модель памяти состоит из трех сегментов для каждого процесса: для текста (исполняемого кода), данных и стека. Изначально для управления памятью ис­пользовался свопинг, но в большинстве современных версий системы UNIX для этого применяется страничная подкачка. Состояние каждой страницы отслежи­вается в карте памяти, а страничный демон поддерживает достаточное количество свободных страниц при помощи алгоритма часов.
Доступ к устройствам ввода-вывода осуществляется при помощи специальных файлов, у каждого из которых есть старший номер устройства и младший номер устройства. Для снижения числа обращений к диску в блочных устройствах вво­да-вывода применяется буферный кэш. Для управления кэшем используется ал­горитм LRU (Least-Recently-Used – «наиболее давнего использования»). Сим­вольный ввод-вывод может осуществляться в обработанном и необработанном режимах. Для дополнительных возможностей символьного ввода-вывода приме­няются дисциплины линии связи или потоки.
Файловая система в UNIX – иерархическая, с файлами и каталогами. Все диски монтируются в единое дерево каталогов, начинающееся в одном корне. Отдельные файлы могут быть связаны с любым каталогом дерева. Чтобы пользоваться файлом, его нужно сначала открыть. При этом процессу, открывающему файл, возвращает­ся дескриптор файла, который затем используется при чтении этого файла и запи­си в файл. Внутри файловая система использует три основные таблицы: таблицу дескрипторов файлов, таблицу дескрипторов открытых файлов и таблицу i-узлов. Из этих таблиц таблица i-узлов является наиболее важной. В ней содержится ин­формация, необходимая для управления файлом и позволяющая найти его блоки.
Защита файлов основывается на регулировании доступа для чтения, записи и исполнения, предоставляемого владельцу файла, членам его группы и всем осталь­ным пользователям. Для каталогов бит исполнения интерпретируется как разре­шение поиска в каталоге.


Контрольные вопросы и задания

1. Опишите интерфейсы ОС UNIX.
2. Каковы особенности оболочки и утилит системы UNIX?
3. Дайте определение программы, называемой фильтром.
4. Как называются файлы, содержащие команды оболочки?
5. В чем заключается идея стандартизации обслуживающих программ UNIX?
6. Представьте состав нижнего уровня ядра UNIX.
7. Какие функции выполняет уровень системы виртуальной памяти UNIX?
8. Назовите главные функции уровня интерфейсов системы UNIX.
9. Какие функции выполняют в UNIX фоновые процессы, называемые демо­нами?

10. Опишите механизмы взаимодействия и синхронизации процессов в UNIX.
11. Какие структуры данных, относящиеся к процес­сам, поддерживает ядро системы UNIX?
12. Перечислите категории информации, хранящейся в таблице процессов.
13. Какие данные составляют структуру пользователя?
14. Опишите этапы создания процесса в системе UNIX.
15. Охарактеризуйте методы планирования в ОС семейства UNIX .
16. Из каких сегментов состоит адресное пространство в UNIX?
17. В чем заключается свойство отображения файлов на адресное пространство памяти?
18. Опишите способы реализации управления памятью в UNIX .
19. Как работает механизм страничной подкачки в UNIX?
20. Поясните структуру карты памяти и реализацию алгоритма замещения страниц.
21. Опишите реализацию ввода-вывода в ОС UNIX .
22. Дайте определение понятию «сокет» и перечислите наиболее распространенные типы сокетов.
23. Охарактеризуйте решения, применяемые в UNIX для структурирования драйверов символьных устройств и придания им свойства модульности.
24. Какие типы файлов поддерживаются в ОС UNIX?
25. Как реализована классическая файловая система UNIX.
26. В чем заключаются особенности реализация файловой системы Berkeley Fast?
27. Представьте реализацию файловых систем Linux.
28. Охарактеризуйте файловую систему NFS.
29. Каким образом реализуется свойство автомонтировки файловых систем в UNIX?
30. К каким проблемам приводит использование кэширования данных
в файловой системе NFS?
31. Опишите функционирование системы безопасности в UNIX.
8. Пример практической реализации
операционной системы: Windows 2000

8.1. Обзор структуры операционной систем Windows 2000

8.1.1. Структура системы

Операционная система Windows 2000 состоит из двух основных частей: самой опе­рационной системы, работающей в режиме ядра, и подсистем окружения, работа­ющих в режиме пользователя. Ядро является традиционным ядром в том смысле, что оно управляет процессами, памятью, файловой системой и т. д. Подсистемы окружения являются отдельными процессами, помогающими пользователю выполнять определенные систем­ные функции.
Одно из многих усовершенствований системы NT по сравнению с Windows 3.1 заключалось в ее модульной структуре. Она состояла из относительно небольшо­го ядра, работавшего в режиме ядра, плюс нескольких серверных процессов, рабо­тавших в режиме пользователя. Процессы пользователя взаимодействовали с сер­верными процессами с помощью модели клиент-сервер: клиент посылал серверу сообщение, а сервер выполнял определенную работу и возвращал клиенту резуль­тат в ответном сообщении. Такая модульная структура упрощала перенос систе­мы на другие компьютеры. В результате операционная система Windows NT была успешно перенесена на платформы с процессорами, отличными от процессоров Intel, а именно: Alpha корпорации DEC, Power PC корпорации IBM и MIPS фир­мы SGI. Кроме того, такая структура защищала ядро от ошибок в коде серверов. Однако для увеличения производительности, начиная с версии NT 4.0, довольно большая часть операционной системы (например, управление системными вызо­вами и вся экранная графика) были возвращены в ядро. Такая схема сохранилась и в Windows 2000.
Тем не менее в операционной системе Windows 2000 сохранилась некоторая структура. Система разделена на несколько уровней, каждый из которых пользу­ется службами лежащего ниже уровня. Два нижних уровня программного обеспечения – уровень так называемых аппаратных абстрак­ций и ядро написаны на языке С и ассемблере и являются частично машинно-зависимыми. Верхние уровни написаны исключи­тельно на С и почти полностью машинно-независимы. Драйверы написаны на С или, в некоторых случаях, на C++.
Рассмотрим подробнее различ­ные компоненты системы, начиная с самых нижних уровней и постепенно продви­гаясь наверх.
Одна из целей создания Windows 2000 (и Windows NT) заключалась в возмож­ности переносить систему на другие платформы. В идеале при появлении новой машины для запуска операционной системы на ней нужно всего лишь переком­пилировать операционную систему новым компилятором для данной машины. К сожалению, в реальности сделать это не совсем просто. Хотя можно добиться полной переносимости верхних уровней операционной системы (так как в основном они имеют дело с внут­ренними структурами данных), нижние уровни работают с регистрами устройств, прерываниями, DMA и другими аппаратными особенностями, которые очень силь­но отличаются на разных машинах. Несмотря на то, что большая часть кода нижнего уровня напи­сана на С, даже ее нельзя просто перенести с процессора Pentium на процессор Alpha, перекомпилировать и перезагрузить, так как существует большое количе­ство мелких различий между этими процессорами, не имеющих отношения к раз­личиям в наборе команд, которые невозможно спрятать компилятором.
Корпорация Microsoft, хорошо представляя себе эту проблему, предприняла серьез­ные попытки скрыть многие из аппаратных различий в тонком уровне на самом дне системы, названном уровнем аппаратных абстракций (HAL, Hardware Abstraction Layer). Работа уровня HAL заключается в том, чтобы предоставлять всей остальной системе абстрактные аппаратные устройства, свободные от индиви­дуальных отличительных особенностей, которыми так богато реальное аппарат­ное обеспечение. Эти устройства представляются в виде машинно-независимых служб (процедурных вызовов и макросов), которые могут использоваться осталь­ной операционной системой и драйверами. Поскольку драйверы и ядро пользуются службами HAL (идентичными на всех операционных системах Windows 2000, не­зависимо от аппаратного обеспечения) и не обращаются напрямую к устройствам, требуется значительно меньше изменений для их переноса на другую платформу. Перенос самого уровня HAL довольно прост, так как весь машинно-зависимый код сконцентрирован в одном месте, а цель переделки четко определена, то есть за­ключается в реализации всех служб уровня HAL.
В уровень HAL включены те службы, которые зависят от набора микросхем материнской платы и меняются от машины к машине в разумных предсказуемых пределах. Другими словами, он разработан, чтобы скрывать различия между материнскими платами различных производителей, но не различия между процес­сорами Pentium и Alpha. К службам уровня HAL относятся: доступ к регистрам устройств, адресация к устройствам, независящим от шины, обработка прерыва­ний и возврат из прерываний, операции прямого доступа к памяти (DMA), управление таймерами, часами реального времени, спин-бло­кировками нижнего уровня и синхронизация многопроцессорных конфигура­ций, интерфейс с BIOS и доступ к CMOS-памяти. Уровень HAL не предоставляет абстракций или служб для специфических устройств ввода-вывода – клавиатур, мышей или дисков, а также блоков управления памятью.
Драйверам часто бывает нужно получить доступ к специфическим устройствам ввода-вывода. На аппаратном уровне у драйвера есть один или несколько адресов определенной шины. Поскольку у современных компьютеров часто есть несколь­ко шин (PCI, SCSI, USB, IEEE1394 и т. д.), может случиться, что два или более устройств имеют один и тот же адрес шины, поэтому требуется некоторый способ отличать эти устройства. Уровень HAL предоставляет службу для идентифика­ции устройств, отображая адреса устройств на шине на логические системные адреса. Поэтому драйверам не нужно следить за тем, которое устройство находит­ся на какой шине. Такая логическая адресация аналогична дескрипторам, выдава­емым операционной системой программам пользователя для обращения к файлам и другим системным ресурсам. Этот механизм также защищает более высокие уровни от свойств структур шин и соглашений об адресации.
С прерываниями связана схожая проблема – они также являются зависимыми от шины. Здесь уровень HAL предоставляет службы для именования прерываний уникальным в пределах всей системы способом, а также службы, позволяющие драйверам связывать процедуры обработки прерываний с прерываниями перено­симым способом. При этом не нужно знать, какой вектор к какой шине относится. Управление уровнем запроса прерывания также осуществляется на уровне HAL.
Другая служба HAL занимается управлением операциями DMA независимым от устройств способом. HAL может управлять как единым для всей системы меха­низмом DMA, так и механизмами DMA, специфичными для конкретных плат вво­да-вывода. Обращение к устройствам осуществляется по их логическим адресам.
Уровень HAL также реализует программные операции чтения/записи с разнесе­нием данных (с обращением к не являющимся соседними блокам памяти).
Уровень HAL управляет часами и таймерами, обеспечивая переносимость работающих с ними программ. Время хранится в интервалах по 100 нc, что существенно точнее, чем то, как это делалось в MS-DOS в 2-секундных интервалах. Временные службы уровня HAL обеспечивают независимость драйверов от фактических частот, на которых работают часы.
Иногда требуется синхронизация компонентов ядра на очень низком уровне, особенно для того, чтобы избежать конфликтов на многопроцессорных системах. Уровень HAL предоставляет несколько примитивов для управления этой синхро­низацией. Примером являются спин-блокировки, в которых один центральный процессор просто ждет, пока другой центральный процессор не освободит опреде­ленный ресурс. В частности, такой метод синхронизации применяется в ситуаци­ях, в которых доступ к ресурсу, как правило, получается всего на несколько ко­манд процессора.
Наконец, после загрузки операционной системы уровень HAL общается с BIOS и инспектирует память конфигурации CMOS, если она используется, чтобы определить, какие шины и устройства ввода-вывода содержатся в системе и как их следует настроить. Затем эта информация помещается в реестр, чтобы другие компоненты системы могли просматривать их, не обращаясь напрямую к BIOS или CMOS-памяти.
Поскольку уровень HAL является в большой степени машинно-зависимым, он должен в совершенстве соответствовать системе, на которой установлен, поэтому набор различных уровней HAL поставляется на компакт-диске Windows 2000. Во время установки системы из них выбирается подходящий уровень и копируется на жесткий диск в системный каталог \winnt\system32 в виде файла hal.dll. При всех последующих загрузках операционной системы используется эта версия уровня HAL. Если удалить этот файл, то система загрузиться не сможет.
Хотя эффективность уровня HAL является довольно высокой, для мульти­медийных приложений ее может быть недостаточно. По этой причине корпора­ция Microsoft также производит пакет программного обеспечения, называемый DirectX, расширяющий функциональность уровня HAL дополнительными про­цедурами и предоставляющий пользовательским процессам прямой доступ к ап­паратному обеспечению.
Над уровнем аппаратных абстракций располагается уровень, содержащий то, что корпорация Microsoft называет ядром, а также драйверы устройств. Начиная с NT 4.0, практически вся операционная система была помещена в пространство ядра. При описании операционной системе UNIX термин «ядро» используется для обозначения всего, что работает в режиме ядра. В данном разделе все программное обеспечение, работающее в режиме ядра, будем называть «операционной системой».
Часть ядра (и большая часть уровня HAL) постоянно находится в оперативной памяти (то есть не выгру­жается). При помощи установки соответствующего приоритета эта часть ядра может решать, допустимо ли прерывание от устройств ввода-вывода или нет. Хотя значительная часть ядра представляет собой машинно-зависимую програм­му, тем не менее большая ее часть написана на С, кроме тех мест, в которых произ­водительность считается важнее всех остальных задач.
Назначение ядра заключается в том, чтобы сделать всю остальную часть опера­ционной системы независимой от аппаратуры и, таким образом, легко переноси­мой на другие платформы. Оно начинается там, где заканчивается уровень HAL. Ядро получает доступ к аппаратуре через уровень HAL. Оно построено на чрезвы­чайно низкоуровневых службах уровня HAL, формируя из них абстракции более высоких уровней. Например, у уровня HAL есть вызовы для связывания процедур обработки прерываний с прерываниями и установки их приоритетов, но больше практически ничего уровень HAL в этой области не делает. Ядро, напротив, предо­ставляет полный механизм для переключения контекста. Оно должным образом сохраняет все регистры центрального процессора, изменяет таблицы страниц, со­храняет кэш центрального процессора и т. д. Когда все эти действия выполнены, работавший ранее поток оказывается полностью сохраненным в таблицах, распо­ложенных в памяти. Затем ядро настраивает карту памяти нового потока и загру­жает его регистры, после чего новый поток готов к работе.
Программа планирования потоков также располагается в ядре. Когда насту­пает пора проверить, не готов ли к работе новый поток, например, после того, как истечет выделенный потоку квант времени или по завершении процедуры обработ­ки прерываний ввода-вывода, ядро выбирает поток и выполняет переключение контекста, необходимое, чтобы запустить этот поток. С точки зрения остальной операционной системы переключение потоков автоматически осуществляется более низкими уровнями, так что для более высоких уровней не остается никакой работы. Сам алгоритм планирования будет обсуждаться далее в разделе, посвященном процессам и потокам.
Помимо предоставления абстрактной модели аппаратуры более высоким уровням и управления переключениями потоков, ядро также выполняет еще одну ключевую функцию: предоставляет низко-уровневую поддержку двум классам объектов – управляющим объектам и объектам диспетчеризации. Эти объекты не являются объектами, к которым пользовательские процессы получают дескрип­торы, но представляют собой внутренние объекты, на основе которых исполняю­щая система строит объекты пользователя.
Управляющие объекты – это объекты, управляющие системой, включая при­митивные объекты процессов, объекты прерываний и два объекта, называемых DPC и АРС. Объект DPC (Deferred Procedure Call – отло­женный вызов процедуры) используется, чтобы отделить часть процедуры обра­ботки прерываний, для которой время является критичным, от той ее части, для которой время некритично. Как правило, процедура обработки прерываний сохра­няет несколько аппаратных регистров, связанных с прерывающим устройством ввода-вывода, чтобы их можно было потом восстановить, и разрешает аппаратуре продолжать работу, но оставляет большую часть обработки на потом. Например, когда пользователь нажимает на клавишу, процедура обработки прерываний от клавиатуры считывает из регистра код нажатой клавиши и разреша­ет прерывания от клавиатуры. Но эта процедура не должна немедленно обрабаты­вать введенный символ, особенно если в данный момент происходит нечто более важное (то есть нечто с более высоким приоритетом). Пока обработка клавиши за­нимает не более 100 мс, пользователь ничего не заметит. Отложенные вызовы про­цедуры также применяются для слежения за таймерами и другой активностью, для которой не требуется немедленная обработка. Очередь DPC представляет собой механизм напоминания о том, что есть работа, которую следует выполнить позднее.
Объект АРС (Asynchronous Procedure Call – асинхронный вызов процедуры) похож на отложенный вызов процедуры DPC, но отличается тем, что асинхрон­ный вызов процедуры выполняется в контексте определенного процесса. Когда обрабатывается нажатая клавиша, не имеет значения, в каком контексте работает DPC, так как все, что требуется сделать, – это исследовать введенный код и, воз­можно, поместить его в буфер в ядре. Однако если по прерыванию потребуется скопировать буфер из пространства ядра в адресное пространство пользовательс­кого процесса (например, по завершении операции чтения модема), тогда проце­дура копирования должна работать в контексте получателя. Контекст получателя нужен для того, чтобы в таблице страниц одновременно содержались и буфер ядра, и буфер пользователя. По этой причине в разных ситуаци­ях используются АРС или DPC.
Еще один тип объектов ядра – объекты диспетчеризации. К ним относятся семафоры, мьютексы, события, таймеры и другие объекты, изменения состояния которых могут ждать потоки. Причина, по которой они должны обра­батываться ядром, заключается в том, что они тесно переплетены с планированием потоков, что входит в круг задач ядра.
Над ядром и драйверами устройств располагается верхняя часть операционной системы, называемая исполняющей системой (а также иногда супервизором или диспетчером). Исполняющая система написана на С, она не зависит от архитектуры и может быть перенесена на новые машины с относительно небольшими усилиями. Исполняющая система состоит из 10 компонентов, каждый из которых представляет собой просто набор процедур, работающих вместе для выполнения некоторой задачи. Между отдель­ными компонентами нет жестких границ, и различные авторы, описывающие ис­полняющую систему, могут даже по-разному группировать составляющие ее про­цедуры в компоненты. Следует заметить, что компоненты одного уровня могут вызывать друг друга, и на практике они этим довольно активно пользуются.
Менеджер объектов управляет всеми объектами, известными операционной системе. К ним относятся процессы, потоки, файлы, каталоги, семафоры, устрой­ства ввода-вывода, таймеры и многое другое. При создании объекта менеджер объектов получает в адресном пространстве ядра блок виртуальной памяти и воз­вращает этот блок в список свободных блоков, когда объект уничтожается. Его работа заключается в том, чтобы следить за всеми объектами. Отметим, что большинство компонентов исполня­ющей системы не являются процесса­ми или потоками, а представляют собой просто набор процедур, которые могут выполняться другими потоками в режиме ядра. Однако некоторые из них, такие как менеджер питания и менеджер plug-and-play, являются настоящими потоками. Менеджер объектов также управляет пространством имен, в которое помеща­ется созданный объект, чтобы впоследствии к нему можно было обратиться по имени. Все остальные компоненты исполняющей системы активно пользуются объектами во время своей работы. Объекты занимают центральное место в функ­ционировании операционной системы Windows 2000.
Менеджер ввода-вывода формирует каркас для управления устройствами ввода-вывода и предоставляет общие службы ввода-вывода. Он предоставляет остальной части системы независимый от устройств ввод-вывод, вызывая для выполнения физического ввода-вывода соответствующий драйвер. Здесь также располагаются все драйверы устройств. Файловые системы формально являются драйверами устройств под управлением менеджера ввода-вывода. Существует два драйвера для файловых систем FAT и NTFS, независимые друг от друга и управляющие различными разделами диска. Все файловые системы FAT управляются одним драйвером. (Ввод-вывод будет рассмотрен далее в подразделе «Ввод-вывод в Windows 2000», а файловая система NTFS – в подразделе «Файловая система Windows 2000»).
Менеджер процессов управляет процессами и потоками, включая их созда­ние и завершение. Он занимается не стратегиями, применяемыми по отношению к процессам, а механизмом, используемым для управления ими. Менеджер про­цессов основывается на объектах потоков и процессов ядра и добавляет к ним до­полнительные функции. Это ключевой элемент многозадачности в Windows 2000. Управление процессами будет рассматриваться далее в подразделе «Процессы и потоки в Windows 2000».
Менеджер памяти реализует архитектуру виртуальной памяти со страничной подкачкой по требованию операционной системы Windows 2000. Он управляет преобразованием виртуальных страниц в физические страничные блоки. Таким образом, он реализует правила защиты, ограничивающие доступ каждого про­цесса только теми страницами, которые принадлежат его адресному пространству, а не адресным пространствам других процессов (кроме специальных случаев). Он также контролирует определенные системные вызовы, относящиеся к вирту­альной памяти. Управление памятью будет рассматриваться в подразделе «Управле­ние памятью».
Менеджер безопасности приводит в исполнение сложный механизм без­опасности Windows 2000, удовлетворяющий требованиям класса С2 Оранжевой книги Министерства обороны США. В Оранжевой книге перечислено множество правил, которые должна соблюдать система, начиная с аутентификации при реги­страции и заканчивая управлением доступом, а также обнулением страниц перед их повторным использованием. Менеджер безопасности будет обсуждаться в подраз­деле «Безопасность в Windows 2000».
Менеджер кэша хранит в памяти блоки диска, которые использовались в по­следнее время, чтобы ускорить доступ к ним в случае, если они понадобятся вновь. Его работа состоит в том, чтобы определить, какие блоки понадобятся снова, а ка­кие нет. Операционная система Windows 2000 может одновременно использовать несколько файловых систем. В этом случае менеджер кэша обслуживает все фай­ловые системы, таким образом, каждой файловой системе не нужно заниматься управлением собственного кэша. Когда требуется блок, он запрашивается у менед­жера кэша. Если у менеджера кэша нет блока, он обращается за блоком к соответ­ствующей файловой системе. Поскольку файлы могут отображаться в адресное пространство процессов, менеджер кэша должен взаимодействовать с менеджером виртуальной памяти, чтобы обеспечить требуемую непротиворечивость. Количе­ство памяти, выделенной для кэша, динамически изменяется и может увеличивать­ся или уменьшаться при необходимости. Менеджер кэша будет описан в подразделе «Кэширование в Windows 2000».
Менеджер plug-and-play получает все уведомления об установленных новых устройствах. Для некоторых устройств проверка производится при загрузке сис­темы, но не после нее. Другие устройства, например устройства USB (Universal Serial Bus – универсальная последовательная шина), могут подключаться в лю­бое время, и их подключение запускает пересылку сообщения менеджеру plug-and-play, который затем находит и загружает соответствующий драйвер.
Менеджер энергопотребления управляет потреблением электроэнергии. Он выключает монитор и диски, если к ним не было обращений в течение определен­ного интервала времени. На переносных компьютерах менеджер энергопотребле­ния следит за состоянием батарей и, когда заряд батарей подходит к концу, пред­принимает соответствующие действия. Эти действия, как правило, заключаются в том, что он сообщает работающим программам о состоянии батарей. В результа­те программы могут сохранить свои файлы и приготовиться к корректному завер­шению работы.
Менеджер конфигурации отвечает за состояние реестра. Он добавляет новые записи и ищет запрашиваемые ключи.
Менеджер вызова локальной процедуры обеспечивает высокоэффективное взаимодействие между процессами и их подсистемами. Поскольку этот путь ну­жен для выполнения некоторых системных вызовов, эффективность оказывается критичной, вот почему для этого не используются стандартные механизмы меж­процессного взаимодействия.
Исполняющий модуль Win32 GDI обрабатывает определенные системные вызовы (но не все). Изначально он располагался в пространстве пользователя, но в версии NT 4.0 для увеличения производительности был перенесен в простран­ство ядра. Интерфейс графических устройств GDI (Graphic Device Interface) за­нимается управлением графическими изображениями для монитора и принтеров. Он предоставляет системные вызовы, позволяющие пользовательским програм­мам выводить данные на монитор и принтеры независящим от устройств спосо­бом. Он также содержит оконный менеджер и драйвер дисплея. До версии NT 4.0 интерфейс графических устройств также находился в пространстве пользователя, но производительность при этом оставляла желать лучшего, поэтому корпорация Microsoft переместила его в ядро.
Над исполняющей системой размещается уровень, называемый систем­ными службами. Его функции заключаются в предоставлении интерфейса к ис­полняющей системе. Он принимает настоящие системные вызовы Windows 2000 и вызывает другие части исполняющей системы для их выполнения.
При загрузке операционная система Windows 2000 загружается в память как набор файлов. Основная часть операционной системы, состоящая из ядра и ис­полняющей системы, хранится в файле ntoskrnl.exe. Уровень HAL представляет собой библиотеку общего доступа, расположенную в отдельном файле hal.dll. Интерфейс Win32 и интерфейс графических устройств хранятся вместе в тре­тьем файле – win32k.sys. Кроме этого, загружается множество драйверов устройств, у большинства которых расширение sys. Существует две версии файла ntoskrnl.exe: для однопроцессорных и многопроцессорных систем. Также существуют версии для процессора Хеоn, способного поддерживать более 4 Гбайт физичес­кой памяти, и для процессора Pentium, который так много оперативной памяти поддержать не может. Наконец, этот модуль может содержать или не содержать отладочные функции, предназначенные для отлад­ки системы.
Каждый из драйверов устройств могут управлять одним или несколькими устройствами ввода-вывода, но драйвер устройства может также выполнять действия, не относящиеся к какому-либо специфическому устройству – шифровать поток данных или даже просто предоставлять доступ к структурам данных ядра. Драйверы устройств не являются частью двоичного файла ntoskrnl.exe. Преимущество такого подхода заключается в том, что как только драйвер устанавливается в систему, он добавляется в реестр и затем динамически загружается при каждой загрузке системы. Таким образом, файл ntoskrnl.exe остается одинаковым для всех конфигураций систем, но каждая система точно настраивается на конфигурацию аппаратуры.
Существуют драйверы для реально видимых и осязаемых устройств ввода-вы­вода, таких как диски и принтеры, но также есть драйверы для многих внутренних устройств и микросхем. Кроме того, как уже было сказано, файловые системы также представлены в виде драй­веров устройств. Самым большим является драйвер устройства для интерфейса Win32 и GDI. Он обрабатывает множество системных вызовов и управляет большей частью графики.
8.1.2. Реализация объектов

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

Ключом к пониманию объектов является тот факт, что исполняемый объект представляет собой просто набор последовательных слов в памяти (то есть в вир­туальном адресном пространстве ядра). Объект представляет собой структуру дан­ных в памяти. Файл на диске не является объектом, хотя для файла при его открытии создается объект (то есть структура данных в вирту­альном адресном пространстве ядра). Из того факта, что объекты представляют собой всего лишь структуры данных в виртуальном адресном пространстве ядра, следует, что при перезагрузке (или сбое) системы все объекты теряются. Дей­ствительно, когда операционная система загружается, нет никаких объектов (кро­ме бездействующих системных процессов, чьи объекты жестко прошиты в файле ntoskrnl.exe). Все остальные объекты создаются на ходу при загрузке системы и во время работы различных программ инициализации, а позднее пользовательских программ.
Каждый объект содержит заголовок с определенной информацией, общей для всех объектов всех типов. Поля заголов­ка включают имя объекта, каталог, в котором объект живет в пространстве объек­тов, информацию защиты (при открытии объекта выполняется определенная про­верка), а также список процессов, у которых есть открытые дескрипторы к данному объекту (если установлен определенный флаг отладки).
Каждый заголовок объекта также содержит поле цены квоты, представляющей собой плату, взимаемую с процесса за открытие объекта. Если файловый объект стоит один пункт, а процесс принадлежит к заданию, у которого есть 10 пунктов квоты, то суммарно все процессы этого задания могут открыть не более 10 фай­лов. Таким образом, для объектов каждого типа могут реализовываться ограниче­ния на ресурсы.
Объекты занимают важный ресурс – участки виртуального адресного про­странства ядра – поэтому, когда объект более не нужен, он должен быть удален, а его адресное пространство возвращено системе. Для этого в заголовке каждого объекта содержится счетчик ссылок на объект. Этот счетчик увеличивается на единицу каждый раз, когда объект открывается, и уменьшается на единицу при закрытии объекта. Когда значение счетчика уменьшается до 0, это означает, что никто более не пользуется этим объектом. Когда объект открывается или освобож­дается компонентом исполняющей системы, используется второй счетчик, даже если настоящий дескриптор при этом не создается. Когда оба счетчика умень­шаются до 0, это означает, что этот объект более не используется ни одним пользо­вателем и ни одним исполняющим процессом, то есть объект может быть удален, а его память освобождена.
Менеджеру объектов бывает необходимо получать доступ к динамическим структурам данных (объектам), но он не единственная часть исполняющей систе­мы, которой это нужно. Другим частям исполняющей системы также бывает нуж­но динамически получать на время участки памяти. Для этого исполняющая сис­тема содержит два пула в адресном пространстве ядра: для объектов и для других динамических структур данных. Один пул является выгружаемым, а другой невыгружаемым (фиксированным в памяти). Объекты, обращения к которым часты, хранятся в невыгружаемом пуле. Объекты, обращения к которым редки, например ключи реестра и некоторая информация, относящаяся к безопасности, хранятся в выгружаемом пуле. Когда памяти не хва­тает, этот пул может быть выгружен на диск и загружен обратно по страничному прерыванию. В действительности значительная часть программ и структур дан­ных операционной системы также является выгружаемой, что позволяет снизить потребление памяти. Объекты, которые могут понадобиться, когда система выпол­няет критический участок программы (и когда подкачка не разрешается), должны храниться в невыгружаемом пуле. Когда требуется небольшое количество памяти, страница может быть получена из любого пула, а затем разбита на мелкие участки размером от 8 байт.
Объекты подразделяются на типы. Это означает, что у каждого объекта есть свойства, общие для всех объектов этого типа. Тип объекта определяется указате­лем на объект типа. Информация о типе объекта включает такие пункты, как название типа, данные о том, может ли поток ждать изменения состояния этого объекта («да» для мьютексов, «нет» для открытых файлов), и должен ли объект этого типа храниться в выгружаемом или невыгружаемом пуле. Каждый объект указывает на свой объект типа.
Наконец, самая важная часть объекта – это указатели на программы для опре­деленных стандартных операций, таких как open, close и delete. Когда вызывается одна из этих операций, используется указатель на типовой объект, в котором выби­рается и выполняется соответствующая процедура. Такой механизм предоставля­ет системе возможность инициализировать новые объекты и освобождать память при их удалении.
Компоненты исполняющей системы могут динамически создавать новые типы. Фиксированного списка типов объектов не существует, но некоторые наиболее употребительные типы рассмотрим ниже. Давайте кратко рассмотрим эти типы объектов. Существует один объект для каждого процесса и для каждого потока. В объекте хранятся основные свойства, необходимые для управления этим процессом или потоком. Следующие три объек­та – семафор, мьютекс и событие – имеют отношение к синхронизации процессов. Семафоры и мьютексы работают так, как и ожидается, но с дополнительными функ­циями (например, максимальными значениями и тайм-аутами). События могут быть в одном из двух состояний: сигнализирующем и несигнализирующем. Если поток ждет события, находящегося в сигнализирующем состоянии, он немедлен­но получает управление. Если же ожидаемое потоком событие находится в несиг­нализирующем состоянии, тогда поток блокируется до тех пор, пока какой-либо другой поток не переведет это событие в сигнализирующее состояние (проще гово­ря, пока это событие не произойдет). Событие может также быть настроено таким образом, что после получения сигнала ожидающим его процессом это событие автоматически перейдет в несигнализирующее состояние. В противном случае событие останется в сигнализирующем состоянии.
Объекты порт, таймер и очередь также имеют отношение к связи и синхрони­зации. Порты представляют собой каналы между процессами, использующиеся для обмена сообщениями. Таймеры предоставляют способ блокировать процесс или поток на определенный срок. Очереди применяются для уведомления пото­ков о том, что начатая ранее асинхронная операция ввода-вывода завершена.
Объекты открытых файлов создаются при открытии файла. У неоткрытых фай­лов нет объектов, управляемых менеджером объектов. Маркеры доступа представ­ляют собой объекты безопасности. Они идентифицируют пользователя и сообща­ют, какие привилегии имеет этот пользователь. Профили представляют собой структуры,используемые для хранения периодически фиксируемых значений счетчика команд работающего потока, которые позволяют определить, на что дан­ная программа тратит свое время.
Секции являются объектами, используемыми системой памяти для управле­ния отображаемыми на память файлами. Они хранят сведения о том, какой файл (или часть файла) на какие адреса памяти отображается. Ключи представляют со­бой ключи реестра и применяются для установки связи между именем и значени­ем.
Каталоги объектов являются полностью локальными по отношению к менед­жеру объектов. Они предоставляют способ объединять связанные объекты тем же способом, каким обычные каталоги объединяют файлы в файловой системе. Символьные ссылки также подобны своим двойникам в файловой системе: они позволяют имени в одной части пространства имен объектов ссылаться на объект в другой части этого пространства имен. У каждого известного системе устрой­ства есть объект устройства, содержащий информацию о нем и использующийся для ссылки на устройство в системе. Наконец, у каждого загруженного драйвера устройства есть объект в пространстве объектов.
Пользователи могут создавать новые объекты или открывать уже существующие объекты при помощи вызовов Win32, таких как CreateSemaphore и OpenSemaphore. Эти вызовы являются библиотечными процедурами, которые в конечном итоге об­ращаются к настоящим системным вызовам. При успешном выполнении первый вы­зов создает, а второй открывает объект, создавая в результате 64-разрядную запись в таблице дескрипторов, хранящуюся в приватной таблице дескрипторов процес­са в памяти ядра. Пользователю для последующей работы возвращается 32-раз­рядный индекс, указывающий положение дескриптора в таблице.
64-разрядный элемент таблицы дескрипторов в ядре содержит два 32-разряд­ных слова. Одно слово содержит 29-разрядный указатель на заголовок объекта. Младшие три разряда используются как флаги (например, указывающие, насле­дуется ли дескриптор дочерним процессом). Когда указатель используется, эти разряды маскируются. Второе слово содержит 32-разрядную маску прав доступа. Она нужна, потому что проверка разрешений выполняется только в то время, когда объект создается или открывается. Если у процесса есть только разрешение для чтения объекта, тогда все остальные биты маски будут нулями, что дает системе возможность отвергать любую операцию, кроме операции чтения.
По мере того как во время выполнения программы создаются и удаляются объек­ты, менеджеру объектов необходимо следить за ними. Для выполнения этой работы он поддерживает пространство имен, в котором располагаются все объекты систе­мы. Пространство имен может использоваться процессом, чтобы найти и открыть дескриптор объекта другого процесса при условии, что для этого у него есть необхо­димые разрешения. Пространство имен объектов является одним из трех пространств имен, поддерживаемых в Windows 2000. Остальные два представляют собой про­странство имен файловой системы и пространство имен реестра. Все три являются иерархическими пространствами имен со множеством уровней каталогов для орга­низации элементов. Объекты каталогов предоставляют средства реализации этого иерархического пространства имен для объектов.
Поскольку объекты исполняющей системы являются временными (то есть ис­чезают при выключении компьютера, в отличие от файловой системы и элемен­тов реестра), в начале загрузки системы в памяти нет объектов и пространство имен объектов пусто. Во время загрузки различные части исполняющей системы создают каталоги и заполняют их объектами. Например, когда менеджер plug-and-play обнаруживает новые устройства, он создает по объекту для каждого устройства и помещает эти объекты в пространство имен. Когда система полностью загружена, все устройства ввода-вывода, дисковые разделы и другие открытия системы оказываются в пространстве имен объектов.
Необходимо отметить, что некоторые компоненты исполняющей системы перед созданием объектов обращаются к реестру, чтобы определить свои дальнейшие действия. Важнейший пример – драйверы устройств. При загрузке система смотрит в реестр, чтобы узнать, какие драйверы ей нужны. При загрузке каждого драйвера создается объект, а его имя добавляется в про­странство имен объектов. В системе обращение к драйверу осуществляется по ука­зателю на его объект.
8.1.3. Подсистемы окружения
Итак, операционная система Windows 2000 состоит из компонентов, работающих в режиме ядра, и компонентов, работающих в режиме пользователя. Выше были рассмотрены компоненты, работающие в режиме ядра. Теперь перейдем к рассмотрению компонентов, работающих в ре­жиме пользователя.
Существует три типа таких компонентов: динамические биб­лиотеки DLL (Dynamic Link Library – динамически подключаемая библиотека), подсистемы окружения и служебные процессы. Эти компоненты работают вместе, предоставляя каждому пользовательскому процессу интерфейс, отличный от интерфейса системных вызовов Windows 2000.
Операционной системой Windows 2000 поддерживаются три различных доку­ментированных интерфейса прикладного программирования API: Win32, POSIX и OS/2. У каждого из этих интерфейсов есть список библиотечных вызовов, ко­торые могут использовать программисты. Работа библиотек DLL и подсистем окружения за­ключается в том, чтобы реализовать функциональные возможности опубликован­ного интерфейса, тем самым скрывая истинный интерфейс системных вызовов от прикладных программ. В частности, интерфейс Win32 является официальным интерфейсом для операционных систем Windows 2000, Windows NT, Windows 95/ 98/Me. При использовании библиотеки DLL и подсистемы окружения Win32 программа может быть написана в соот­ветствии со спецификацией Win32, в результате чего она сможет без каких-либо изменений работать на всех этих версиях Windows, несмотря на то, что сами сис­темные вызовы в различных системах различны.
Рассмотрим способ реализации этих интерфейсов на примере Win32. Програм­ма, пользующаяся интерфейсом Win32, как правило, состоит из большого коли­чества обращений к функциям Win32 API, например CreateWindow, DrawMenuBar и OpenSemaphore. Существуют тысячи подобных вызовов, и большинство программ использует значительное их количество. Один из возможных способов реализа­ции заключается в статическом связывании каждой программы, использующей интерфейс Win32, со всеми библио-течными процедурами, которыми она пользу­ется. При таком подходе каждая двоичная программа будет содержать копию всех используемых ею процедур в своем исполняемом двоичном файле. Недостаток такого подхода заключается в том, что при этом расходуется много памяти, если пользователь одновременно откроет несколько программ, использу­ющих одни и те же библиотечные процедуры. Например, программы Word, Excel и Powerpoint используют абсолютно одинаковые процедуры для открытия диалого­вых окон, рисования окон, отображения меню, работы с буфером обмена и т. д. Поэтому, если одновременно открыть все эти программы, при такой реализации программ в памяти будут находиться три идентичные копии каждой библиотеч­ной процедуры. Чтобы избежать подобной проблемы, все версии Windows поддерживают ди­намические библиотеки DLL. Каждая динамическая библиотека содержит набор тесно связанных библиотечных процедур и все их структуры данных в одном фай­ле, как правило (но не всегда), с расширением dll. Когда приложение компонуется, компоновщик видит, что некоторые библиотечные процедуры принадлежат к ди­намическим библиотекам, и записывает эту информацию в заголовок исполняе­мого файла. Обращения к процедурам динамических библиотек производятся не напрямую, а при помощи вектора передачи в адресном пространстве вызывающе­го процесса. Изначально этот вектор заполнен нулями, так как адреса вызываемых процедур еще неизвестны.
При запуске прикладного процесса все требуемые динамические библиотеки обнаруживаются (на диске или в памяти) и отображаются на виртуальное адрес­ное пространство процесса. Затем вектор передачи заполняется верными адреса­ми, что позволяет вызывать библиотечные процедуры через этот вектор с незна­чительной потерей производительности. Выигрыш такой схемы заключается в том, что при запуске нескольких приложений, использующих одну и ту же динамичес­кую библиотеку, в физической памяти требуется только одна копия текста DLL (но каждый процесс получает свою собственную копию приватных статических данных в DLL). В операционной системе Windows 2000 динамические библиоте­ки используются очень активно для всех аспектов системы.
Каждый пользовательский процесс, как правило, связан с несколькими динамическими библиотеками, совместно реа­лизующими интерфейс Win32. Чтобы обратиться к вызову API, вызывается одна из процедур в DLL. Дальнейшие действия зависят от вызова Win32 API. Различные вызовы реализованы по-разному. В некоторых случаях динамические библиотеки обращаются к другой динами­ческой библиотеке, которая, в свою очередь, обращается к ядру операци­онной системы. Динамическая биб­лиотека может также выполнить всю работу самостоятельно, совсем не обращаясь к системным вызовам. Для других вызовов Win32 API выбирается другой маршрут, а именно: сначала процессу подсистемы Win32 посылается сообщение, выполняющее некоторую работу и обращающееся к системному вызову. При этом в некоторых случаях подсистема также выполняет всю работу в пространстве пользователя и немедленно возвращает управление.
Следует также отметить, что DLL не являются единственными динамическими библиотеками в системе. В каталоге \winnt\system32 есть более 800 отдельных файлов DLL общим объемом в 130 Мбайт. Количество содержащихся в них вызовов API превышает 13 000.
Хотя интерфейс процессов Win32 является наиболее важным, в операционной системе Windows 2000 существует еще два интерфейса: POSIX и OS/2. Среда POSIX предоставляет минимальную поддержку для приложений UNIX. Этим интерфейсом, например, не поддерживаются потоки, работа с окнами или сетью. Перенос любой реальной программы из системы UNIX в Windows 2000 при помо­щи этой подсистемы практически невозможен. Эта подсистема не является самодостаточной и пользуется вызовами подсистемы Win32 для большей части своей работы, но не предостав­ляя пользовательским программам полного интерфейса Win32. Функциональность подсистемы OS/2 ограничена практически в той же степе­ни, что и функциональность подсистемы POSIX.

8.2. Процессы и потоки в Windows 2000

8.2.1. Основные понятия

В операционной системе Windows 2000 поддерживаются традиционные процес­сы, способные общаться и синхронизироваться друг с другом так же, как это дела­ют процессы в UNIX. Каждый процесс содержит по крайней мере один поток, со­держащий, в свою очередь, как минимум одно волокно (облегченный поток). Более того, для управления определенными ресурсами процессы могут объединяться в задания. Все вместе – задания, процессы, потоки и волокна – образует общий набор инструментов для управления ресурсами и реализации параллелизма как на однопроцессорных, так и на многопроцессорных машинах.
Задание в Windows 2000 представляет собой набор, состоящий из одного или нескольких процессов, управляемых как единое целое. В частности, с каждым заданием ассоциированы квоты и лимиты ресурсов, хранящиеся в соот­ветствующем объекте задания. Квоты включают такие пункты, как максимальное количество процессов (не позволяющее процессам задания создавать бесконтроль­ное количество дочерних процессов), суммарное время центрального процессора, доступное для каждого процесса в отдельности и для всех процессов вместе, а также максимальное количество используемой памяти для процесса и для всего задания. Задания также могут ограничивать свои процессы в вопросах безопасности, на­пример, запрещать им получать права администратора (суперпользователя) даже при наличии правильного пароля.
Как и в системе UNIX, процессы представляют собой контейнеры для ресур­сов. У каждого процесса есть 4-гигабайтное адресное пространство, в котором пользователь занимает нижние 2 Гбайт (в версиях Windows 2000 Advanced Server и Datacenter Server этот размер может быть по желанию увеличен до 3 Гбайт), а операционная система занимает остальную его часть. Таким образом, операци­онная система присутствует в адресном пространстве каждого процесса, хотя она и защищена от изменений с помощью аппаратного блока управления памятью MMU. У процесса есть идентификатор процесса, один или несколько потоков, список дескрипторов (управляемых в режиме ядра) и маркер доступа, хранящий информацию защиты. Процессы создаются с помощью вызова Win32, который принимает на входе имя исполняемого файла, определяющего начальное содер­жимое адресного пространства, и создает первый поток.
Каждый процесс начинается с одного потока, но новые потоки могут создавать­ся динамически. Потоки формируют основу планирования центрального процес­сора, так как операционная система всегда для запуска выбирает поток, а не про­цесс. Соответственно, у каждого потока есть состояние (готовый, работающий, блокированный и т. д.), тогда как у процессов состояний нет. Потоки могут дина­мически создаваться вызовом Win32, которому в адресном пространстве процесса задается адрес начала исполнения. У каждого потока есть идентификатор потока, выбираемый из того же пространства, что и идентификаторы процессов, поэтому один и тот же идентификатор никогда не будет использован одновременно для процесса и для потока. Идентификаторы процессов и потоков кратны четырем, поэтому они могут использоваться в роли байтовых индексов в таблицах ядра, как и другие объекты.
Как правило, поток работает в пользовательском режиме, но когда он обраща­ется к системному вызову, то переключается в режим ядра, после чего продолжает выполнять тот же поток, с теми же свойствами и ограничениями, которые были у него в режиме пользователя. У каждого потока есть два стека – один используется в режиме ядра, а другой в режиме пользователя. Помимо состояния, идентифика­тора и двух стеков, у каждого потока есть контекст (в котором сохраняются его регистры, когда он не работает), приватная область для локальных переменных, а также может быть свой собственный маркер доступа. Если у потока есть свой мар­кер доступа, то он перекрывает маркер доступа процесса, чтобы клиентские потоки могли передать свои права доступа серверным потокам, выполняющим работу для них. Когда поток завершает свою работу, он может прекратить свое существование. Когда прекращает существование последний активный поток, процесс завершается.
Важно понимать, что потоки представляют собой концепцию планирования, а не концепцию владения ресурсами. Любой поток может получить доступ ко всем объектам его процесса. Все, что ему для этого нужно сделать, – это заполучить дес­криптор и обратиться к соответствующему вызову Win32. Для потока нет ника­ких ограничений доступа к объекту, связанных с тем, что этот объект создан или открыт другим потоком. Система даже не следит за тем, какой объект каким потоком создан. Как только дескриптор объекта помещен в таблицу дескрипторов про­цесса, любой поток процесса может его использовать.
Помимо нормальных потоков, работающих в процессах пользователя, в опера­ционной системе Windows 2000 есть множество процессов-демонов, не связанных ни с каким пользовательским процессом (они ассоциированы со специальной си­стемой или простаивающими процессами). Некоторые демоны выполняют адми­нистративные задачи, как, например, запись «грязных» (модифицированных) страниц на диск, тогда как другие формируют пул, и ими могут пользоваться компоненты исполняющей системы или драйверы, которым нужно выполнить какие-либо асинхронные за­дачи в фоновом режиме. Переключение потоков в операционной системе Windows 2000 занимает до­вольно много времени, так как для этого необходимо переключение в режим ядра, а затем возврат в режим пользователя. Для предоставления сильно облегченного псевдопараллелизма в Windows 2000 используются волокна, подобные потокам, но планируемые в пространстве пользователя создавшей их программой (или ее системой поддержки исполнения). У каждого потока может быть несколько воло­кон, так же как у процесса может быть несколько потоков, с той разницей, что когда волокно логически блокируется, оно помещается в очередь блокированных волокон, после чего для работы выбирается другое волокно в контексте того же потока. Операционная система не знает о смене волокон, так как все тот же поток продолжает работу. Так как операционная система ничего не знает о волокнах, то с ними, в отличие от заданий, процессов и потоков, не связаны объекты испол­няющей системы. Для управления волокнами нет и настоящих системных вызо­вов. Однако для этого есть вызовы Win32 API. Они относятся к тем вызовам Win32 API, которые не обращаются к системным вызовам.
Отметим, что операци­онная система Windows 2000 может работать на симметричных многопроцессор­ных системах. Это означает, что код операционной системы должен быть полнос­тью реентерабельным, то есть каждая процедура должна быть написана таким образом, чтобы два или более центральных процессора могли поменять свои пере­менные без особых проблем. Во многих случаях это означает, что программные секции должны быть защищены при помощи спин-блокировки или мьютексов, удерживающих дополнительные центральные процессоры в режиме ожидания, пока первый центральный процессор не выполнит свою работу (при помощи по­следовательного доступа к критическим областям).
Верхний предел в 32 центральных процессора является жестким пределом, так как во многих местах операционной системы для учета использования централь­ных процессоров используются битовые массивы размером в 32-разрядное машин­ное слово. Например, один однословный битовый массив используется для того, чтобы следить, какой из центральных процессоров свободен в данный момент, а другой массив используется в каждом процессе для перечисления центральных процессоров, на которых этому процессу разрешено работать. 64-разрядная версия Windows 2000 должна будет без особых усилий поддерживать до 64 центральных процессоров. Для превышения этого ограничения потребуется существенная пере­делка программы (с использованием по нескольку слов для битовых массивов).

8.2.2. Межпроцессное взаимодействие

Для общения друг с другом потоки могут использовать широкий спектр возмож­ностей, включая каналы, именованные каналы, почтовые ящики, вызов удаленной процедуры и совместно используемые файлы. Каналы могут работать в одном из двух режимов, выбираемом при создании канала: байтовом и режиме сообщений. Байтовые каналы работают так же, как и в системе UNIX. Каналы сообщений в чем-то похожи на байтовые каналы, но сохраняют границы между сообщениями, так что четыре записи по 128 байт будут читаться с другой стороны канала как четыре сообщения по 128 байт, а не как одно 512-байтовое сообщение, как это может слу­читься с байтовыми каналами. Также имеются именованные каналы, для которых существуют те же два режима. Именованные каналы, в отличие от обычных кана­лов, могут использоваться по сети.
Почтовые ящики представляют собой особенность системы Windows 2000, которой нет в UNIX. В некоторых аспектах они подобны каналам, но не во всем. Во-первых, почтовые ящики являются однонаправленными, тогда как кана­лы могут работать в обоих направлениях. Они также могут использоваться по сети, но не предоставляют гарантированной доставки. Наконец, они позволяют отправляющему процессу использовать широковещание для рассылки сообщения не одному, а сразу многим получателям.
Сокеты подобны каналам с тем отличием, что они при нормальном использо­вании соединяют процессы на разных машинах. Например, один процесс пишет в сокет, а другой процесс на удаленной машине читает из него. Сокеты также могут использоваться для соединения процессов на одной машине, но поскольку их ис­пользование влечет за собой большие накладные расходы, чем использование ка­налов, то, как правило, они применяются в контексте сети.
Вызов удаленной процедуры представляет собой тот способ, которым процесс А просит процесс В вызвать процедуру в адресном пространстве процесса В от име­ни процесса А и вернуть результат процессу А. Существуют различные ограниче­ния на параметры. Например, нет смысла передавать указатель другому процессу.
Наконец, процессы могут совместно использовать память для одновременного отображения одного и того же файла. Все, что один процесс будет писать в этот файл, будет появляться в адресном пространстве других процессов. С помощью такого механизма можно легко реализовать общий буфер, применяемый в задаче производителя и потребителя.
Помимо многочисленных механизмов межпроцессного взаимодействия, опера­ционная система Windows 2000 также предоставляет множество механизмов син­хронизации, включая семафоры, мьютексы, критические области и события. Все эти механизмы работают с потоками, а не процессами, так что когда поток блоки­руется на семафоре, другие потоки этого процесса (если такие есть) не затрагива­ются и могут продолжать работу.
Семафор создается при помощи API-функции CreateSemaphore, которая может задать для него начальное значение, а также установить максимальное значение. Семафоры представляют собой объекты в ядре и, таким образом, обладают деск­рипторами или дескрипторами защиты. Копия дескриптора может быть получена с помощью функции DuplicateHandle и передана другому процессу, в результате чего несколько процессов могут синхронизироваться, используя один семафор.
Мьютексы также представляют собой объекты ядра, используемые для синх­ронизации, но они проще семафоров, так как не содержат счетчиков. По существу, они являются блокировками, для работы с которыми используются функции API WaitForSingleObject и ReleaseMutex. Как и дескрипторы семафоров, дескрипторы мьютексов можно скопировать и передать другому процессу, так что потоки раз­личных процессов смогут иметь доступ к одному и тому же мьютексу.
Третий механизм синхронизации основан на критических секциях (или критических областях). Критические секции подобны мьютексам, но отличаются тем, что они связаны с адресным про­странством создавшего их потока. Поскольку критические секции не являются объектами ядра, у них нет дескрипторов или дескрипторов защиты и они не могут передаваться от одного процесса другому. Блокирование и разблокирование выполняется функциями EnterCrlt leal Section и LeaveCritical Sect Ion соответствен­но. Поскольку эти функции API в основном выполняются в пространстве пользо­вателя и обращаются к системным вызовам в ядро, только когда требуется блоки­рование потока, они работают быстрее, чем мьютексы.
В последнем механизме синхронизации используются объекты ядра, называе­мые событиями, которые бывают двух видов: сбрасываемые вручную и сбрасыва­емые автоматически. Каждое событие может находиться в одном из двух состоя­ний: установленном и сброшенном. Поток может ждать какого-либо события с помощью функции WaitForSingleObject. Если другой поток вызывает событие при помощи функции SetEvent, результат зависит от типа события. Если событие яв­ляется сбрасываемым вручную, то все ждущие его потоки отпускаются, а событие остается в установленном состоянии, пока его кто-либо не сбросит при помощи функции ResetEvent. В случае сбрасываемого автоматически события отпускается только один ожидающий его поток, а событие тут же сбрасывается. Кроме функ­ции SetEvent существует также функция PulseEvent, отличающаяся от первой функ­ции тем, что если этого события никто не ждет, событие все равно само сбрасыва­ется и, таким образом, пропадает впустую. При использовании функции SetEvent событие, которого никто не ждет, напротив, остается в установленном состоянии, так что как только какой-либо поток обратится к функции WaitForSingleObject, он будет тут же отпущен, после чего событие сбросится.
События, мьютексы и семафоры могут иметь имена и храниться в файловой системе, подобно именованным каналам. Несколько процессов могут синхрони­зироваться друг с другом, открывая одно и то же событие, мьютекс или семафор, что проще, чем создание такого объекта одним процессом и передача другим про­цессам дубликата дескриптора, хотя такой способ, конечно, также возможен.
Интерфейс Win32 API содержит около 100 вызовов, работающих с процесса­ми, потоками и волокнами. Значительное количество этих вызовов в той или иной мере имеет отношение к межпроцессному взаимодействию.
8.2.3. Реализация процессов и потоков

Процессы и потоки имеют большее значение и являются более сложными, чем за­дания и волокна. Процесс со­здается другим процессом при помощи вызова интерфейса Win32 CreateProcess. Этот вызов обращается (в режиме пользователя) к процедуре в динамической биб­лиотеке kernel32.dll, которая в несколько этапов создает процесс, используя при этом множество системных вызовов и других действий.
Создание потока также состоит из нескольких этапов. Сначала работающий процесс обращается к функции CreateThread, которая вызывает процедуру внутри kernel32.dll. Эта процедура вы­деляет в вызывающем процессе память для стека режима пользователя, а затем обращается к системному вызову NtCreateThread, чтобы создать объект потока для исполняющей системы, проинициализировать его, а также создать и проинициализировать блок управления потоком. Затем поток начинает работу с собственной инициализации.
Когда создается процесс или поток, исходному процессу возвращается дескрип­тор, который можно использовать для запуска, остановки, уничтожения и провер­ки созданного процесса или потока. Владелец дескриптора может передать его дру­гому процессу защищенным способом. Эта техника применяется, чтобы отладчики могли иметь полный контроль над управляемыми ими процессами.
В операционной системе Windows 2000 нет центрального потока планирования. Вместо этого, когда какой-либо поток не может более выполняться, этот поток сам переходит в режим ядра и запускает планировщика, чтобы определить, на какой поток переключиться.
Текущий поток выполняет программу планировщика при одном из следующих условий:
1) поток блокируется на семафоре, мьютексе, событии, операции ввода-выво­да и т. д;
2) поток сигнализирует каким-либо объектом (например, выполняет операцию up на семафоре);
3) истекает квант времени работающего потока.
В случае 1 поток уже работает в режиме ядра, чтобы выполнить операцию с объектом диспетчера или ввода-вывода. Возможно, он не может продолжать работу, поэтому он должен сохранить свой контекст, запустить программу плани­ровщика, чтобы выбрать своего преемника, и загрузить контекст этого потока, что­бы запустить его.
В случае 2 поток также находится в ядре. Однако после сигнализирования объектом он, определенно, может продолжать работу, так как эта операция никог­да не приводит к блокированию. Тем не менее поток должен запустить процедуру планировщика, чтобы посмотреть, нет ли среди готовых к работе потока с более высоким приоритетом. Если такой поток есть, происходит переключение на этот поток, так как операционная система Windows 2000 является системой с при­оритетным прерыванием (то есть переключение потока может произойти в любой момент, а не только тогда, когда у текущего потока закончится выделенный ему квант времени).
В случае 3 происходит эмулированное прерывание с передачей управления в ядро. При этом поток также запускает процедуру планировщика, чтобы опреде­лить, какой поток следует запустить после текущего потока. Если все остальные потоки в данный момент окажутся заблокированными, планировщик может про­должить выполнение текущего потока, выделив ему новый квант времени. В про­тивном случае происходит переключение потока.
Планировщик также вызывается при еще двух условиях:
1) завершается операция ввода-вывода;
2) истекает ожидание таймера.
В первом случае какой-нибудь поток, возможно, ожидал окончания этой опе­рации ввода-вывода и теперь может продолжить свою работу. Необходимо опре­делить, должен ли этот поток прервать выполнение текущего потока, так как по­токам не гарантируется минимальный рабочий интервал времени. Планировщик не запускается во время работы самой процедуры обработки прерываний (так как при этом прерывания могут оказаться запрещенными на слишком долгий срок). Вместо этого отложенный вызов процедуры устанавливается в очередь и вы­полняется немного позднее, после того как процедура обработки прерываний закончит свою работу. Во втором случае поток выполнил операцию down на сема­форе или блокировался на каком-либо другом объекте, но установленное время ожидания истекло. И в этом случае обработчик прерываний должен установить процедуру в очередь, чтобы она не была запущена во время работы обработчика преры­ваний. Если в результате тайм-аута поток оказался готовым к работе, будет запу­щен планировщик, и если ничего более важного в данный момент нет, будет вы­полнен отложенный вызов процедуры.
Теперь рассмотрим сам алгоритм планирования. Интерфейс Win32 API содержит два вызова, предоставляющих процессам возможность влиять на пла­нирование потоками. Алгоритм планирования в значительной степени определяется этими вызовами.
Во-первых, есть вызов SetPriorltyClass, устанавливающий класс приоритета всех потоков вызывающего процесса. К допустимым значениям при­оритета относятся: при­оритет реального времени, высокий при­оритет, при­оритет выше нормы, нормальный при­оритет, при­оритет ниже нормы и неработающий при­оритет.
Во-вторых, имеется вызов SetThreadPrlorlty, устанавливающий относительный приоритет некоторого потока (возможно, но не обязательно, потока, обращающе­гося к этому вызову) по сравнению с другими потоками данного процесса. При­оритет может иметь следующие значения: критичный ко времени при­оритет, самый высокий при­оритет, при­оритет выше нормы, нормальный при­оритет, при­оритет ниже нормы, самый низкий при­оритет и неработающий при­оритет. Таким образом, шесть классов процессов и семь классов потоков могут образовать 42 ком­бинации. Эта информация поступает на вход алгоритма планирования.
Планировщик работает следующим образом. В системе существует 32 уровня приоритета, пронумерованные от 0 до 31. 42 комбинации отображаются на эти 32 приоритета, определяя базовый приоритет потока. Кроме того, у каждого потока есть текущий приоритет, кото­рый может быть выше (но не ниже) базового приоритета.
Чтобы использовать эти приоритеты для планирования, система содержит мас­сив из 32 элементов, соответствующих приоритетам от 0 до 31. Каждый элемент массива указывает на начало списка готовых пото­ков с соответствующим приоритетом. Базовый алгоритм планирования состоит из процедуры сканирования массива от приоритета 31 до приоритета 0. Как только найден непустой элемент, выбирается поток в начале очереди и запускается на один квант времени. Когда квант истекает, поток направляется в конец очереди своего приоритета, а следующим выбирается поток в начале очереди. Другими сло­вами, когда есть несколько готовых потоков с наивысшим уровнем приоритета, они запускаются поочередно, получая каждый по одному кванту времени. Если гото­вых потоков нет, запускается бездействующий поток.
Следует отметить, что при планировании не учитывается, какому процессу принадлежит тот или иной поток. То есть планировщик не выбирает сначала про­цесс, а затем поток в этом процессе. Он смотрит только на потоки. Он даже не зна­ет, какой поток какому процессу принадлежит. На многопроцессорной системе каждый центральный процессор сам занимается планированием своих потоков при помощи массива приоритетов. Чтобы гарантировать, что в каждый момент времени лишь один центральный процессор работает с массивом, используется спин-блокировка.
В системе Windows 2000 Professional длительность кванта по умолчанию равна 20 мс; на однопроцессорных серверах его значение равно 120 мс; на многопроцессорных системах используют­ся различные другие варианты в зависимости от частоты процессора. Более ко­роткий квант улучшает работу интерактивных процессов, тогда как более длин­ный квант снижает количество переключений контекста и тем самым увеличивает производительность. Значения по умолчанию при желании могут быть увеличены в 2, 4 или 6 раз.
Последняя модификация алгоритма планирования заключается в том, что когда окно становится окном переднего плана, все его потоки получают более длительные кванты времени. Величина прибавки интервала времени хранится в системном реестре. Таким образом, поток получает больше процессорного вре­мени, и, соответственно, достигается лучшее обслуживание для окна, перемещен­ного на передний план.
8.2.4. Загрузка Windows 2000

Прежде чем операционная система Windows 2000 сможет начать работу, она долж­на загрузиться. Процесс загрузки создает начальные процессы. С точки зрения аппаратного обеспечения, процесс загрузки состоит из чтения первого сек­тора первого диска (главной загрузочной записи), после чего управление передается прочитанной програм­ме. Эта короткая программа на ассемблере считывает таблицу разделов, что­бы определить, в каком разделе содержится загружаемая операционная система. Найдя раздел с операционной системой, начальный загрузчик считывает первый сектор этого раздела, называемый загрузочным сектором, и передает управление ему. Программа, содержащаяся в загрузочном секторе, считывает корневой ката­лог своего дискового раздела, находит в нем файл ntldr. Если этот файл удается найти, он загружается в память и ему передается управление. Программа ntldr загружает операционную систему Windows 2000. Существует не­сколько версий загрузочного сектора в зависимости от формата раздела (FAT-16, FAT-32 или NTFS). При установке Windows 2000 на диск записываются соответ­ствующие версии главной загрузочной записи и загрузочного сектора.
Затем программа ntldr считывает файл Boot.ini, представляющий собой един­ственный файл с информацией о конфигурации, не содержащейся в реестре. Он хранит в себе списки всех версий файлов hal.dll и ntoskernl.exe, которые могут быть загружены с данного раздела диска. В этом файле также содержатся такие пара­метры, как количество центральных процессоров и оперативной памяти, сколько памяти отводить процессу пользователя (2 или 3 Гбайт), а также на какой часто­те работают часы реального времени. Затем программа ntldr выбирает и загру­жает файлы hal.dll и ntoskernl.exe, а также файл bootvid.dll, представляющий собой видеодрайвер по умолчанию. Он обеспечивает вывод на дисплей во время процес­са загрузки. После этого программа ntldr считывает реестр, чтобы найти драйверы, необходимые для завершения загрузки (например, драйверы клавиатуры и мыши, а также десятки других драйверов, требуемых для управления различными микро­схемами на материнской плате). Наконец, загрузчик считывает все эти драйверы и передает управление программе ntoskernl.exe.
После запуска операционная система выполняет некоторые общие процедуры инициализации, а затем вызывает компоненты исполняющей системы, чтобы те также выполнили собственную инициализацию. Например, менеджер объектов подготавливает свое пространство имен, чтобы другие компоненты могли обра­щаться к нему и добавлять свои объекты в пространство имен. Многие компонен­ты также выполняют определенные действия, относящиеся к их функциям. В частности, менеджер памяти настраивает начальные таблицы страниц, а менеджер plug-and-play определяет, какие устройства ввода-вывода присутствуют, и загружает их драйверы. Вся загрузка состоит из десятков этапов, в течение которых на экране отображается полоса прогресса, растущая по мере выполнения очередных этапов. Последний этап заключается в создании первого настоящего пользовательского процесса – сеансового менеджера smss.exe. Как только этот процесс начинает рабо­ту, загрузка считается законченной.
Сеансовый менеджер представляет собой «родной» процесс операционной системы Windows 2000. Он обращается к истинным системным вызовам и не пользуется вызовами подсистемы окружения Win32, которая в тот момент еще даже не работает. Одной из его первоочередных обязанностей является запуск этой подсистемы (csrss.exe). Он также считывает с диска ульи реестра и узнает из них, что еще он должен сделать. Как правило, его работа заключается в помещении мно­жества объектов в пространство имен менеджера объектов, создании дополнитель­ных файлов подкачки и открытии нужных DLL. Завершив свою работу, сеансо­вый менеджер создает демон регистрации winlogon.exe.
В этот момент операционная система загружена и работает. Теперь пора запус­тить служебные процессы (демоны в пространстве пользователя) и позволить пользователям регистрироваться в системе. Сначала winlogon.exe создает менед­жера аутентификации (lsass.exe), а затем запускает родительский процесс всех слу­жебных процессов (sennces.exe). Последний процесс по информации, хранящейся в реестре, определяет, какие демоны в пространстве пользователя нужно запустить и в каких файлах они находятся. После этого он приступает к их созданию. Как правило, уже после того, как первый пользова­тель зарегистрировался в системе, но еще до того, как он успел в ней что-либо сделать, в операционной системе Windows 2000 наблюдается высокая активность с большим количеством обращений к диску. Это программа services.exe создает системные службы. Кроме того, она загружает все оставшиеся (еще не загружен­ные) драйверы устройств.
Программа winlogon.exe также отвечает за регистрацию всех пользователей в системе. После успешного входа пользователя в систему про­грамма winlogon.exe получает из реестра профиль пользователя и определяет по нему, какую оболочку запустить. Стан­дартный рабочий стол Windows представляет собой программу explorer.exe, у которой настроены некоторые параметры. При желании пользо­ватель может выбрать в качестве оболочки другую программу, для чего ему нужно просто отредактировать реестр. Отметим, что редактирование реестра требует особой внимательности и осторожности, а всего одна допущенная ошибка может сделать систему незагружаемой.

8.3. Управление памятью в Windows 2000

8.3.1. Основные понятия

В операционной системе Windows 2000 у каждого пользовательского процесса есть собственное виртуальное адресное пространство. Виртуальные адреса 32-разряд­ные, поэтому у каждого процесса 4 Гбайт виртуального адресного пространства. Нижние 2 Гбайт за вычетом около 256 Мбайт доступны для программы и данных процесса; верхние 2 Гбайт защищенным образом отображаются на память ядра. Страницы виртуального адресного пространства имеют фиксированный размер (4 Кбайт на компьютере с процессором Pentium) и подгружаются по требованию. Нижние и верхние 64 Кбайт каждого виртуального адресного пространства в обычном состоянии не отображаются на физическую память. Это делается преднамеренно, что­бы облегчить перехват программных ошибок. Недействительные указатели часто имеют значение 0 или -1, и попытки их использования в системе Windows 2000 вызовут немедленное прерывание вместо чтения или, что еще хуже, записи слова по неверному адресу. Однако когда запускаются старые программы MS-DOS в ре­жиме эмуляции, нижние 64 Кбайт могут отображаться на физическую память.
Начиная с адреса 64 К, могут располагаться приватные данные и программа пользователя. Они могут занимать почти 2 Гбайт. Последний фрагмент этих 2 Гбайт памяти содержит некоторые системные указатели и таймеры, используемые со­вместно всеми пользователями в режиме доступа «только чтение». Отображение этих данных в эту область памяти позволяет всем процессам получать к ним дос­туп без лишних системных вызовов.
Верхние 2 Гбайт виртуального адресного пространства содержат операционную систему, включая код, данные и выгружаемый и невыгружаемый пулы (использу­емые для объектов и т. д.). Верхние 2 Гбайт используются совместно всеми про­цессами, кроме таблиц страниц, которые являются индивидуальными для каждо­го процесса. Верхние 2 Гбайт процессам в режиме пользователя запрещены для записи, а по большей части также запрещены и для чтения. Причина, по которой они размещаются здесь, заключается в том, что когда поток обращается к систем­ному вызову, он переключается в режим ядра, но остается все тем же потоком. Если сделать всю операционную систему и все ее структуры данных (как и весь пользо­вательский процесс) видимыми в адресном пространстве потока, когда он переклю­чается в режим ядра, то отпадает необходимость в изменении карты памяти или выгрузке кэша при входе в ядро. Все, что нужно сделать, – это переключиться на стек режима ядра. Платой за более быстрые системные вызовы при данном подходе является уменьшение приватного адресного пространства для каждого процес­са. Большим базам данных уже сейчас становится тесно в таких рамках, вот поче­му в версиях Windows 2000 Advanced server и Datacenter Server есть возможность использования 3 Гбайт для адресного пространства пользовательских процессов.
Каждая виртуальная страница может находиться в одном из трех состояний: свободном, зарезервированном и фиксированном. Свободная страница не исполь­зуется в настоящий момент, и ссылка на нее вызывает страничное прерывание. Когда процесс запускается, все его страницы находятся в свободном состоянии, пока программа и исходные данные не будут отображены на их адресное простран­ство. Как только данные или программа отображаются на страницу, страница на­зывается фиксированной. Обращение к фиксированной странице преобразуется при помощи аппаратного обеспечения виртуальной памяти и завершается успехом, если эта страница находится в оперативной памяти. В противном случае происхо­дит страничное прерывание, операционная система находит требуемую страницу на диске и считывает ее в оперативную память.
Виртуальная страница может также находиться в зарезервированном состоя­нии, в таком случае эта страница не может отображаться, пока резервирование не будет явно удалено. Например, когда создается новый поток, в виртуальном ад­ресном пространстве резервируется 1 Мбайт пространства для стека, но фиксиру­ется только одна страница. Такая техника означает, что стек может вырасти до 1 Мбайт без опасения, что какой-либо другой поток захватит часть необходимого непрерывного виртуального адресного пространства. Помимо состояния (свободная, зарезервированная или фиксированная), у страниц есть также и другие атрибуты, например страница может быть доступной для чтения, записи или исполнения.
При выделении фиксированным страницам места резервного хранения исполь­зуется интересный компромисс. Простая стратегия вданном случае состояла бы в отведении для каждой фиксированной страницы одной страницы в файле подкач­ки во время фиксации страницы. Это означало бы, что всегда есть место, куда за­писать каждую фиксированную страницу, если потребуется удалить ее из памяти. Недостаток такой стратегии заключается в том, что при этом может потребовать­ся файл подкачки размером со всю виртуальную память всех процессов. На боль­шой системе, которой редко требуется выгрузка виртуальной памяти на диск, та­кой подход приведет к излишнему расходованию дискового пространства. Чтобы не тратить пространство на диске понапрасну, в Windows 2000 фиксиро­ванным страницам, у которых нет естественного места хранения на диске (напри­мер, страницам стека), не выделяются страницы на диске до тех пор, пока не настанет необходимость их выгрузки на диск. Такая схема усложняет систему, так как во время обработки страничного прерывания может понадобиться обращение к фай­лам, в которых хранится информация о соответствии страниц, а чтение этих файлов может вызвать дополнительные страничные прерывания. С другой стороны, для страниц, которые никогда не выгружаются, пространства на диске не требуется.
Подобный выбор (усложнение системы или увеличение производительности и дополнительные функции), как правило, разрешается в пользу последнего, так как достоинства лучшей производительности и большего числа функций очевид­ны, тогда как недостатки усложнения системы (сложность поддержки и увеличе­ние частоты сбоев) бывает сложно учесть.
У свободных и зарезервированных страниц никогда не бывает теневых страниц на диске и обращение к ним всегда приво­дит к страничным прерываниям. Теневые страницы на диске организованы в один или несколько файлов под­качки. Может быть организовано до 16 файлов подкачки, для повышения произ­водительности операций ввода-вывода они могут быть распределены по отдель­ным дискам, которых также может быть до 16. У каждого файла есть начальный размер и максимальный размер, до которого он может вырасти при необходимос­ти. Эти файлы могут сразу быть созданы максимального размера во время уста­новки системы, чтобы уменьшить вероятность их сильной фрагментации, но с по­мощью панели управления позднее можно создать новые файлы. Операционная система следит за тем, какие виртуальные страницы на какую часть файла подкач­ки отображаются. Страницы, содержащие исполняемый текст программ, не дуб­лируются в файлах подкачки. В файлах подкачки хранятся только изменяемые страницы.
В Windows 2000, как и во многих версиях UNIX, файлы могут отображаться напрямую на области виртуального адресного пространства (то есть занимать мно­жество соседних страниц). После того, как файл отображен на адресное простран­ство, он может читаться и писаться при помощи обычных команд обращения к памяти. Отображаемые на память файлы реализуются тем же способом, что и фик­сированные страницы, но теневые страницы хранятся не в файле подкачки, а в файле пользователя. Поэтому при отображении файла на память версия файла, находящаяся в памяти, может отличаться от дисковой версии (вследствие записи в виртуальное адресное пространство). Однако когда отображение файла прекра­щается или файл принудительно выгружается на диск, дисковая версия снова при­водится в соответствие с последними изменениями файла в памяти.
В Windows 2000 два и более процессов могут одновременно отображать на свои виртуальные адресные пространства, возможно, в различные адреса, одну и ту же часть одного и того же файла. Читая и записывая сло­ва памяти, процессы могут общаться друг с другом и передавать друг другу ин­формацию с очень большой скоростью, так как копирование при этом не требует­ся. У различных процессов могут быть различные права доступа. Поскольку все процессы, использующие отображаемый на память файл, совместно используют одни и те же страницы, изменения, произведенные одним процессом, немедленно становятся видимыми для всех остальных процессов, даже если файл на диске еще не был обновлен. Также предпринимаются меры, благодаря которым процесс, от­крывающий файл для нормального чтения, видит текущие страницы в ОЗУ, а не устаревшие страницы с диска.
Следует отметить, что при совместном использовании двумя программами од­ного файла DLL может возникнуть проблема, если одна из программ изменит ста­тические данные файла. Если не предпринять специальных действий, то другой процесс увидит измененные данные, что, скорее всего, не соответствует намерени­ям этого процесса. Эта проблема решается таким способом: все отображаемые стра­ницы помечаются как доступные только для чтения, хотя в то же время некоторые из них тайно помечаются как в действительности доступные и для записи. Когда к такой странице происходит обращение операции записи, создается приват­ная копия этой страницы и отображается на память. Теперь в эту страницу мож­но писать, не опасаясь задеть других пользователей или оригинальную копию на диске. Такая техника называется копированием при записи.


8.3.2. Реализация управления памятью

В операционной системе Windows 2000 поддерживается подгружаемое по тре­бованию одинарное линейное 4-гигабайтное адресное пространство для каждого процесса. Сегментация в любой форме не поддерживается. Теоретически размер страниц может быть любой степенью двух, вплоть до 64 Кбайт. На компьютерах с процессором Pentium страницы имеют фиксированный размер в 4 Кбайт. На компьютерах с процессором Itanium они могут быть 8 или 16 Кбайт. Кроме того, сама операционная система может использовать страницы по 4 Мбайт, чтобы снизить размеры таблицы страниц.
В отличие от планировщика, выбирающего отдельные потоки для запуска и не заботящегося о процессах, менеджер памяти занимается исключительно процесса­ми и не беспокоится о потоках, так как именно процессы, а не потоки вла­деют адресным пространством, которым занимается менеджер памяти. При выде­лении области виртуального адресного пространства менеджер памяти создает для нее описатель виртуаль­ной памяти VAD (Virtual Address Descriptor), в котором хранится информация о диапазоне отображаемых адресов, файле резервного хранения и смещении в файле для отображаемой части файла, а также режим доступа. Когда происходит обращение к первой странице, создается каталог таблиц страниц, а указатель на нее помещается в описатель виртуальной памяти. Адресное пространство полнос­тью описывается списком своих описателей виртуальной памяти. Такая схема по­зволяет поддерживать несплошные адресные пространства, так как неиспользуе­мые области между отображаемыми областями не потребляют ресурсов.
В операционной системе Windows 2000 опережающая подкачка страниц не ис­пользуется ни в каком виде. Когда запускается процесс, в памяти не находится ни одной страницы процесса. При каждом страничном прерывании происходит пе­редача управления ядру. Ядро формирует машинно-независимый описатель, в который помещается инфор­мация о том, что случилось, и передает его части исполняющей системы, выпол­няющей функции менеджера памяти. Менеджер памяти проверяет полученный описатель на корректность. Если страница, вызвавшая прерывание, попадает в фик­сированную или зарезервированную область, он ищет адрес в списке описателей виртуальной памяти, находит (или создает) таблицу страниц и ищет в ней соот­ветствующий элемент. Элементы таблицы страниц различаются в разных архитектурах. У неотображаемых страниц также есть записи в таблице, но их формат несколько отличается. Например, если неотображаемая страница должна быть обнулена перед употреблением, этот факт отражается в таблице.
Страничные прерывания подразделяются на пять категорий:
1.Страница, к которой было обращение, не является фиксированной.
2.Произошло нарушение защиты.
3.Запись в совместно используемую страницу.
4.Стеку требуется дополнительная память.
5.Страница, к которой было обращение, является фиксированной, но в насто­ящий момент она не загружена в память.
Первый и второй случаи представляют собой фатальные ошибки, которые не могут быть исправлены или проигнорированы. У третьего случая симптомы схо­жи со вторым (попытка записи в страницу, для которой разрешено только чтение), но лечение этого случая возможно. В этом случае страница копируется в новый физический страничный блок, после чего для копии разрешается чтение/запись. Таким образом, работает копирование при записи. Если совместно используемая страница помечена как доступная для записи во всех процессах, использующих ее, страничного прерывания при записи в такую страницу не возникает и копии при записи не возникает также. В четвертом случае требуется выделение нового стра­ничного блока и его отображение. Однако правила безопасности требуют, чтобы эта страница содержала только нули, что не позволяет новому процессу узнать, чем занимался предыдущий владелец страницы. Таким образом, нужно найти страни­цу, содержащую одни нули или, если это невозможно, нужно выделить другой страничный блок и обнулить его на месте. Наконец, пятый случай представляет собой нормальное страничное прерывание. Менеджер памяти находит страницу на диске и считывает ее в память.
Отметим, что операционная система Windows 2000 не читает отдельные страницы прямо с дис­ка. Вместо этого считывается несколько последовательных страниц, как правило, от 1 до 8, чтобы минимизировать количество обращений к диску. Для страниц, со­держащих код программы, используются серии из большего числа страниц, чем при считывании страниц данных.
Замена страниц происходит следующим образом. Система пытается поддерживать определенное количество свободных страниц в памяти, чтобы, когда произойдет страничное прерывание, свободная страница могла быть найдена немедленно, без необходимости сначала записать несколько других страниц на диск. В результате применения такой стратегии большинство страничных прерываний удовлетворя­ются при помощи всего одной дисковой операции (чтения страницы с диска), хотя иногда приходится выполнять две операции (запись на диск «грязной» страницы, после чего с диска читается требуемая страница). Конечно, страницы, пополняющие список свободных страниц, должны откуда-то поступать. Поэтому настоящая работа алгоритма замещения страниц характери­зуется тем, как эти страницы забираются у процессов и помещаются в список сво­бодных страниц.
В системе подкачки активно использует­ся понятие рабочего набора. У каждого процесса (не у каждого потока) есть рабо­чий набор. Этот набор состоит из отображенных страниц, находящихся в памяти, при обращении к которым, следовательно, не происходит страничных прерываний. Размер и состав рабочего набора, естественно, меняются по мере работы процесса. Рабочий набор каждого процесса описывается двумя параметрами: минималь­ным и максимальным размерами. Эти размеры не являются жесткими границами. Процесс может иметь в памяти меньше страниц, чем значение нижней границы, или (при определенных обстоятельствах) больше установленного максимума. Вначале эти границы одинаковы для каждого процесса, но они могут меняться со временем. Начальное значение минимума по умолчанию находится в диапазоне от 20 до 50 страниц, а начальное значение максимума по умолчанию находится в диапазоне от 45 до 345 страниц, в зависимости от общего объема оперативной па­мяти. Значения по умолчанию могут быть изменены системным администратором.
Если происходит страничное прерывание, а размер рабочего набора меньше минимального значения, то к рабочему набору добавляется страница. С другой стороны, если происходит страничное прерывание, а размер рабочего набора боль­ше максимального значения, то из рабочего набора (но не из памяти) изымается страница, чтобы выделить место для новой страницы. Этот алгоритм означает, что в операционной системе Windows 2000 используется локальный алгоритм, не по­зволяющий процессу получить слишком много памяти, что предотвращает при­чинение процессами ущерба друг другу. Однако система пытается настроить эти параметры. Например, если она замечает, что один процесс слишком активно за­нимается подкачкой (а остальные процессы нет), система может увеличить значе­ние максимального предела для рабочего набора. Таким образом, алгоритм пред­ставляет собой смесь локальных и глобальных решений. Тем не менее существует абсолютный предел размера рабочего набора: даже если в системе работает всего один процесс, он не может занять последние 512 страниц, чтобы оставить немного оперативной памяти для новых процессов.
Раз в секунду выделенный демон-поток ядра, называемый менеджером балансового множества, проверяет, доста­точно ли в системе свободных страниц. Если свободных страниц меньше, чем нуж­но, он запускает менеджер рабочих наборов, который исследует рабочие наборы и освобождает дополнительные страницы. Менеджер рабочих наборов сначала определяет порядок, в котором нужно исследовать процессы. В первую очередь страницы отнимаются у больших процессов, которые бездействовали в течение долгого времени. В последнюю очередь рассматривается процесс переднего плана. Затем менеджер рабочих наборов начинает исследование процессов в выбран­ном порядке. Если рабочий набор процесса в настоящий момент оказывается мень­ше своего нижнего предела или с момента последней инспекции число странич­ных прерываний у этого процесса было выше определенного уровня, то страницы у него не отнимаются. В противном случае менеджер рабочих наборов отнимает у процесса одну или несколько страниц. Количество забираемых у процесса стра­ниц довольно сложным образом зависит от общего объема ОЗУ, а также от того, насколько много требуется памяти текущим процессам, и от того, как размер текущего рабоче­го набора соотносится с верхним и нижним пределами, и кроме того от других парамет­ров. Все страницы рассматриваются по очереди.
На однопроцессорной машине если бит обращений к странице сброшен, то счетчик, связанный со страницей, увеличивается на единицу. Если этот бит уста­новлен в единицу, счетчик обнуляется. После сканирования из рабочего набора удаляются страницы с наибольшими значениями счетчика. Поток продолжает изу­чать процессы, пока он не высвободит достаточного количества страниц, после чего он останавливается. Если полный перебор всех процессов не привел к освобождению достаточного числа страниц, менеджер рабочих наборов начинает второй проход, на котором он уже при необходимости отнимает страницы у процессов, размер рабочего набора кото­рых меньше минимального.
На многопроцессорной машине алгоритм, основанный на проверке бита обраще­ний, уже не работает, так как, хотя текущий центральный процессор не обращался в последнее время к данной странице, к ней могли обращаться другие централь­ные процессоры. Исследование же битов обращений всех центральных процессо­ров представляет собой слишком дорогое удовольствие. Поэтому бит обращений вообще не учитывается, а удаляются самые старые страницы.
Следует отметить, что с точки зрения процедуры замены страниц операцион­ная система сама рассматривается как процесс. Она владеет страницами и у нее также есть рабочий набор. Этот рабочий набор тоже может быть уменьшен. Одна­ко некоторые части системы и невыгружаемый пул фиксированы в памяти и не могут выгружаться ни при каких обстоятельствах.
Каждая страница памяти находится в одном или не­скольких рабочих наборах или в одном из пяти списков. В списке «чистых» (резервных) и в списке «грязных» (модифицированных) страниц учитываются страницы, которые недавно были удалены из рабочих набо­ров, но все еще находятся в памяти и все еще ассоциированы с процессами, ис­пользовавшими их. Различие между ними заключается в том, что у чистых стра­ниц есть копия на диске, тогда как у модифицированных страниц таких копий нет, и эти страницы еще предстоит сохранить. В список свободных страниц входят чи­стые страницы, уже не ассоциированные ни с какими процессами. В список обну­ленных страниц входят страницы, не ассоциированные ни с какими процессами и заполненные нулями. Пятый список состоит из физически дефектных страниц памяти. Это гарантирует, что эти страницы ни для чего не используются.
Страницы перемещаются между рабочими наборами и различными списками менеджером рабочих наборов и другими потоками-демонами ядра. Рассмотрим эти переходы. Когда менеджер рабочих наборов удаляет страницу из рабочего набора, страница попадает на дно списка чистых страниц или списка модифицирован­ных страниц в зависимости от своего состояния. В обоих списках хранятся действительные страницы, поэтому если про­исходит страничное прерывание и требуется одна из этих страниц, она удаляется из списка и возвращается в свой рабочий набор без операции дискового ввода-вывода. Когда процесс завершает свою работу, то все его страницы, которые не используются другими процессами, попадают в список свободных страниц. Эти страницы уже не ассоциированы с каким-либо процессом и не могут возвра­щаться в рабочие наборы по страничному прерыванию. Другие переходы вызываются другими демонами. Один раз в 4 секунды запускается поток свопера в поисках процесса, все потоки которого бездействовали в течение опре­деленного интервала времени. Если ему удается найти такие процессы, он открепляет стеки этих процессов и перемещает страницы процессов в списки «чистых» и «грязных» страниц.
Два других демона, демон записи отображенных страниц и демон записи моди­фицированных страниц, активизируются время от времени, чтобы проверить, доста­точно ли чистых страниц. Если количество чистых страниц ниже определенного уровня, они берут страницы из верхней части списка модифицированных страниц, записывают их на диск, а затем помещают их в список «чистых» страниц. Пер­вый демон занимается записью в отображаемые файлы, а второй пишет страницы в файлы подкачки. В результате их деятельности «грязные» страницы становятся «чистыми».
Причина наличия двух демонов, занимающихся очисткой страниц, заключа­ется в том, что отображаемый на память файл может вырасти в результате записи в него. При этом росте потребуются новые свободные блоки диска. Отсутствие в памяти свободного места для записи в него страниц может привести к взаимо­блокировке. Второй поток может вывести ситуацию из тупика, записывая страни­цы в файл подкачки, который никогда не увеличивается в размерах.
Если процесс освобождает страницу, эта страница более не связана с процессом и может быть помещена в список свобод­ных страниц, если только она не используется совместно другими процессами. Когда страничное прерывание требует страничный блок, чтобы поместить в него страницу, которая должна быть считана, этот блок по возможности берется из списка свободных страниц. Не имеет значения, что эта страница может все еще содержать конфиденциальную информацию, так как вся она будет тут же целиком перезаписана. При увеличении стека ситуация складывается иная. В этом случае требуется пустой страничный блок и правила безопасности требуют, чтобы страница содержала все нули. По этой причине другой демон ядра – поток обнуления страниц – работает с минимальным приоритетом, стирая содержимое страниц в списке свободных страниц и помещая их в список обнуленных стра­ниц. Когда центральный процессор простаивает и в списке свободных страниц есть страницы, поток обнуления страниц может обнулять их, так как обнуленная страница более полезна, чем просто свободная страница.
Для отслеживания всех страниц и всех списков операционная система Win­dows 2000 содержит базу данных страничных блоков, состоящую из записей по числу страниц ОЗУ. Эта таблица проиндексирована по номеру физи­ческого страничного блока. Записи таблицы имеют фиксированную длину, но для различных типов записей используются различные форматы (например, для дей­ствительных записей и для недействительных). Действительные записи содержат информацию о состоянии страницы, а также счетчик, хранящий число ссылок на эту страницу в таблицах страниц. Этот счетчик позволяет системе определить, ког­да страница уже более не используется. Если страница находится в рабочем наборе, то в записи также указывается номер рабочего набора. Кроме того, в записи содер­жится указатель на таблицу страниц, в которой есть указатель на эту страницу (если такая таблица страниц есть). Страницы, используемые совместно, учитыва­ются особо. Также запись содержит ссылку на следующую страницу в списке (если такая есть) и различные другие поля и флаги, такие как «страница читается», «страница пишется» и т. д.
Итак, управление памятью представляет собой очень сложную подсистему с большим количеством структур данных, алгоритмов и эвристических методов. Во многом она является саморегулируемой, но у нее есть также много механизмов, которые может использовать системный администратор, чтобы влиять на про­изводительность системы. Следует подчеркнуть, что управление памятью в реальных системах намного сложнее простого алгорит­ма подкачки, вроде алгоритма часов или алгоритма старения.

8.4. Ввод-вывод в системе Windows 2000

8.4.1. Основные понятия

Менеджер ввода-вывода родственен менеджеру plug-and-play. Основная идея ме­ханизма plug-and-play заключается в настраиваемой шине. За многие годы было разработано множество шин, включая PC Card, PCI, USB, IEEE 1394 и SCSI, по­этому менеджер plug-and-play может послать каждому разъему запрос и попросить устройство назвать себя. Определив, что за устройство подключено к шине, менед­жер plug-and-play выделяет для него аппаратные ресурсы, такие как уровни пре­рываний, находит необходимые драйверы и загружает их в память. При загрузке каждого драйвера для него создается объект драйвера. Для некоторых шин, напри­мер SCSI, настройка происходит только во время загрузки операционной системы. Для других шин, таких как USB и IEEE 1394, она может производиться в любой момент, для чего требуется тесный контакт между менеджером plug-and-play, драй­вером шины (который и выполняет настройку) и менеджером ввода-вывода.
Менеджер ввода-вывода также тесно связан с менеджером энергопотребления. Менеджер энергопотребления может перевести компьютер в одно из семи состо­яний, которые можно примерно описать следующим образом:
1.Полностью действующий.
2.Режим сниженного энергопотребления-1: мощность, потребляемая централь­ным процессором, снижается, ОЗУ и кэш работают, возможен мгновенный переход в режим полного действия.
3.Режим сниженного энергопотребления-2: центральный процессор и ОЗУ работают; кэш центрального процессора отключен; возможно продолжение работы с текущего значения счетчика команд.
4.Режим сниженного энергопотребления-3: центральный процессор и кэш отключены; ОЗУ работает; возможен перезапуск с фиксированного адреса.
5.«Зимняя спячка»: центральный процессор, кэш и ОЗУ отключены; возмо­жен перезапуск из сохраненного на диске файла.
6. Выключен: все выключено; требуется полная перезагрузка.
Устройства ввода-вывода также могут находиться в различных состояниях. Включением и выключением этих устройств занимаются вместе менеджер энер­гопотребления и менеджер ввода-вывода. Обратите внимание, что состояния со 2 по 6 используются, только если центральный процессор бездействовал в течение определенного времени.
Формально все файло­вые системы представляют собой драйверы ввода-вывода. Обращения к блокам диска от пользовательских процессов сначала посылаются менеджеру кэша. Если менеджер кэша не может удовлетворить запрос из кэша, он просит менеджер вво­да-вывода вызвать драйвер соответствующей файловой системы, чтобы тот полу­чил требуемый блок с диска.
Windows 2000 поддерживает асинхрон­ный ввод-вывод. Поток может начать операцию ввода-вывода, а затем продол­жить выполнение параллельно с вводом-выводом. Такая возможность особенно важна для серверов. Существует множество способов, с помощью которых по­ток может определить, что операция ввода-вывода завершена. Один из способов состоит в создании в момент обращения к вызову ввода-вывода объекта события, а потом ожидания этого события. Другой способ заключается в указании очереди, в которую будет послано сообщение о завершении операции ввода-вывода. Третий способ заключается в предоставлении процедуры обратного вызова, к которой обращается система, когда операция ввода-вывода завершена.
8.4.2. Реализация ввода-вывода в Windows 2000

Основная функция менеджера ввода-вывода за­ключается в создании каркаса, в котором могут работать различные устройства вво­да-вывода. Структуру каркаса образуют набор независимых от устройств процедур для определенных аспектов ввода-вывода и набор загруженных драйве­ров для общения с устройствами.
Чтобы гарантировать, что драйверы устройств хорошо работают с остальной час­тью системы Windows 2000, корпорация Microsoft определила для драйверов мо­дель Windows Driver Model, которой драйверы устройств должны соответство­вать. Более того, корпорация Microsoft также предоставляет набор инструментов, который должен помочь разработчикам драйверов в создании драйверов, соответ­ствующих модели Windows Driver Model. Согласующиеся с ней драйверы должны удовлетворять всем сле­дующим требованиям (а также некоторым другим):
1. Обрабатывать входящие запросы ввода-вывода, поступающие в стандарт­ном формате.
2. Основываться на объектах, как и остальная часть системы Windows 2000.
3. Позволять динамическое добавление или удаление устройств plug-and-play.
4. Допускать, когда это возможно, управление энергопотреблением.
5. Допускать реконфигурацию в терминах использования ресурсов.
6. Быть реентерабельными для возможности их использования на мультипро­цессорах.
Запросы ввода-вывода передаются драйверам в виде стандартизированных пакетов, называемых IRP (Input/output Request Packet – пакет запроса ввода-вывода). Драйверы, согласующиеся с моделью Windows Driver Model, должны уметь обрабатывать пакеты IRP. Драйвер должен поддерживать работу с объекта­ми, то есть поддерживать определенный список методов, к которым может обра­щаться остальная система. Он также должен корректно работать с другими объек­тами операционной системы Windows 2000, доступ к которым осуществляется при помощи дескрипторов объектов.
Драйверы, согласующиеся с моделью Windows Driver Model, должны полнос­тью поддерживать устройства plug-and-play. Это означает, что если устройство, управляемое драйвером, внезапно добавляется в систему или удаляется из систе­мы, драйвер должен быть готов к получению данной информации и корректной реакции на эту информацию, даже в том случае, если устройство удаляется в момент обращения к нему. Также драйверы должны поддерживать управление энергопот­реблением для тех устройств, для которых это возможно. Например, если система решает, что теперь пора перейти в режим низкого энергопотребления, все драйве­ры должны поддерживать этот режим, чтобы сберегать энергию. Они также долж­ны поддерживать обратный переход в режим нормального функционирования.
Драйвер должен быть настраиваемым, что означает отсутствие каких бы то ни было встроенных предположений о линиях прерываний или портах ввода-выво­да, используемых определенным устройством. Драйверы устройств также должны работать на многороцессорных машинах, так как под­держка многопроцессорности была заложена в операционную систему Windows 2000 при разработке. Это требование означает, это во время обработки драйвером запро­са от одного центрального процессора может прийти запрос от другого центрально­го процессора. Второй центральный процессор может начать выполнение програм­мы драйвера одновременно с первым центральным процессором. Драйвер должен функционировать корректно, даже когда он вызывается одновременно двумя и более центральными процессорами. Это означает, что доступ ко всем чувствитель­ным структурам данных должен предоставляться только внутри критических об­ластей. Простое предположение, что других обращений к драйверу не будет, пока не завершится обработка текущего обращения к нему, недопустимо.
В операционной системе UNIX обращение к драйверам производится по номеру главного устройства. В Windows 2000 применяется другая схема. Во время загруз­ки операционной системы или в тот момент, когда в систему добавляется новое устройство plug-and-play, поддерживающее установку системы без перезагрузки, операционная система Windows 2000 автоматически обнаруживает его и вызывает менеджер plug-and-play. Менеджер plug-and-play запрашивает у устройства назва­ние фирмы-производителя и номер модели устройства. Вооружившись данными сведениями, он ищет драйвер для данного устройства в определенном каталоге на жестком диске. Если этого драйвера нет, он отображает диалоговое окно, в котором пользователю предлагается вставить гибкий диск или CD-ROM с драйвером. Ког­да драйвер обнаружен, он загружается в память.
Каждый драйвер должен поставлять набор процедур, которые могут быть вы­званы для получения требуемого обслуживания. Первая процедура, называемая DriverEntry, инициализирует драйвер. Она вызывается сразу после загрузки драйве­ра. Процедура может создавать таблицы и структуры данных, но не должна обращать­ся к самому устройству. Она также заполняет некоторые поля объекта драйвера, созданного менеджером ввода-вывода при загрузке драйвера. Поля в объекте драй­вера включают указатели на все остальные процедуры, предоставляемые драйвером.
Кроме того, для каждого устройства, управляемого драйвером (например, для каждо­го диска IDE, управляемого драйвером диска IDE), создается объект устройства и инициализируется так, чтобы он указывал на объект драйвера. При наличии объекта устройства можно легко найти объект драйвера и, таким образом, обращаться к его методам.
Вторая процедура драйвера называется AddDevice. Она вызывается менедже­ром plug-and-play всего один раз для каждого добавляемого устройства. После этого драйвер вызывается первым пакетом IRP, который устанавливает вектор прерываний и инициализирует аппаратуру. Кроме того, драйвер должен содержать процедуру обработки прерываний, различные процедуры, управляющие таймера­ми, путь быстрого ввода-вывода, управление DMA, позволять прервать исполня­ющийся текущий запрос и многое другое.
В Windows 2000 драйвер должен сам выполнять всю работу. С другой сто­роны, в системе Windows 2000 могут существовать стеки драйверов. Это означает, что запрос может проходить через целую последовательность драйверов, каждый из которых выполняет свою часть работы. Стеки драйверов позволяют отделить управление шиной от управления соб­ственно устройством. Например, управление шиной PCI отличается большой сложностью, что вызвано большим количеством режимов и транзакций шины. Таким образом, отделение управления шиной от управления устройством, подключенным к данной шине, облегчает работу по созданию драйвера. Программисту, пишущему драйвер устройства, более не нужно изучать вопрос управления шиной. Он может просто использовать стандартный драйвер шины, находящийся в стеке драйверов. У драй­веров USB и SCSI есть части, специфичные для конкретных устройств, и общая часть, для которой используются отдельные драйверы.
Кроме того, стеки драйверов позволяют добавлять в стек драйверы-фильтры. Фильтрующий драйвер выполняет некоторые преобразования проходящих через них данных. Например, фильтрующий драйвер может сжать данные по пути к дис­ку или зашифровать их по дороге в сеть. Помещение драйверного фильтра в стек драйверов означает, что ни прикладная программа, ни настоящий драйвер устрой­ства не должны знать о присутствии фильтрующего драйвера и что фильтрующий драйвер работает автоматически для всех данных, поступающих с устройства или на устройство.

8.5. Файловые системы Windows 2000

8.5.1. Файловые системы типа FAT

Операционная система Windows 2000 кроме новой файловой системы NTFS, разработанной специально для Windows NT, поддерживает несколько устаревших файловых систем типа FAT операционной системы MS-DOS.
Логический раздел, отформатированный под файловую систему FAT (File Allocation Table – таблица размещения файлов), состоит из следующих областей:
1.Загрузочный сектор содержит программу начальной загрузки операционной системы. Вид этой программы зависит от типа операционной системы, которая будет загружаться из этого раздела.
2. Основная копия FAТ содержит информацию о размещении файлов и катало­гов на диске.
3. Резервная копия FAT.
4. Корневой каталог занимает фиксированную область размером в 32 сектора (16 Кбайт), что позволяет хранить 512 записей о файлах и каталогах, так как каждая запись каталога состоит из 32 байт.
5. Область данных предназначена для размещения всех файлов и всех каталогов, кроме корневого каталога.
Файловая система FAT поддерживает всего два типа файлов: обычный файл и каталог. Файловая система распределяет память только из области данных, причем использует в качестве минимальной единицы дискового пространства кластер.
Таблица FAT (как основная копия, так и резервная) состоит из массива индексных указателей, количество которых равно количеству кластеров области данных. Между кластерами и индексными указателями имеется взаимно однозначное соответствие – нулевой указатель соответствует нулевому кластеру и т.д.
Индексный указатель может принимать следующие значения, характеризующие состояние связанного с ним кластера:
– кластер свободен (не используется);
– кластер используется файлом и не является последним кластером фййла (в этом случае индексный указатель содержит номер следующего файла);
• последний кластер файла;
• дефектный кластер;
• резервный кластер.
Таблица FAT является общей для всех файлов раздела. В исходном состоянии (после форматирования) все кластеры раздела свободны и все индексные указатели (кроме тех, которые соответствуют резервным и дефектным блокам) принимают значение «кластер свободен». При размещении файла ОС просматривает FAT, начиная с начала, и ищет первый свободный индексный указатель. После его обнаружения в поле записи каталога «номер первого кластера» фиксируется номер этого указателя. В кластер с этим номером записываются данные файла, он становится первым кластером файла. Если файл умещается в одном кластере, то в указатель, соответствующий данному кластеру, заносится специальное значение «последний кластер файла». Если же размер файла больше одного кластера, то ОС продолжает просмотр FAT и ищет следующий указатель на свободный кластер. После его обнаружения в предыдущий указатель заносится номер этого кластера, который теперь становится следующим кластером файла. Процесс повторяется до тех пор, пока не будут размещены все данные файла. Таким образом создается связный список всех кластеров файла.
В начальный период после форматирования файлы будут размещаться в последовательных кластерах области данных, однако после определенного количества удалений файлов кластеры одного файла окажутся в произвольных местах области данных, чередуясь с кластерами других файлов.
Размер таблицы FAT и разрядность используемых в ней индексных указателей определяется количеством кластеров в области данных. Для уменьшения потерь из-за фрагментации желательно кластеры делать небольшими, а для сокращения объема адресной информации и повышения скорости обмена наоборот – чем больше, тем лучше. При форматировании диска под файловую систему FAT обычно выбирается компромиссное решение и размеры кластеров выбираются из диапазона от 1 до 128 секторов, или от 512 байт до 64 Кбайт.
Очевидно, что разрядность индексного указателя должна быть такой, чтобы в нем можно было задать максимальный номер кластера для диска определенного объема. Существует несколько разновидностей FAT, отличающихся разрядностью индексных указателей, которая и используется в качестве условного обозначения: FAT12, FAT16 и FAT32. В файловой системе FAT16 используются 16-разрядные указатели, что позволяет поддерживать до 65 536 кластеров в области данных диска, а в FAT32 – 32-разрядные указатели для более чем 4 миллиардов кластеров.
Таблица FAT при фиксированной разрядности индексных указателей имеет пе­ременный размер, зависящий от объема области данных диска. При удалении файла из файловой системы FAT в первый байт соответствующей записи каталога заносится специальный признак, свидетельствующий о том, что эта запись свободна, а во все индексные указатели файла заносится признак «кла­стер свободен». Остальные данные в записи каталога, в том числе номер первого кластера файла, остаются нетронутыми, что оставляет шансы для восстановле­ния ошибочно удаленного файла. освобождения записи. Очевидно, что надежно можно восстановить только файлы, которые были расположены в последовательных кластерах диска, так как при отсутствии связного списка выявить принадлежность произвольно расположенного кластера удаленному файлу невозможно (без анализа содержимого кластеров, выполняемого пользователем «вручную»).
Резервная копия FAT всегда синхронизируется с основной копией при любых операциях с файлами, поэтому резервную копию нельзя использовать для oтмены ошибочных действий пользователя, выглядевших с точки зрения системы вполне корректными. Резервная копия может быть полезна только в том случае, когда секторы основной памяти окaзывaютcя физически поврежденными и не читаются.
Используемый в FAT метод хранения адресной информации о файлах не отличается большой надежностью – при разрыве списка индексных указателей в одном месте, например из-за сбоя в работе программного кода ОС по причине внешних электромагнитных помех, теряется информация обо всех последующих кластерах файла.
Файловые системы FAT12 и FAT16 оперировали с именами файлов, состоящими из 12 символов по схеме «8.3». В версии FAT16 операционной системы Win­dows NT был введен новый тип записи каталога – «длинное имя», что позволяет использовать имена длиной до 255 символов, причем каждый символ длинного имени хранится в двухбайтном формате. Имя по схеме «8.3», называемое теперь коротким, по-прежнему хранится в 12-байтовом поле имени файла в записи каталога, а длинное имя помещается порциями по 13 символов в одну или несколько записей, следующих непосредственно за основной записью каталога. Каждый символ кодируется двумя байтами, поэтому 13 символов занимают 26 байт, а оставшиеся 6 байт отведены под служебную информацию. Таким образом у файла имеется два имени – короткое для совместимости со старыми приложениями, не понимающими длинных имен, и длинное имя, удобное в использовании. Файловая система FAT32 также под­держивает короткие и длинные имена.
Файловые системы FAT12 и FAT16 получили большое распространение благо­даря их применению в операционных системах MS-DOS и Windows 3.x – самых массовых операционных системах первого десятилетия эры персональных ком­пьютеров. По этой причине эти файловые системы поддерживаются сегодня и
другими ОС, такими как UNIX, OS/2, Windows 95/98/ME и Windows NT/2000/XP. Однако из-за постоянно растущих объемов жестких дисков, а также возрастающих требований к надежности, эти файловые системы практически вытеснены вначале сиcтемой FAT32, впервые появившейся в Windows 95 OSR2, а затем и файловыми сиcтемами других типов.

8.5.2. Файловая система типа NTFS

Система NTFS (New Technology File System – файловая система новой технологии) представляет собой новую сложную файловую систему, разработанную специально для Windows NT и перене­сенную в Windows 2000. Эта фай­ловая система не является попыткой улучшить старую файловую систему MS-DOS.
Длина имени файла в системе NTFS ограничена 255 символами, полная длина пути ограничивается 32 767 символами. Файл в системе NTFS – это не просто линейная последовательность байтов, как файлы в системах FAT-32 и UNIX. Вместо этого файл состоит из множества атрибутов, каждый из которых представляется в виде потока байтов. Большинство файлов имеет несколько коротких потоков, таких как имя файла и его 64-битовый идентификатор, плюс один длинный (неименованный) поток с данными. Однако у файла может быть и несколько длинных потоков данных. У каждого потока своя длина. Каждый поток может блокировать­ся независимо от остальных потоков. Максимальная длина потока составляет 16 экзабайт (2 в степени 64 байт). Для отслеживания местонахождения процесса в каждом потоке используются 64-разрядные файловые указатели.
Вызовы функций Win32 API для управления файлами и каталогами в первом приближении подобны соответствующим им в UNIX, но у функций Win32 API больше параметров и другая модель безопасности. Процедура откры­тия файла возвращает дескриптор файла, который затем может использоваться для чтения этого файла или записи в файл. Для графических приложений заранее не определены указатели в файлах. Стандартные потоки ввода, вывода и сообщений об ошибках при необходимости должны открываться явно. Однако в консольном режиме они открываются заранее. Интерфейс Win32 также содержит ряд допол­нительных вызовов, отсутствующих в системе UNIX.
Каждый том NTFS (то есть дисковый раздел) содержит файлы, каталоги, битовые массивы и другие структуры данных. Каждый том организован как линейная последовательность блоков (кластеров по терминологии Microsoft). Размер блока фиксирован для каждого тома и варьируется в пределах от 512 байт до 64 Кбайт, в зависимости от размера тома. Для большинства дисков NTFS используются блоки размером в 4 Кбайт как компромисс между большими блоками (для эф­фективности операций чтения/записи) и маленькими блоками (для уменьшения потерь дискового пространства на внутреннюю фрагментацию). Обращение к бло­кам осуществляется по их смещению от начала тома, для которого используются 64-разрядные числа.
Главной структурой данных в каждом томе является главная файловая табли­ца MFT (Master File Table), представляющая собой линейную последовательность записей фиксированного (1 Кбайт) размера. Каждая запись MFT описывает один файл или один каталог. В ней содержатся атрибуты файла, такие как его имя и временные штампы, а также список дисковых адресов, указывающих на располо­жение блоков файла. Если файл очень большой, то иногда бывает необходимо ис­пользовать две и более записей главной файловой таблицы, чтобы вместить спи­сок всех блоков файла. В этом случае первая запись MFT, называемая базовой записью, указывает на другие записи MFT.
Сама главная файловая таблица представляет собой файл и, как и любой файл, может располагаться влюбом месте тома, тем самым устраняется проблема дефект­ных секторов на первой дорожке дискового раздела. Кроме того, этот файл может при необходимости возрастать до максимального размера в 248 записей.
Главная файловая таблица имеет следующую структуру.
Каждая запись MFT состоит из последовательности пар (заголовок атрибута, значение). Каждый атрибут начинается с заголовка, идентифицирующего этот атрибут и сообщающе­го длину значения, так как некоторые атрибуты, например имя файла или данные, могут иметь переменную длину. Если значение атрибута достаточно короткое, что­бы поместиться в запись MFT, оно помещается туда. Если же это значение слиш­ком длинное, оно располагается в другом месте диска, а в запись MFT помещается указатель на него. Первые 16 записей MFT зарезервированы для файлов метаданных NTFS. Каждая запись описывает нормальный файл, у которого есть атрибуты и блоки данных, как у любого файла. У каждого такого файла есть имя, начинающееся с символа доллара, указывающего на то, что это файл мета­данных. Первая запись описывает сам файл MFT. В частности, она содержит ин­формацию о расположении блоков файла MFT, что позволяет системе найти файл MFT. Очевидно, чтобы найти всю остальную информацию о файловой системе, у операционной системы Windows 2000 должен быть некий способ нахождения первого блока файла MFT. Номер первого блока файла MFT содержится в загру­зочном блоке, куда он помещается при установке системы.
Запись 1 представляет собой дубликат первой части файла MFT. Эта инфор­мация является настолько ценной, что наличие второй копии может быть необхо­димо на случай, если один из первых блоков главной файловой таблицы вдруг ста­нет дефектным. Запись 2 представляет собой журнал. Когда в файловой системе производятся структурные изменения, такие как добавление нового каталога или удаление существующего каталога, информация о предстоящей операции реги­стрируется в журнале. Таким образом, увеличивается вероятность корректного восстановления файловой системы в случае сбоя во время выполнения операции. Изменения атрибутов файлов также регистрируются здесь. В этом журнале не регистрируются только изменения данных пользователя. В записи 3 содержится информация о томе, например его размер, метка и версия.
Как уже отмечалось, каждая запись MFT содержит последо-вательность пар (заголовок атрибута, значение). Файл $AttrDef является тем местом, в котором определяются атрибуты. Информация об этом файле хранится в записи 4 табли­цы MFT. В следующей записи содержатся данные о корневом каталоге, который сам представляет собой файл и может произвольно увеличиваться в размерах. Он описывается записью 5 главной файловой таблицы.
Свободное место на диске учитывается с помощью битового массива. Битовый массив сам является файлом, и его атрибуты и дисковые адреса хранятся в запи­си 6 таблицы MFT. Следующая запись таблицы MFT указывает на файл начальной загрузки. Запись 8 используется для того, чтобы связать вместе все дефектные блоки и гарантировать, что они никогда не встретятся в файлах. Запись 9 содер­жит информацию о защите. Запись 10 используется для преобразования регистра. Наконец, запись 11 представляет собой каталог, содержащий раз­личные файлы для дисковых квот, идентификаторов объектов, точек повторного анализа и т. д. Последние четыре записи MFT зарезервированы на будущее.
Каждая запись MFT состоит из заголовка записи, за которым следует после­довательность пар (заголовок атрибута, значение). Заголовок записи содержит: магическое число, используемое для проверки действительности записи; поряд­ковый номер, обновляемый каждый раз, когда запись используется для нового файла; счетчик обращений к файлу; действительное количество байт, использу­емых в записи; идентификатор (индекс, порядковый номер) базовой записи (используемый только для записей расширения), а также другие различные поля. Следом за заголовком записи располагается заголовок первого атрибута, за кото­рым идет значение первого атрибута, потом заголовок второго атрибута, значение второго атрибута и т. д.
В файловой системе NTFS определено 13 атрибутов, которые могут появ­ляться в записях MFT. Все записи таблицы MFT состоят из последовательности заголовков атрибутов, каждый из которых иден­тифицирует следующий за ним атрибут, а также содержит длину и расположе­ние поля значения вместе с разнообразными флагами и прочей информацией. Как правило, значения атрибутов располагаются непосредственно за заголовками, но если длина значения слишком велика, чтобы поместиться в запись таблицы MFT, она может быть помещена в отдельный блок диска. Такой атрибут называ­ется нерезидентным атрибутом. Например, таким атрибутом является атрибут данных. Некоторые атрибуты, такие как атрибуты имени, могут повторяться, но все атрибуты должны располагаться в записи MFT в фиксированном поряд­ке. Длина заголовков резидентных атрибутов 24 байт, заголовки для нерезидент­ных атрибутов длиннее, так как они содержат информацию о месте расположения атрибута.
Стандартное информационное поле содержит сведения о владельце файла, ин­формацию о защите, временные штампы (необходимые для стандарта POSIX), счет­чик жестких связей, бит «только чтение», архивный бит и т. д. Это поле имеет фик­сированную длину, и оно всегда присутствует.
В операционной системе Windows NT 4.0 информация о защите файла могла содержать­ся в атрибуте файла, но в Windows 2000 эти данные хранятся в отдельном файле, что позволяет нескольким файлам совместно пользоваться общими описателями защиты. Список атрибутов нужен на случай, если атрибуты не помещаются в за­пись MFT. Каждая запись в списке содержит 48-разрядный индекс в таблице MFT, указывающий на запись расширения, а также 16-разрядный порядковый номер, позволяющий проверить соответствие записи расширения и базовой записи.
Атрибут «идентификатор объекта» задает файлу уникальный номер. Иногда он используется внутри системы. Точка повторного анализа велит процедуре, анализирующей имя файла, выполнить специальные действия. Этот механизм применя­ется для монтирования устройств и символьных ссылок. Два следующих атрибу­та используются только для идентификации тома. Еще три атрибута используют­ся для реализации каталогов. Небольшие каталоги представляют собой простые списки файлов, но большие каталоги реализуются в виде деревьев. Поток дан­ных утилиты регистрации используется шифрующей файловой системой.
Имя потока данных, если оно присутствует, располагается в заголов­ке атрибута данных. Следом за этим заголовком располагается либо список дисковых ад­ресов, определяющий положение файла на диске, либо сам файл (для небольших файлов длиной в несколько сотен байтов). Метод по­мещения самого содержимого файла в запись MFT называется непосредственным файлом.
Для увеличения эффективности дисковые блоки файлам назначаются по воз­можности в виде серий последовательных блоков (сегментов файла). Например, если первый логический блок файла помещается в блоке 20 на диске, тогда систе­ма будет стараться выделить для второго блока этого файла блок 21, для третье­го – блок 22 и т. д. Один из способов выделения файлам таких серий блоков за­ключается в том, чтобы предоставлять файлам сразу по несколько блоков.

Блоки в файле описываются последовательностью записей, каждая из которых описывает последовательность логически непрерывных блоков. Непрерывный файл описывается всего одной записью. К этой категории относятся файлы, за­писываемые за одну операцию от начала до конца. Файл с одной «дыркой» (на­пример файл, для которого определены только блоки с 0 по 49 и с 60 по 79), будет описываться двумя записями. Такой файл может быть создан, если сначала запи­сать в него первые 50 блоков, затем переместить указатель в файле на логический блок 60 и записать еще 20 блоков. Когда из такого файла читается «дырка», все отсутствующие байты оказываются нулями.
Каждая запись начинается с заголовка, определяющего смещение первого бло­ка в файле. Затем располагается смещение первого блока, не покрываемого пер­вой записью. Следом за каждым заголовком располагаются пары, в которых содержатся дисковые адреса и длины серий блоков. Эти дисковые адреса представляют собой смещение блока от начала дискового раздела. Длина серии – это количество бло­ков в серии. В записи серии может содержаться любое необходимое количество пар.
Для хранения больших фрагментированных файлов может быть использовано несколько записей таблицы MFT. Проблема может возникнуть, если потребуется так много записей MFT, что в базовой записи не поместятся все индексы MFT. Эта проблема решается следу­ющим образом: список записей MFT делается нерезидентным (то есть хранится отдельно на диске, а не в базовой записи MFT). В этом случае его размер уже ничем не ограничен.
Запись MFT для небольшого каталога со­держит несколько каталоговых записей, каждая из которых описывает файл или каталог. Фиксированная часть содержит индекс записи MFT файла, длину имени файла, а также другие разнообразные поля и флаги. Поиск файла в каталоге по имени состоит в последовательном переборе всех имен файлов. Для больших каталогов используется другой формат. Вместо того чтобы линей­но перечислять файлы, используется дерево, обеспечивающее поиск в алфа­витном порядке и упрощающее добавление в каталог новых имен в соответству­ющие места.
Помимо обычных файлов и каталогов, файловая система NTFS поддерживает жесткие связи, подобные используемым в UNIX, а также символьные ссылки при помощи механизма, называемого точками повторного анализа. Файл или каталог может быть помечен как точка повторного анализа, и с ним может быть ассоци­ирован блок данных. Когда во время анализа имени файла встречается такой файл или каталог, срабатывает обработка исключения и интерпретируется блок данных. Блок может выполнять различные действия, включая переадресацию поиска, ссы­лаясь на другую часть дерева каталогов или даже на другой дисковый раздел. Этот механизм используется для поддержки как символьных ссылок, так и монтиров­ки файловых систем.
Файловая система NTFS поддерживает прозрачное сжатие файлов. Файл может быть создан в сжатом режиме. Это означает, что файловая система NTFS будет ав­томатически пытаться сжать блоки этого файла при записи их на диск и автомати­чески распаковывать их при чтении. Процессы, читающие этот файл или пишу­щие в него, не будут даже догадываться о том, что при этом происходит компрессия или декомпрессия данных.
Сжатие данных файла происходит следующим образом. Когда файловая сис­тема NTFS записывает на диск файл, помеченный для сжатия, она изучает первые 16 логических блоков файла, независимо от того, сколько сегментов на диске они занимают. Затем к этим блокам применяется алгоритм сжатия. Если полученные на выходе блоки могут поместиться в 15 или менее блоков, то сжатые данные за­писываются на диск, предпочтительно в виде одного сегмента. Если получить вы­игрыш хотя бы в один блок не удается, то данные 16 блоков так и записываются в несжатом виде. Затем весь алгоритм повторяется для следующих 16 блоков и т. д.
В операционной системе Windows 2000 предусмотрена возможность шифрования файлов. Чтобы использовать шифрование в операционной системе Windows 2000, нужно пометить каталог как зашифрованный, в результате чего будут зашифрованы все файлы в этом каталоге, а все новые файлы, перемещен­ные в этот каталог или созданные в нем, также будут зашифрованы. Само шифро­вание и дешифрование выполняется не файловой системой NTFS, а специальным драйвером EPS (Encrypting File System – шифрующая файловая система), разме­щающимся между NTFS и пользовательским процессом. Таким образом, приклад­ная программа не знает о шифровании, а сама файловая система NTFS только частично вовлечена в этот процесс.


8.6. Безопасность в Windows 2000

8.6.1. Основные понятия

Операци­онная система Windows 2000 обладает рядом достаточно совершенных и эффективных свойств безопасности, включая следующие:
1. Безопасная регистрация в системе с мерами предосторожности против по­пыток применения фальшивой программы регистрации.
2. Дискреционное управление доступом.
3. Управление привилегированным доступом.
4. Защита адресного пространства для каждого процесса.
5. Обнуление страниц перед выделением их процессу.
6. Аудит безопасности.
Безопасная регистрация означает, что системный администратор может потре­бовать ото всех пользователей наличия пароля для входа в систему. Дискреционное управление доступом позволяет владельцу файла или другого объекта указать, кто может пользоваться объектом и каким образом. Средства управления привилегированным доступом позволяют системному администрато­ру (суперпользователю) получать доступ к объекту, несмотря на установленные его владельцем разрешения доступа. Под защитой адресного пространства имеется в виду лишь то, что у каждого процесса есть собственное защищенное виртуаль­ное адресное пространство, недоступное для любого неавторизованного процесса. Следующий пункт означает, что при увеличении стека выделяемые для него стра­ницы заранее обнуляются, так что процесс не может обнаружить в них информа­ции, помещенной предыдущим владельцем страницы памяти (страницы подаются процессам из списка обнуленных страниц). Наконец, аудит безопасности следует понимать как регистрацию системой в журнале опре­деленных событий, относящихся к безопасности. Впоследствии этот журнал может просматривать системный администратор.
У каждого пользователя (и группы) операционной системы Windows 2000 есть идентификатор безопасности SID (Security IDentifier), по которому операцион­ная система отличает его от других пользователей. Идентификаторы безопаснос­ти представляют собой двоичные числа с коротким заголовком, за которым следу­ет длинный случайный компонент. Каждый SID должен быть уникален в пределах всей планеты. Когда пользователь запускает процесс, этот процесс и его потоки работают под идентификатором пользователя. Большая часть системы безопасно­сти спроектирована так, чтобы гарантировать предоставление доступа к каждому объекту только потокам с авторизованными идентификаторами безопасности.
У каждого процесса есть маркер доступа, в котором указывается SID и другие свойства. Как правило, он назначается при регистрации в системе процедурой winlogon. Заголовок маркера содержит некоторую админи­стративную информацию. По значению поля срока действия можно определить, когда маркер перестанет быть действительным. Поле Groups (группы) указывает группы, к которым принадлежит процесс. Это поле необходимо для соответствия требованиям стандарта POSIX. Поле Default DACL (Default Discretionary Access Control List – список разграничительного контроля доступа по умолчанию) представляет собой список управ­ления доступом, назначаемый объектам, созданным процессом, если не опреде­лены другие списки ACL. Идентификатор безопасности пользователя указывает пользователя, владеющего процессом. Ограниченные идентификаторы SID по­зволяют ненадежным процессам принимать участие в заданиях вместе с надеж­ными процессами, но с меньшими полномочиями и меньшими возможностями причинения ущерба.
Наконец, перечисленные в маркере привилегии (если они перечислены) дают процессу особые полномочия, такие как право выключать компьютер или полу­чать доступ к файлам, к которым в противном случае в этом доступе процессу было бы отказано. Привилегии позволяют разбить полномочия системного админист­ратора на отдельные права, которые могут предоставляться процессам по отдель­ности. Таким образом, пользователю может быть предоставлена часть полномо­чий суперпользователя, но не все его полномочия. Итак, маркер доступа содержит информацию о том, кто владеет процессом и какие умолчания и полномочия ассо­циированы с ним.
Когда пользователь регистрируется в системе, процесс winlogon назначает мар­кер доступа начальному процессу. Последующие процессы, как правило, наследу­ют этот маркер. Маркер доступа процесса изначально применяется ко всем пото­кам процесса. Однако поток во время исполнения может получить другой маркер доступа. В этом случае маркер доступа потока перекрывает маркер доступа про­цесса. В частности, клиентский поток может передать свой маркер доступа сервер­ному потоку, чтобы сервер мог получить доступ к защищенным файлам и другим объектам клиента. Такой механизм называется перевоплощением.
Другим основным понятием является дескриптор защиты. У каждого объекта есть ассоциированный с ним дескриптор защиты, содержащий список пользова­телей и групп, имеющих доступ к данному объекту. Дескриптор защиты состоит из заголовка, за которым следует список DACL с одним или несколькими элемен­тами АСЕ (Access Control Entry – элемент списка контроля доступа ACL). Два основных типа элементов списка – это разрешение и запрет доступа. Разрешаю­щий элемент содержит SID пользователя или группы и битовый массив, опре­деляющий набор операций, которые процессы с данным идентификатором SID могут выполнять с определенным объектом. Запрещающий элемент работает ана­логично, но совпадение идентификаторов означает, что обращающийся процесс не может выполнять перечисленные операции.
Кроме списка DACL у дескриптора защиты есть также список SACL (System Access Control List – системный список контроля доступа), который похож на DACL, только вместо пользователей и групп, имеющих доступ к объекту, в нем перечисляются операции с этим объектом, регистрируемые в специальном журна­ле. В операционной системе Windows 2000 также пре­доставляются дополнительные возможности аудита для регистрации доступа к объектам.
8.6.2. Реализация защиты в Windows 2000

Защита в автономной системе Windows 2000 реализуется при помощи нескольких компонентов. Регистрацией в системе управляет программа winlogon, а аутентификацией занимаются Isass и msgina.dll. Результатом успешной регистрации в системе является новая оболочка с ассоци­ированным с ней маркером доступа. Этот процесс использует в реестре ключи SECURITY и SAM. Первый ключ определяет общую политику безопасности, а вто­рой ключ содержит информацию о защите для индивидуальных пользователей.
Как только пользователь регистрируется в системе, выполняется операция защиты при открытии объекта. Для каждого вызова ОрепХХХ требуется имя откры­ваемого объекта и набор прав доступа к нему. Во время обработки процедуры от­крытия объекта менеджер безопасности проверяет наличие у вызы­вающего процесса соответствующих прав доступа. Для этого он просматривает все маркеры доступа вызывающего процесса, а также список DACL, ассоциированный с объектом. Он просматривает по очереди элементы списка ACL. Как только он находит запись, соответствующую идентификатору SID вызывающего процесса или одной из его групп, поиск прав доступа считается законченным. Если вызыва­ющий процесс обладает необходимыми правами, объект открывается, в противном случае в открытии объекта отказывается.
Помимо разрешающих записей, списки DACL могут также содержать запре­щающие записи. Поскольку менеджер безопасности прекращает поиск, наткнув­шись на первую запись с указанным идентификатором, запрещающие записи по­мещаются в начало списка DACL, чтобы пользователь, которому строго запрещен доступ к какому-либо объекту, не смог получить его как член какой-либо группы, которой этот доступ предоставлен.
После того как объект открыт, дескриптор объекта возвращается вызываю­щему процессу. При последующих обращениях проверяется, входит ли данная операция в число операций, разрешенных в момент открытия объекта, что­бы, например, не допустить записи в файл, открытый для чтения.

Резюме

Структура операционной системы Windows 2000 включает в себя уровень аппа­ратных абстракций HAL, ядро, исполняющую систему и тонкий уровень систем­ных служб, перехватывающий входящие системные вызовы. Кроме того, опера­ционная система содержит множество драйверов устройств, включая файловую систему и интерфейс графических устройств GDI. Уровень HAL скрывает от верхних уровней определенные различия в аппаратуре. Ядро пытается скрыть от исполняющей системы остальные различия, чтобы сделать ее почти полностью машинно-независимой.
В основе исполняющей системы лежат объекты памяти. Пользовательские процессы создают их и получают дескрипторы, позволяющие управлять этими объектами. Компоненты исполняющей системы также могут создавать объекты. Менеджер объектов управляет пространством имен, в которое могут добавляться объекты для возможности их поиска по имени.
Операционная система Windows 2000 поддерживает процессы, задания, пото­ки и волокна. У процессов есть виртуальные адресные пространства, кроме того, процессы являются контейнерами ресурсов. Потоки представляют собой едини­цы исполнения и планируются операционной системой. Волокна являются уп­рощенными потоками, планируемыми полностью в пространстве пользователя. Задания представляют собой наборы процессов и используются для выделения квот ресурсов. При планировании используется приоритетный алгоритм, в кото­ром управление получает готовый поток с максимальным приоритетом.
Операционной системой Windows 2000 поддерживается виртуальная память с подкачкой по требованию. Алгоритм подкачки основан на понятии рабочего набо­ра. Система управляет несколькими списками свободных страниц, так что когда происходит страничное прерывание, как правило, у системы уже есть доступная страница. Списки свободных страниц получают страницы, отнимаемые у рабочих наборов при помощи сложных алгоритмов, пытающихся изымать в первую оче­редь давно не использовавшиеся страницы.
Ввод-вывод осуществляется драйверами устройств, согласующимися с моде­лью Windows Driver Model. При запуске каждого драйвера инициализируется объект драйвера, содержащий адреса процедур, к которым может обращаться опе­рационная система, чтобы добавить новое устройство или выполнить операцию ввода-вывода. Драйверы могут собираться в стеки или действовать как фильтры.
В основе файловой системы NTFS лежит главная файловая таблица MFT, в ко­торой содержится по одной записи для каждого файла или каталога. У каждого файла есть множество атрибутов, которые могут храниться в записи таблицы MFT или вне таблицы – на диске. Среди прочих возможностей файловая система NTFS под­держивает сжатие и шифрование.
Защита основывается на списках управления доступом ACL. У каждого про­цесса есть маркер управления доступом, идентифицирующий процесс, а также указывающий, какими особыми привилегиями он обладает. С каждым объектом ассоциирован дескриптор защиты. Дескриптор защиты указывает на список раз­граничительного управления доступом DACL, содержащий записи списка ACL, разрешающие или запрещающие доступ к этому объекту отдельным пользовате­лям или группам.
Контрольные вопросы и задания

1. Опишите структурное построение ОС Windows 2000.
2. Каковы функции так называемого уровня аппаратных абстракций Windows 2000?
3. Охарактеризуйте ядро ОС Windows 2000.
4. Какие объекты носят наименование управляющих объектов?
5. Перечислите объекты, которые относятся к типу объектов диспетчеризации.
6. Представьте состав исполняющей системы Windows 2000.
7. Какие функции выполняет менеджер объектов?
8. Какова роль менеджера процессов?
9. Для выполнения каких функций применяется интерфейс графических устройств GDI?
10. Опишите реализацию объектов в Windows 2000.
11. Какие компоненты системы Windows 2000 работают в режиме пользователя?
12. Перечислите интерфейсы прикладного программирования API, поддерживаемые Windows 2000.
13. Дайте определения понятиям «задание», «процесс», «поток», «волокно» в Windows 2000.
14. Опишите способы межпроцессного взаимодействия в Windows 2000.
15. Как осуществляется реализация процессов и потоков в Windows 2000?
16. Перечислите этапы загрузки ОС Windows 2000.
17. Что понимается под виртуальной страницей памяти и в каких состояниях она может находиться?
18. Как реализуется управление памятью в ОС Windows 2000?
19. Опишите реализацию ввода-вывода в ОС Windows 2000.
20. Какие режимы поддерживает менеджер энергопотребления?
21. Охарактеризуйте файловые системы типа FAT.
22. Представьте особенности организации файловой системы NTFS и ее достоинства по сравнению с файловыми системами типа FAT.
23. Какова структура главной файловой таблицы MFT в NTFS?
24. Каким образом производится сжатие данных файла в NTFS?
25. Перечислите и охарактеризуйте основные понятия системы безопасности Windows 2000.
26. Опишите особенности реализации защиты в ОС Windows 2000.
Заключение

Основными функциями операционных систем являются функции управления процессами и ресурсами вычислительных машин и систем. Процессы и ресурсы – это ключевые понятия операционных систем. За время своего существования процессы могут неоднократно изменять свое состояние, проходя через стадии создания, готовности, выполнения, ожидания и завершения своей работы. Операционная система организует планирование выполнения процессов, обеспечивает процессы необходимыми системными ресурсами, поддерживает взаимодействие процессов и решает проблемы их синхронизации.
При управлении таким важнейшим ресурсом, как память вычислительной машины, операционная система контролирует наличие свободной и занятой памяти, выделяет память для выполнения процессов и освобождает память после завершении процессов, реализует вытеснение процессов из оперативной памяти на дисковую память и возвращение их обратно в оперативную память, обеспечивает настройку адресов программы на конкретную область физической памяти.
Управляя устройствами ввода-вывода, операционная система передает устройствам соответствующие команды, перехватывает прерывания, обрабатывает ошибки, обеспечивает интерфейс между устройствами ввода-вывода и остальной частью машины. Для освобождения процессора от операций последовательного вывода данных из оперативной памяти или последовательного ввода в нее используется механизм прямого доступа внешних устройств к памяти.
Возможность управления файлами со стороны операционной системы на логическом уровне структур данных и операций обеспечивают различные типы файловых систем. Файловая система представляет собой набор спецификаций и соответствующее им про­граммное обеспечение, которые отвечают за создание, уничтожение, организацию, чтение, запись, модификацию и перемещение файловой информации, а также за управление доступом к файлам и за управление ресурсами, которые используют­ся файлами.
В операционных системах для многопроцессорных вычислительных машин сложность планирования процессов существенно возрастает, так как требуется не только решение вопросов очередности запуска процессов на выполнение, но и выбор центрального процессора, на котором должен быть запущен тот или иной процесс.
В многомашинных вычислительных системах механизмы организации межпроцессной взаимосвязи принципиально отличаются от организации такой взаимосвязи в автономных вычислительных машинах. Базой для взаимодействия процессов в автономных машинах служит общая разделяемая память. Многомашинные вычислительные системы по определению не имеют общей разделяемой памяти, и поэтому основой межпроцессной взаимосвязи в них служит обмен физическими сообщениями посредством коммуникационной среды.
Операционные системы многомашинных вычислительных систем распределенного типа – вычислительных сетей – обычно называют сетевыми. Фундаментальным понятием сетевых операционных систем, позволяющим определить и реализовать взаимодействие удаленных процессов, является понятие сетевого протокола.
Наиболее совершенным и перспективным классом операционных систем являются так называемые распределенные операционные системы. Распределенная система создает для пользователя полную иллюзию того, что он работает в обычной автономной системе. Эффективным способом построения распределенных операционных систем является установка специального промежуточного уровня программного обеспечения поверх сетевой операционной сис­темы, который предоставляет приложениям определенную однородность взаимодействия. Среди различных типов про­межуточного программного обеспечения следует выделить документное, файловое, объектное и координационное.
Основными принципами построения современных операционных систем являются принципы модульности, генерируемости, функциональной избыточности, виртуализации, совместимости с другими системами, открытости, легкой наращиваемости, мобильности, обеспечения надежной безопасности. Операционные системы прошли длительный путь развития и совершенствования своей архитектуры от монолитных систем до хорошо структурированных модульных систем, способных к развитию, расширению и легкому переносу на новые платформы. При модульном построении в системе выделяется некоторая часть важных программных модулей, которые для более эффективной организации вычис­лительного процесса должны постоянно находиться в оперативной памяти. Эту группу модулей называют ядром операционной системы. Другие системные программные модули (транзитные или диск-резидентные) загружаются в оперативную память только при необходимости, а в случае отсутствия свободного пространства могут быть замещены другими транзитными модулями. Для использования прикладными программами системных ресурсов операционной системы и реализуемых ею функций пред­назначен интерфейс прикладного программирования, который может быть реализован как на уровне операционной системы, так и на уровне системы программирования или на уровне внешней библиотеки процедур и функций.
Прообразом современных операционных систем являются разработанные в середине 1950-х годов системы пакетной обработки, которые были первыми системными программами, предназначенными для управления вычислительным процессом. Следующим этапом эволюции операционных систем стала разработка в 1960-х годах универсальных систем, которые были способны работать на разных типах вычислительных машин, имеющих различный набор периферийных устройств и используемых в разных областях человеческой деятельности. Существенным достижением систем этого периода явилась реализация многозадачного режима и спулинга.
Важнейшей вехой в истории и современном состоянии операциионных систем является семейство многопользовательских многозадачных систем UNIX. UNIX получила несколько ветвей развития исходной архитектуры. Двумя главными из таких ветвей стали System V (корпорации AT&T) и BSD (Калифорний­ского университета в Беркли). Впоследствии на основе обеих этих ветвей был создан ряд новых версий ОС UNIX. Третья самостоятельная ветвь развития UNIX начиналась с относительно простой «учебной» системы MINIX, за которой в 1991 году последовала зна­чительно более мощная многоза­дачная многопользовательская ОС LINUX. Операционные системы типа UNIX широко используется на вычислительных маши­нах различных классов от ноутбуков до суперкомпьютеров.
Для персональных компьютеров клона IBM PC были разработаны однопользовательские однозадачные операционные системы типа MS-DOS корпорации Microsoft и их аналоги других корпораций. Управление компьютером в среде MS-DOS осуществлялось в режиме командной строки. Для того, чтобы сделать общение с компьютером более простым, были предложены так называемые программы-оболочки, представляющие собой программные надстройки операционной системы, позволяющие пользователю осуществлять действия по управлению ресурсами компьютера в рамках более развитого и удобного, чем командная строка, псевдографического интерфейса. Следующим историческим шагом в развитии оболочек операционных систем стало появление в 1986 г. графической многооконной операционной оболочки Windows от корпорации Microsoft, которая работала на базе MS-DOS, а основой пользовательского интерфейса Windows послужил так называемый графический интерфейс пользователя GUI, представляющий собой иерархически организованную систему окон и других графических объектов. Дальнейшим развитием семейства Microsoft Windows стала разработка полноценных операционных систем Windows 95 (Windows 4.0) и Windows NT, положившим начало двух ветвей ОС от Microsoft: Windows 95/98/ME и Windows NT/2000/XP/2003.
Линия систем «новой технологии» Windows NT принципиально отличается от линии Windows 9.х. В качестве фундаментальных компонентов в состав семейства Windows NT входят развитый сетевой сервис, поддержка работы высокопроизводительных многопроцессорных вычислительных комплексов, средства обеспечения эффективной безопасности. На смену версии Windows NT 4.0. в 2000 году пришла существенно усовершенствованная и усиленная ОС Windows 2000 Professional. Серверной версией Windows 2000 Professional является система Windows 2000 Server, включающаяет множество дополнительных специальных функций. В конце 2001 г. корпорация Microsoft выпустила новую версию – Windows XP, продолжившую линию Win­dows NT. Новая серверная версия Windows Server 2003 обладает рядом преимуществ по сравнению с Windows 2000 Server.
Значительную роль в развитии ОС играет фирма Novell со своим сетевым операционными системами семейства NetWare. Историческое значение имеет система OS/2 корпорации IBM, которая появилась на рынке раньше Windows 95 и была первой 32-х разрядной операционной системой для персональных компьютеров.
Следует отметить также некоторые специализированные системы, например, предназначенные исключительно для выполнения коммуникационных задач или ориентированные на определенную аппаратную платформу компьютеров.
Любая из современных операционных систем имеет свои особенности построения и практической реализации, применяет те или иные способы и механизмы управления процессами и ресурсами, использует различные по степени универсальности и совместимости прикладные интерфейсы, обладает разным уровнем функциональности и может позиционироваться для определенных сфер применения. Для каждой из систем можно указать ее преимущества и недостатки, сильные и слабые стороны. Естественно, что предприятию или отдельному пользователю хотелось бы работать с оптимальной операционной системой, удовлетворяющей комплексу наиболее важных требований. «Идеальная» операционная система скорее всего должна иметь такую же степень интеграции и такую же поддержку, как Microsoft Windows 2000/XP/Server 2003, такую же превосходную отказоустойчивость, как Solaris 8 компании Sun Microsystems, такую же службу справочника, какой снабжена Novell NetWare 5.1, а также универсальность и гибкость, свойственные системе Linux.
Подобно другим программным продуктам информационных технологий, операционные системы постоянно совершенствуются. Основное внимание при разработке новых версий операционных систем уделяется базовым службам (файловые службы, службы печати, защиты, аутентификации, службы справочника), средствам управления, масштабируемости, применимости, надежности, службам Интернет, интрасетей и электронной коммерции. Та компания, которая в своей версии операционной системы лучше других обеспечивает эти качества, становится лидером продаж на рынке. Конкурентная борьба, как хорошо известно, является лучшим двигателем прогресса, в том числе и в области операционных систем.
Каждая из компаний – игроков на рынке операционных систем – имеет собственные планы дальнейшего развития своих продуктов.
Например, в ближайших (на момент подготовки данного учебного пособия) планах корпорации Microsoft выпуск новых версий Windows XP. К ним относятся Windows XP Media Center Edition (MCE) 2004, включающая несколько новых специализированных модулей и призванная превратить персональный компьютер в полнофункциональный развлекательный центр, а также Windows XP 64-bit Edition, являющаяся модификацией XP для компьютеров с 64-разрядными процессорами. Microsoft ведет разработку операционной системы нового поколения, именуемой как Windows Longhorn. По заявлениям Microsoft эта система станет революционной версией, основанной на внедрении новых технологий. Планируется выпуск ее 32- и 64-разрядных модификаций.
Можно надеяться, что не остановятся на достигнутом и другие компании – разработчики операционных систем, поэтому пользователей ждут еще более совершенные, функциональные, производительные и комфортные среды взаимодействия с вычислительной техникой.
Библиографический список

1. Андреев А. Г. и др. Microsoft Windows 2000 Server и Professio-nal / Под общ. ред. А.Н. Чекмарева и Д.Б. Вишнякова. – СПб.: БХВ – Петербург, 2001. – 1056 с.: ил.
2. Андреев А. Г. и др. Microsoft Windows XP. Руководство администратора/ Под общ. ред. А. Н. Чекмарева. – СПб.: БХВ – Петербург, 2003. – 848 с.: ил.
3. Бэкон Д., Харрис Т. Операционные системы. – СПб.: Питер, 2004. – 800 с.: ил.
4. Вишневский А. В. Windows Server 2003. Для профессионалов. – СПб.: Питер, 2004. – 767 с.: ил.
5. Гордеев А.В. Операционные системы. – СПб.: Питер, 2005. – 418 с.: ил.
6. Гордеев А. В., Молчанов А. Ю. Системное программное обеспечение. – СПб.: Питер, 2001. – 736 с.: ил.
7. Назаров С. В. Администрирование локальных сетей Windows NT/2000/NET: Учеб. пособие. – М.: Финансы и статистика, 2003. – 478 с.: ил.
8. Новиков Ю., Черепанов А. Персональные компьютеры: аппаратура, системы, Интернет: Учебный курс. – СПб.: Питер, 2001.– 464 с.: ил.
9. Олаф Кирх. Linux: Руководство администратора сети. – СПб.: Питер, 2000. – 242 с.: ил.
10. Олифер В.Г., Олифер Н. А. Сетевые операционные системы. – СПб.: Питер, 2001. – 544 с.: ил.
11. Основы операционных систем: Курс лекций. / В. Е. Карпов, К. А. Коньков. – М.: ИНТУИТ.РУ «Интернет-Университет Информационных Технологий», 2004. – 632 с.: ил.
12. Партыка Т. Л., Попов И. И. Операционные системы, среды и оболочки. – М.: ФОРУМ – ИНФРА-М, 2005. – 400 с.: ил.
13. Таненбаум Э. Современные операционные системы. – СПб.: Питер, 2004. – 1040 с.: ил.
14. Таненбаум Э., М. ван Стеен. Распределенные системы. Принципы и парадигмы. – СПб.: Питер, 2003. – 877 с.: ил.
15. Ханикат Дж. Знакомство с Microsoft Windows Server 2003: Пер. с англ. – М.: Издательско-торговый дом «Русская Редакция», 2003. – 464 с.: ил.
16. Чекмарев А. Н., Вишневский А. В., Кокорева О. И. Microsoft Windows Server 2003. – СПб.: БХВ – Петербург, 2003. – 1184 с.: ил.
17. Microsoft Windows 2000 Server:Учебный курс MCSA/MCSE: Пер. с англ. – М.: Издательско-торговый дом «Русская Редакция», 2002. – 912 с.: ил.
18. Microsoft Windows XP Professional: Учебный курс MCSA/MCSE: Пер. с англ. – М.: Издательско-торговый дом «Русская Редакция», 2002. – 1008 с.: ил.
Словарь терминов и определений

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

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

Критическая секция (критическая область)
часть программы, в которой осуществляется доступ к разделяемым данным.

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

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


Логическая организация файла
представление файла в виде определенным образом организованных логических записей.

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

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

Модуль операционной системы
функционально законченный элемент системы, выполненный в соответствии с принятыми межмодульными интерфей­сами.

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

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

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

Операционная среда
программ­ная среда, которую образует операционная система и в которой выполняются прикладные программы пользователей.

Планирование процессов
распределение процессов между имеющимися ресурсами.

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

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

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

Правила синхро­низации
определяют порядок взаимосвязи процессов.

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

Приоритет
характеризует степень привилегированности процесса при использовании ресурсов вычислительной машины.

Процесс
последовательность операций при выполнении программы или ее части в совокупности с используемыми данными.

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

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

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

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

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





Файл
набор данных, организованных в виде совокупности записей определенной структуры.

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

Физическая организация файла
описывает правила расположения файла на устройстве внешней памяти, в частности, на диске.

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






Алфавитно-предметный указатель

А
Адрес
виртуальный 34
физический 34

Б
Блок информации 81
Блокирующая переменная 23
Буфер обмена 26

В
Виртуальная память 38
Виртуальное адресное простран-
ство 34
Взаимоблокировка 26
Вызов
блокирующй (синхроннй) 82
неблокирующй (асинхроннй) 82
Вычислительная (компьютерная)
сеть 81
Вычислительная машина
многопроцессорная 65, 80
однопроцессорная 8, 65
Вычислительная система
многомашинная 80
сосредоточенного типа 80
распределенного типа 80

Д
Дескриптор процесса 12
Диспетчеризация 12
Доступ к файлу
избирательный 57
мандатный 57

И
Инкапсуляция 110
Интерфейс-оболочка 7, 121, 137
Интерфейс
графический 7, 111, 123,
командной строки 7, 122
К
Квантование 13
Кластер 55
Контекст процесса 12
Контроллер прямого доступа
к памяти 49
Критическая секция (критическая
область) программы 23
Кэш-память 43

Л
Логическая запись 55
Локальность данных в кэш-памяти
пространственная 44
временная 44

М
Метод очередей ресурсов 12
Механизм прямого доступа к
памяти 49
Многозадачность
вытесняющая 14, 15
невытесняющая 14, 15
Многопоточность 29
Многоуровневая модель 58
Модуль операционной системы 97
Монитор 27
Мультипрограммный режим 119
Мультипроцессор 65
Мьютекс 25

О
Операционная среда 6, 7
Операционная система (ОС) 6, 7
Операционная оболочка 7, 121
Организация файла
логическая 55
физическая 55


П
Пакетная обработка информации 118
Перемещающий загрузчик 34, 35
Планирование процессов 11, 69
Подсистема
буферизации 57
управления процессами 11
Поток 28, 29
Право доступа к файлу 57
Прерывание 16
внешнее (асинхронное) 18
внутреннее (синхронное) 18
программное 19
Прикладной программный
интерфейс 51, 112, 113
Приоритет 1, 13, 19
Процесс 8
взаимодействующий 9
взаимосвязанный 9
внешний 9
внутренний 9
выполняемый 11
готовый 11
завершенный 11
изолированный 9
интерактивный 8
конкурирующий 9
новый 11
ожидающий 11
пакетный 8
пользовательский 9
порождающий 8
порож­денный 8
равный 8
реального времени 8
системный 21
тождественный 8
эквивалентный 8

Р
Распределенная операционная
система 89, 90



Распределение памяти
страничное 38
сегментное 41
странично-сегментное 42
Реентерабельность 97

Ресурс 10
активный 10
виртуальный 10
воспроизводимый 10
временный 10
второстепенный 10
жесткий 10
информационный 10
ис­пользуемый параллельно 10
ис­пользуемый последовательно 10
основной 10
пассивный 10
постоянный 10
потребляе­мый 10
программный 10
простой 10
со­ставной 10
физический 10
эластичный 10

С
Свопинг 42
Сетевая операционная система 88
Сетевой протокол 88
Семафор 24, 25
Символьное имя 34
Система управления файлами 50, 51
Сообщение 81
Спин-блокировка 24
Спулинг 48
Стаб
клиентский 86
серверный 87
Супервизор прерываний 21



Т
Транзитный программный модуль
98
Таблица управления процессом
(ТУП) 11

У
Устройство ввода-вывода 44
байт-ориентированное 45
блок-ориентированное 45
выделенное 46
разделяемое 46

Ф
Фрагментация памяти 36
Файл 10, 50
двоичный 54
каталог 54
обычный 54
специальный 54
текстовый 54
Файловая система 50, 51

Я
Ядро операционной системы 97, 98






Храпский Сергей Филиппович






ОПЕРАЦИОННЫЕ СИСТЕМЫ, СРЕДЫ И ОБОЛОЧКИ

ОСНОВНЫЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ




Редактор Л. Г.Сигитова







Лицензия ЛР № 021278 от 06.04.98 г.
Подписано в печать 01.09.05. Формат 60х84 1/16.
Бумага типогр. Оперативная печать.
Усл. печ. л. 15.05. Уч.-изд. л. 14,35. Тираж 60 экз.
Издат. № 366. Заказ 387. Цена договорная.




Издательско-полиграфический центр ОГИС
644099, Омск, ул. Красногвардейская, 9