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

grep main a.c b.c c.c

где "main" - подстрока, поиск которой производится в файлах a.c, b.c и c.c с
выдачей в файл стандартного вывода тех строк, в которых она содержится. Со-
держимое выводного файла может быть следующим:

a.c: main(argc,argv)
c.c: /* here is the main loop in the program */
c.c: main()

Программа wc с необязательным параметром -l подсчитывает число строк в файле
стандартного ввода. Командная строка

grep main a.c b.c c.c | wc -l

вызовет подсчет числа строк в указанных файлах, где будет обнаружена подст-
рока "main"; выводной поток команды grep поступит непосредственно на вход
команды wc. Для предыдущего примера результат будет такой:
3

Использование каналов зачастую делает ненужным создание временных файлов.


    1.4 ФУНКЦИИ ОПЕРАЦИОННОЙ СИСТЕМЫ



На Рисунке 1.1 уровень ядра операционной системы изображен непосредст-
венно под уровнем прикладных программ пользователя. Выполняя различные эле-
ментарные операции по запросам пользовательских процессов, ядро обеспечивает
функционирование пользовательского интерфейса, описанного выше. Среди функ-

17

ций ядра можно отметить:
* Управление выполнением процессов посредством их создания, завершения или
приостановки и организации взаимодействия между ними.
* Планирование очередности предоставления выполняющимся процессам времени
центрального процессора (диспетчеризация). Процессы работают с центральным
процессором в режиме разделения времени: центральный процессор (*****) вы-
полняет процесс, по завершении отсчитываемого ядром кванта времени процесс
приостанавливается и ядро активизирует выполнение другого процесса. Позд-
нее ядро запускает приостановленный процесс.
* Выделение выполняемому процессу оперативной памяти. Ядро операционной сис-
темы дает процессам возможность совместно использовать участки адресного
пространства на определенных условиях, защищая при этом адресное простран-
ство, выделенное процессу, от вмешательства извне. Если системе требуется
свободная память, ядро освобождает память, временно выгружая процесс на
внешние
запоминающие устройства, которые называют устройствами выгрузки. Если ядро
выгружает процессы на устройства выгрузки целиком, такая реализация систе-
мы UNIX называется системой со свопингом (подкачкой); если же на устройст-
во выгрузки выводятся страницы памяти, такая система называется системой с
замещением страниц.
* Выделение внешней памяти с целью обеспечения эффективного хранения инфор-
мации и выборка данных пользователя. Именно в процессе реализации этой
функции создается файловая система. Ядро выделяет внешнюю память под поль-
зовательские файлы, мобилизует неиспользуемую память, структурирует файло-
вую систему в форме, доступной для понимания, и защищает пользовательские
файлы от несанкционированного доступа.
* Управление доступом процессов к периферийным устройствам, таким как терми-
налы, ленточные устройства, дисководы и сетевое оборудование.
Выполнение ядром своих функций довольно очевидно. Например, оно узнает,
что данный файл является обычным файлом или устройством, но скрывает это
различие от пользовательских процессов. Так же оно, форматируя информацию
файла для внутреннего хранения, защищает внутренний формат от пользователь-
ских процессов, возвращая им неотформатированный поток байтов. Наконец, ядро
реализует ряд необходимых функций по обеспечению выполнения процессов поль-
зовательского уровня, за исключением функций, которые могут быть реализованы
на самом пользовательском уровне. Например, ядро выполняет действия, необхо-
димые shell'у как интерпретатору команд: оно позволяет процессору shell чи-
тать вводимые с терминала данные, динамически порождать процессы, синхрони-
зировать выполнение процессов, открывать каналы и переадресовывать ввод-вы-
вод. Пользователи могут разрабатывать свои версии командного процессора
shell с тем, чтобы привести рабочую среду в соответствие со своими требова-
ниями, не затрагивая других пользователей. Такие программы пользуются теми
же услугами ядра, что и стандартный процессор shell.


    1.5 ПРЕДПОЛАГАЕМАЯ АППАРАТНАЯ СРЕДА



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


--------------------------
(*****) В главе 12 рассматриваются многопроцессорные системы; до того речь
будет идти об однопроцессорной модели.

18

система еще выполняет учетные операции, связанные с пользовательским процес-
сом, обрабатывает прерывания, планирует процессы, управляет распределением
памяти и т.д. Большинство вычислительных систем разнообразной архитектуры (и
соответствующие им операционные системы) поддерживают большее число уровней,
чем указано здесь, однако уже двух режимов, режима задачи и режима ядра,
вполне достаточно для системы UNIX.
Основные различия между этими двумя режимами:
* В режиме задачи процессы имеют доступ только к своим собственным инструк-
циям и данным, но не к инструкциям и данным ядра (либо других процессов).
Однако в режиме ядра процессам уже доступны адресные пространства ядра и
пользователей. Например, виртуальное адресное пространство процесса может
быть поделено на адреса, доступные только в режиме ядра, и на адреса, дос-
тупные в любом режиме.
* Некоторые машинные команды являются привилегированными и вызывают возник-
новение ошибок при попытке их использования в режиме задачи. Например, в
машинном языке может быть команда, управляющая регистром состояния процес-
сора; процессам, выполняющимся в


Процессы

A B C D
+-------+-------+-------+-------+
Режим ядра | Я | | | Я |
+-------+-------+-------+-------+
Режим задачи | | З | З | |
+-------+-------+-------+-------+

Рисунок 1.5. Процессы и режимы их выполнения


режиме задачи, она недоступна.
Проще говоря, любое взаимодействие с аппаратурой описывается в терминах
режима ядра и режима задачи и протекает одинаково для всех пользовательских
программ, выполняющихся в этих режимах. Операционная система хранит внутрен-
ние записи о каждом процессе, выполняющемся в системе. На Рисунке 1.5 пока-
зано это разделение: ядро делит процессы A, B, C и D, расположенные вдоль
горизонтальной оси, аппаратные средства вводят различия между режимами вы-
полнения, расположенными по вертикали.
Несмотря на то, что система функционирует в одном из двух режимов, ядро
действует от имени пользовательского процесса. Ядро не является какой-то
особой совокупностью процессов, выполняющихся параллельно с пользовательски-
ми, оно само выступает составной частью любого пользовательского процесса.
Сделанный вывод будет скорее относиться к "ядру", распределяющему ресурсы,
или к "ядру", производящему различные операции, и это будет означать, что
процесс, выполняемый в режиме ядра, распределяет ресурсы и производит соот-
ветствующие операции. Например, командный процессор shell считывает вводной
поток с терминала с помощью запроса к операционной системе. Ядро операцион-
ной системы, выступая от имени процессора shell, управляет функционированием
терминала и передает вводимые символы процессору shell. Shell переходит в
режим задачи, анализирует поток символов, введенных пользователем и выполня-
ет заданную последовательность действий, которые могут потребовать выполне-
ния и других системных операций.


    1.5.1 Прерывания и особые ситуации



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

19

сора. По получении сигнала прерывания ядро операционной системы сохраняет
свой текущий контекст (застывший образ выполняемого процесса), устанавливает
причину прерывания и обрабатывает прерывание. После того, как прерывание бу-
дет обработано ядром, прерванный контекст восстановится и работа продолжится
так, как будто ничего не случилось. Устройствам обычно приписываются приори-
теты в соответствии с очередностью обработки прерываний. В процессе обработ-
ки прерываний ядро учитывает их приоритеты и блокирует обслуживание прерыва-
ния с низким приоритетом на время обработки прерывания с более высоким прио-
ритетом.
Особые ситуации связаны с возникновением незапланированных событий, выз-
ванных процессом, таких как недопустимая адресация, задание привилегирован-
ных команд, деление на ноль и т.д. Они отличаются от прерываний, которые вы-
зываются событиями, внешними по отношению к процессу. Особые ситуации возни-
кают прямо "посредине" выполнения команды, и система, обработав особую ситу-
ацию, пытается перезапустить команду; считается, что прерывания возникают
между выполнением двух команд, при этом система после обработки прерывания
продолжает выполнение процесса уже начиная со следующей команды. Для обра-
ботки прерываний и особых ситуаций в системе UNIX используется один и тот же
механизм.


    1.5.2 Уровни прерывания процессора



Ядро иногда обязано предупреждать возникновение прерываний во время кри-
тических действий, могущих в случае прерывания запортить информацию. Напри-
мер, во время обработки списка с указателями возникновение прерывания от
диска для ядра нежелательно, т.к. при обработке прерывания можно запортить
указатели, что можно увидеть на примере в следующей главе. Обычно имеется
ряд привилегированных команд, устанавливающих уровень прерывания процессора
в слове состояния процессора. Установка уровня прерывания на определенное
значение отсекает прерывания этого и более низких уровней, разрешая обработ-
ку только прерываний с более высоким приоритетом. На Рисунке 1.6 показана
последовательность уровней прерывания. Если ядро игнорирует прерывания от
диска, в этом случае игнорируются и все остальные прерывания, кроме прерыва-
ний от часов и машинных сбоев.

+------------------------------+ ^
| Машинные сбои | |
+------------------------------+ |
| Системные часы | Высокий приоритет
+------------------------------+ |
| Диск | |
+------------------------------+ |
| Сетевое оборудование | |
+------------------------------+ |
| Терминалы | Низкий приоритет
+------------------------------+ |
| Программные прерывания | |
+------------------------------+ v

Рисунок 1.6. Стандартные уровни прерываний



    1.5.3 Распределение памяти



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

20

ющихся адресами переменных и информационных структур, а также адресами инст-
рукций и функций. Компилятор генерирует адреса для виртуальной машины так,
словно на физической машине не будет выполняться параллельно с транслируемой
ни одна другая программа.
Когда программа запускается на выполнение, ядро выделяет для нее место в
оперативной памяти, при этом совпадение виртуальных адресов, сгенерированных
компилятором, с физическими адресами совсем необязательно. Ядро, взаимодейс-
твуя с аппаратными средствами, транслирует виртуальные адреса в физические,
т.е. отображает адреса, сгенерированные компилятором, в физические, машинные
адреса. Такое отображение опирается на возможности аппаратных средств, поэ-
тому компоненты системы UNIX, занимающиеся им, являются машинно-зависимыми.
Например, отдельные вычислительные машины имеют специальное оборудование для
подкачки выгруженных страниц памяти. Главы 6 и 9 посвящены более подробному
рассмотрению вопросов, связанных с распределением памяти, и исследованию их
соотношения с аппаратными средствами.


    1.6 ВЫВОДЫ



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




















21


ГЛАВА 2

    ВВЕДЕНИЕ В АРХИТЕКТУРУ ЯДРА ОПЕРАЦИОННОЙ СИСТЕМЫ





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


    2.1 АРХИТЕКТУРА ОПЕРАЦИОННОЙ СИСТЕМЫ UNIХ



Как уже ранее было замечено (см. [Christian 83], стр.239), в системе
UNIX создается иллюзия того, что файловая система имеет "места" и что у про-
цессов есть "жизнь". Обе сущности, файлы и процессы, являются центральными
понятиями модели операционной системы UNIX. На Рисунке 2.1 представлена
блок-схема ядра системы, отражающая состав модулей, из которых состоит ядро,
и их взаимосвязи друг с другом. В частности, на ней слева изображена файло-
вая подсистема, а справа подсистема управления процессами, две главные ком-
поненты ядра. Эта схема дает логическое представление о ядре, хотя в дейст-
вительности в структуре ядра имеются отклонения от модели, поскольку отдель-
ные модули испытывают внутреннее воздействие со стороны других модулей.
Схема на Рисунке 2.1 имеет три уровня: уровень пользователя, уровень яд-
ра и уровень аппаратуры. Обращения к операционной системе и библиотеки сос-
тавляют границу между пользовательскими программами и ядром, проведенную на
Рисунке 1.1. Обращения к операционной системе выглядят так же, как обычные
вызовы функций в программах на языке Си, и библиотеки устанавливают соответ-
ствие между этими вызовами функций и элементарными системными операция-
ми, о чем более подробно см. в главе 6. При этом программы на ассемблере мо-
гут обращаться к операционной системе непосредственно, без использования
библиотеки системных вызовов. Программы часто обращаются к другим библиоте-
кам, таким как библиотека стандартных подпрограмм ввода-вывода, достигая тем
самым более полного использования системных услуг. Для этого во время компи-
ляции библиотеки связываются с программами и частично включаются в программу
пользователя. Далее мы проиллюстрируем эти моменты на примере.

На рисунке совокупность обращений к операционной системе разделена на те
обращения, которые взаимодействуют с подсистемой управления файлами, и те,
которые взаимодействуют с подсистемой управления процессами. Файловая под-
система управляет файлами, размещает записи файлов, управляет свободным
пространством, доступом к файлам и поиском данных для пользователей. Процес-
сы взаимодействуют с подсистемой управления файлами, используя при этом со-
вокупность специальных обращений к операционной системе, таких как open
(для того, чтобы открыть файл на чтение или запись),close, read, write, stat
(запросить атрибуты файла), chown (изменить запись с информацией о владельце
файла) и chmod (изменить права доступа к файлу). Эти и другие операции расс-
матриваются в главе 5.
Подсистема управления файлами обращается к данным, которые хранятся в
файле, используя буферный механизм, управляющий потоком данных между ядром и
устройствами внешней памяти. Буферный механизм, взаимодействуя с драйверами
устройств ввода-вывода блоками, инициирует передачу данных к ядру и обратно.
Драйверы устройств являются такими модулями в составе ядра, которые управля-
ют работой периферийных устройств. Устройства ввода-вывода блоками относятся



22

программы пользователя
^
| +----------------------+
точка пере- | | библиотеки |
сечения ---------|------- +----------------------+
- | - ^
Уровень пользователя - | - |
--------------------------|---------------------|-----------------
Уровень ядра v v
+---------------------------------------------------+
| ^ обращения к операционной системе ^ |
+------+------------------------------------+-------+
| |
+-----------------+---------------+ +----------------+---------+
| v | | v |
| | | |
| подсистема управле- | | ............|
| ния файлами | | . взаимо- .|
| <---+-+ | . действие .|
| | | | . процессов.|
| ^ ^ | | | подсистема ............|
| | | | | | ............|
+-------+--------------+----------+ | | . планиров-.|
| v +-+> управления . щик .|
| +--------------+ | ............|
| | буфер сверх- | | ............|
| | оперативной | | процессами . распреде-.|
| | памяти (кеш) | | . ление .|
| +--------------+ | . памяти .|
| ^ | ^ ............|
| | | | |
| v +-------+------------------+
+-------+----------------------+ |
| v . | |
| символ . блок | |
| . | |
+------------------------------+ |
| | |
| драйверы устройств | |
| ^ | |
+--------------+---------------+ |
| |
+--------------+------------------------------+------------------+
| v аппаратный контроль v |
+----------------------------------------------------------------+
Уровень ядра
------------------------------------------------------------------ Уровень
аппаратуры
+----------------------------------------------------------------+
| технические средства (аппаратура) |
+----------------------------------------------------------------+

Рисунок 2.1. Блок-схема ядра операционной системы


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

23

мать это устройство как запоминающее устройство с произвольной выборкой.
Подсистема управления файлами также непосредственно взаимодействует с драй-
верами устройств "неструктурированного" ввода-вывода, без вмешательства бу-
ферного механизма. К устройствам неструктурированного ввода-вывода, иногда
именуемым устройствами посимвольного ввода-вывода (текстовыми), относятся
устройства, отличные от устройств ввода-вывода блоками.
Подсистема управления процессами отвечает за синхронизацию процессов,
взаимодействие процессов, распределение памяти и планирование выполнения
процессов. Подсистема управления файлами и подсистема управления процессами
взаимодействуют между собой, когда файл загружается в память на выполнение
(см. главу 7): подсистема управления процессами читает в память исполняемые
файлы перед тем, как их выполнить.
Примерами обращений к операционной системе, используемых при управлении
процессами, могут служить fork (создание нового процесса), exec (наложение
образа программы на выполняемый процесс), exit (завершение выполнения про-
цесса), wait (синхронизация продолжения выполнения основного процесса с мо-
ментом выхода из порожденного процесса), brk (управление размером памяти,
выделенной процессу) и signal (управление реакцией процесса на возникновение
экстраординарных событий). Глава 7 посвящена рассмотрению этих и других сис-
темных вызовов.
Модуль распределения памяти контролирует выделение памяти процессам. Ес-
ли в какой-то момент система испытывает недостаток в физической памяти для
запуска всех процессов, ядро пересылает процессы между основной и внешней
памятью с тем, чтобы все процессы имели возможность выполняться. В главе 9
описываются два способа управления распределением памяти: выгрузка (подкач-
ка) и замещение страниц. Программу подкачки иногда называют планировщиком,
т.к. она "планирует" выделение памяти процессам и оказывает влияние на рабо-
ту планировщика центрального процессора. Однако в дальнейшем мы будем ста-
раться ссылаться на нее как на "программу подкачки", чтобы избежать путаницы
с планировщиком центрального процессора.
Модуль "планировщик" распределяет между процессами время центрального
процессора. Он планирует очередность выполнения процессов до тех пор, пока
они добровольно не освободят центральный процессор, дождавшись выделения
к.-л. ресурса, или до тех пор, пока ядро системы не выгрузит их после того,
как их время выполнения превысит заранее определенный квант времени. Плани-
ровщик выбирает на выполнение готовый к запуску процесс с наивысшим приори-
тетом; выполнение предыдущего процесса (приостановленного) будет продолжено
тогда, когда его приоритет будет наивысшим среди приоритетов всех готовых к
запуску процессов. Существует несколько форм взаимодействия процессов между
собой, от асинхронного обмена сигналами о событиях до синхронного обмена со-
общениями.
Наконец, аппаратный контроль отвечает за обработку прерываний и за связь
с машиной. Такие устройства, как диски и терминалы, могут прерывать работу
центрального процессора во время выполнения процесса. При этом ядро системы
после обработки прерывания может возобновить выполнение прерванного процес-
са. Прерывания обрабатываются не самими процессами, а специальными функциями
ядра системы, перечисленными в контексте выполняемого процесса.


    2.2 ВВЕДЕНИЕ В ОСНОВНЫЕ ПОНЯТИЯ СИСТЕМЫ



В это разделе дается обзор некоторых основных информационных структур,
используемых ядром системы, и более подробно описывается функционирование
модулей ядра, показанных на Рисунке 2.1.


    2.2.1 Обзор особенностей подсистемы управления файлами



Внутреннее представление файла описывается в индексе, который содержит

24

описание размещения информации файла на диске и другую информацию, такую как
владелец файла, права доступа к файлу и время доступа. Термин "индекс"
(inode) широко используется в литературе по системе UNIX. Каждый файл имеет
один индекс, но может быть связан с несколькими именами, которые все отража-
ются в индексе. Каждое имя является указателем. Когда процесс обращается к
файлу по имени, ядро системы анализирует по очереди каждую компоненту имени
файла, проверяя права процесса на просмотр входящих в путь поиска каталогов,
и в конце концов возвращает индекс файла. Например, если процесс обращается
к системе:

open("/fs2/mjb/rje/sourcefile", 1);

ядро системы возвращает индекс для файла "/fs2/mjb/rje/sourcefile". Если
процесс создает новый файл, ядро присваивает этому файлу неиспользуемый ин-
декс. Индексы хранятся в файловой системе (и это мы еще увидим), однако при
обработке файлов ядро заносит их в таблицу индексов в оперативной памяти.
Ядро поддерживает еще две информационные структуры, таблицу файлов и
пользовательскую таблицу дескрипторов файла. Таблица файлов выступает гло-
бальной структурой ядра, а пользовательская таблица дескрипторов файла выде-